summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2011-11-30 16:29:55 +0000
committerJonny Lamb <jonny.lamb@collabora.co.uk>2011-11-30 16:29:55 +0000
commitb40460beb2fd3ba1f737f9d830fe731ac7fca846 (patch)
tree59f63bd76da6528a896ab0c865b23215d29f8433
parent92d7ce512fa88d6e3ffb4c4da4aebeb7e529035a (diff)
parent0167647ac7ca5f1f7966ed3fb3d261f85039238d (diff)
Merge remote-tracking branch 'salut/testing'
-rw-r--r--salut/.gitignore156
-rw-r--r--salut/.gitmodules3
-rw-r--r--salut/AUTHORS1
-rw-r--r--salut/Android.mk37
-rw-r--r--salut/COPYING542
-rw-r--r--salut/ChangeLog0
-rw-r--r--salut/Makefile.am25
-rw-r--r--salut/NEWS402
-rw-r--r--salut/README77
-rwxr-xr-xsalut/autogen.sh59
-rw-r--r--salut/configure.ac297
-rw-r--r--salut/data/Makefile.am22
-rw-r--r--salut/data/salut.service.in3
-rw-r--r--salut/docs/Makefile.am22
-rw-r--r--salut/docs/clique.xml168
-rw-r--r--salut/docs/telepathy-salut.8.in39
-rw-r--r--salut/docs/tubes.txt59
-rw-r--r--salut/extensions/Connection_Future.xml110
-rw-r--r--salut/extensions/Makefile.am123
-rw-r--r--salut/extensions/OLPC_Activity_Properties.xml123
-rw-r--r--salut/extensions/OLPC_Buddy_Info.xml269
-rw-r--r--salut/extensions/Salut_Plugin_Test.xml27
-rw-r--r--salut/extensions/all.xml66
-rw-r--r--salut/extensions/connection.xml28
-rw-r--r--salut/extensions/extensions.c6
-rw-r--r--salut/extensions/extensions.h10
-rw-r--r--salut/lib/Makefile.am1
-rw-r--r--salut/lib/ext/Makefile.am16
m---------salut/lib/ext/wocky0
-rw-r--r--salut/lib/gibber/AUTHORS1
-rw-r--r--salut/lib/gibber/COPYING542
-rw-r--r--salut/lib/gibber/Makefile.am123
-rw-r--r--salut/lib/gibber/gibber-bytestream-direct.c639
-rw-r--r--salut/lib/gibber/gibber-bytestream-direct.h75
-rw-r--r--salut/lib/gibber/gibber-bytestream-iface.c184
-rw-r--r--salut/lib/gibber/gibber-bytestream-iface.h94
-rw-r--r--salut/lib/gibber/gibber-bytestream-muc.c426
-rw-r--r--salut/lib/gibber/gibber-bytestream-muc.h69
-rw-r--r--salut/lib/gibber/gibber-bytestream-oob.c1105
-rw-r--r--salut/lib/gibber/gibber-bytestream-oob.h73
-rw-r--r--salut/lib/gibber/gibber-debug.c99
-rw-r--r--salut/lib/gibber/gibber-debug.h81
-rw-r--r--salut/lib/gibber/gibber-fd-transport.c548
-rw-r--r--salut/lib/gibber/gibber-fd-transport.h96
-rw-r--r--salut/lib/gibber/gibber-file-transfer.c509
-rw-r--r--salut/lib/gibber/gibber-file-transfer.h135
-rw-r--r--salut/lib/gibber/gibber-linklocal-transport.c199
-rw-r--r--salut/lib/gibber/gibber-linklocal-transport.h84
-rw-r--r--salut/lib/gibber/gibber-listener.c540
-rw-r--r--salut/lib/gibber/gibber-listener.h99
-rw-r--r--salut/lib/gibber/gibber-muc-connection.c772
-rw-r--r--salut/lib/gibber/gibber-muc-connection.h117
-rw-r--r--salut/lib/gibber/gibber-multicast-transport.c494
-rw-r--r--salut/lib/gibber/gibber-multicast-transport.h84
-rw-r--r--salut/lib/gibber/gibber-oob-file-transfer.c1056
-rw-r--r--salut/lib/gibber/gibber-oob-file-transfer.h70
-rw-r--r--salut/lib/gibber/gibber-r-multicast-causal-transport.c1402
-rw-r--r--salut/lib/gibber/gibber-r-multicast-causal-transport.h121
-rw-r--r--salut/lib/gibber/gibber-r-multicast-packet.c917
-rw-r--r--salut/lib/gibber/gibber-r-multicast-packet.h262
-rw-r--r--salut/lib/gibber/gibber-r-multicast-sender.c2156
-rw-r--r--salut/lib/gibber/gibber-r-multicast-sender.h185
-rw-r--r--salut/lib/gibber/gibber-r-multicast-transport.c1764
-rw-r--r--salut/lib/gibber/gibber-r-multicast-transport.h78
-rw-r--r--salut/lib/gibber/gibber-sockets-unix.h61
-rw-r--r--salut/lib/gibber/gibber-sockets-win32.h29
-rw-r--r--salut/lib/gibber/gibber-sockets.c112
-rw-r--r--salut/lib/gibber/gibber-sockets.h49
-rw-r--r--salut/lib/gibber/gibber-tcp-transport.c290
-rw-r--r--salut/lib/gibber/gibber-tcp-transport.h67
-rw-r--r--salut/lib/gibber/gibber-transport.c296
-rw-r--r--salut/lib/gibber/gibber-transport.h131
-rw-r--r--salut/lib/gibber/gibber-unix-transport.c414
-rw-r--r--salut/lib/gibber/gibber-unix-transport.h117
-rw-r--r--salut/lib/gibber/gibber-util.c96
-rw-r--r--salut/lib/gibber/gibber-util.h36
-rw-r--r--salut/lib/gibber/tests/Makefile.am80
-rw-r--r--salut/lib/gibber/tests/check-gibber-listener.c214
-rw-r--r--salut/lib/gibber/tests/check-gibber-r-multicast-causal-transport.c567
-rw-r--r--salut/lib/gibber/tests/check-gibber-r-multicast-packet.c239
-rw-r--r--salut/lib/gibber/tests/check-gibber-r-multicast-sender.c693
-rw-r--r--salut/lib/gibber/tests/check-gibber-unix-transport.c242
-rw-r--r--salut/lib/gibber/tests/mesh.py235
-rw-r--r--salut/lib/gibber/tests/simplemeshtest.py101
-rw-r--r--salut/lib/gibber/tests/test-r-multicast-transport-io.c226
-rw-r--r--salut/lib/gibber/tests/test-transport.c209
-rw-r--r--salut/lib/gibber/tests/test-transport.h73
-rw-r--r--salut/m4/Makefile.am2
-rw-r--r--salut/m4/as-compiler-flag.m433
-rw-r--r--salut/m4/ax_config_dir.m4109
-rw-r--r--salut/m4/salut-args.m456
-rw-r--r--salut/m4/salut-gcov.m454
-rw-r--r--salut/m4/salut-lcov.m422
-rw-r--r--salut/m4/salut-valgrind.m431
-rw-r--r--salut/m4/tp-compiler-flag.m436
-rw-r--r--salut/m4/tp-compiler-warnings.m440
-rw-r--r--salut/plugins/Makefile.am32
-rw-r--r--salut/plugins/test.c147
-rw-r--r--salut/plugins/test.h65
-rw-r--r--salut/rules/check.mak121
-rw-r--r--salut/rules/lcov.mak29
-rw-r--r--salut/salut/Makefile.am16
-rw-r--r--salut/salut/capabilities.h73
-rw-r--r--salut/salut/capability-set.h86
-rw-r--r--salut/salut/caps-channel-manager.h108
-rw-r--r--salut/salut/connection.h53
-rw-r--r--salut/salut/plugin.h161
-rw-r--r--salut/salut/protocol.h91
-rw-r--r--salut/salut/sidecar.h67
-rw-r--r--salut/salut/telepathy-salut-uninstalled.pc.in15
-rw-r--r--salut/salut/telepathy-salut.pc.in15
-rw-r--r--salut/salut/util.h29
-rw-r--r--salut/src/Makefile.am265
-rw-r--r--salut/src/avahi-contact-manager.c336
-rw-r--r--salut/src/avahi-contact-manager.h67
-rw-r--r--salut/src/avahi-contact.c890
-rw-r--r--salut/src/avahi-contact.h75
-rw-r--r--salut/src/avahi-discovery-client.c388
-rw-r--r--salut/src/avahi-discovery-client.h72
-rw-r--r--salut/src/avahi-muc-channel.c332
-rw-r--r--salut/src/avahi-muc-channel.h72
-rw-r--r--salut/src/avahi-muc-manager.c207
-rw-r--r--salut/src/avahi-muc-manager.h69
-rw-r--r--salut/src/avahi-olpc-activity-manager.c381
-rw-r--r--salut/src/avahi-olpc-activity-manager.h68
-rw-r--r--salut/src/avahi-olpc-activity.c546
-rw-r--r--salut/src/avahi-olpc-activity.h70
-rw-r--r--salut/src/avahi-roomlist-manager.c442
-rw-r--r--salut/src/avahi-roomlist-manager.h69
-rw-r--r--salut/src/avahi-self.c557
-rw-r--r--salut/src/avahi-self.h69
-rw-r--r--salut/src/capabilities.c62
-rw-r--r--salut/src/capabilities.h30
-rw-r--r--salut/src/capability-set.c753
-rw-r--r--salut/src/caps-channel-manager.c128
-rw-r--r--salut/src/caps-hash.c80
-rw-r--r--salut/src/caps-hash.h31
-rw-r--r--salut/src/connection-contact-info.c375
-rw-r--r--salut/src/connection-contact-info.h38
-rw-r--r--salut/src/connection-manager.c202
-rw-r--r--salut/src/connection-manager.h63
-rw-r--r--salut/src/connection.c4110
-rw-r--r--salut/src/connection.h97
-rw-r--r--salut/src/contact-manager.c449
-rw-r--r--salut/src/contact-manager.h91
-rw-r--r--salut/src/contact.c911
-rw-r--r--salut/src/contact.h196
-rw-r--r--salut/src/debug.c143
-rw-r--r--salut/src/debug.h76
-rw-r--r--salut/src/disco.c592
-rw-r--r--salut/src/disco.h103
-rw-r--r--salut/src/discovery-client.c168
-rw-r--r--salut/src/discovery-client.h112
-rw-r--r--salut/src/dummy-discovery-client.c134
-rw-r--r--salut/src/dummy-discovery-client.h63
-rw-r--r--salut/src/file-transfer-channel.c1908
-rw-r--r--salut/src/file-transfer-channel.h92
-rw-r--r--salut/src/ft-manager.c735
-rw-r--r--salut/src/ft-manager.h66
-rw-r--r--salut/src/gabble_namespaces.h27
-rw-r--r--salut/src/im-channel.c738
-rw-r--r--salut/src/im-channel.h70
-rw-r--r--salut/src/im-manager.c647
-rw-r--r--salut/src/im-manager.h64
-rw-r--r--salut/src/muc-channel.c1422
-rw-r--r--salut/src/muc-channel.h92
-rw-r--r--salut/src/muc-manager.c1205
-rw-r--r--salut/src/muc-manager.h84
-rw-r--r--salut/src/namespaces.h123
-rw-r--r--salut/src/olpc-activity-manager.c350
-rw-r--r--salut/src/olpc-activity-manager.h98
-rw-r--r--salut/src/olpc-activity.c700
-rw-r--r--salut/src/olpc-activity.h98
-rw-r--r--salut/src/plugin-loader.c362
-rw-r--r--salut/src/plugin-loader.h88
-rw-r--r--salut/src/plugin.c139
-rw-r--r--salut/src/presence-cache.c691
-rw-r--r--salut/src/presence-cache.h87
-rw-r--r--salut/src/presence.h53
-rw-r--r--salut/src/protocol.c329
-rw-r--r--salut/src/roomlist-channel.c692
-rw-r--r--salut/src/roomlist-channel.h76
-rw-r--r--salut/src/roomlist-manager.c549
-rw-r--r--salut/src/roomlist-manager.h86
-rw-r--r--salut/src/salut.c63
-rw-r--r--salut/src/self.c1081
-rw-r--r--salut/src/self.h172
-rw-r--r--salut/src/sha1/sha1-util.c57
-rw-r--r--salut/src/sha1/sha1-util.h32
-rw-r--r--salut/src/si-bytestream-manager.c884
-rw-r--r--salut/src/si-bytestream-manager.h81
-rw-r--r--salut/src/sidecar.c47
-rw-r--r--salut/src/symbol-hacks.c115
-rw-r--r--salut/src/symbol-hacks.h8
-rw-r--r--salut/src/text-helper.c361
-rw-r--r--salut/src/text-helper.h60
-rw-r--r--salut/src/tube-dbus.c1883
-rw-r--r--salut/src/tube-dbus.h88
-rw-r--r--salut/src/tube-iface.c224
-rw-r--r--salut/src/tube-iface.h75
-rw-r--r--salut/src/tube-stream.c2511
-rw-r--r--salut/src/tube-stream.h87
-rw-r--r--salut/src/tubes-channel.c2606
-rw-r--r--salut/src/tubes-channel.h94
-rw-r--r--salut/src/tubes-manager.c1275
-rw-r--r--salut/src/tubes-manager.h71
-rw-r--r--salut/src/util.c391
-rw-r--r--salut/src/util.h35
-rw-r--r--salut/src/write-mgr-file.c359
-rw-r--r--salut/tests/Makefile.am66
-rw-r--r--salut/tests/README62
-rw-r--r--salut/tests/causalorderingtest.py82
-rw-r--r--salut/tests/check-node-properties.c260
-rw-r--r--salut/tests/continous-failure.py51
-rw-r--r--salut/tests/debug.c40
-rw-r--r--salut/tests/died-node.py53
-rw-r--r--salut/tests/dlopen.supp127
-rw-r--r--salut/tests/failmeshtest.py118
-rw-r--r--salut/tests/failnamemeshtest.py118
-rw-r--r--salut/tests/repair-after-fail-test.py154
-rw-r--r--salut/tests/repair-after-node-disconnected-test.py189
-rw-r--r--salut/tests/twisted/Makefile.am132
-rw-r--r--salut/tests/twisted/avahi/aliases.py170
-rw-r--r--salut/tests/twisted/avahi/caps-file-transfer.py243
-rw-r--r--salut/tests/twisted/avahi/caps-self.py73
-rw-r--r--salut/tests/twisted/avahi/caps-tubes.py791
-rw-r--r--salut/tests/twisted/avahi/close-local-pending-room.py91
-rw-r--r--salut/tests/twisted/avahi/file-transfer/file_transfer_helper.py507
-rw-r--r--salut/tests/twisted/avahi/file-transfer/ft-client-caps.py423
-rw-r--r--salut/tests/twisted/avahi/file-transfer/ichat-receive-directory.py43
-rw-r--r--salut/tests/twisted/avahi/file-transfer/ichat-receive-file.py44
-rw-r--r--salut/tests/twisted/avahi/file-transfer/ichat-send-file-declined.py55
-rw-r--r--salut/tests/twisted/avahi/file-transfer/ichat-send-file.py60
-rw-r--r--salut/tests/twisted/avahi/file-transfer/metadata.py83
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-and-send-file.py36
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file-and-disconnect.py18
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file-and-sender-disconnect-while-pending.py32
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file-and-sender-disconnect-while-transfering.py29
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file-and-xmpp-disconnect.py28
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file-cancelled-immediately.py29
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file-decline.py44
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file-ipv6.py80
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file-not-found.py58
-rw-r--r--salut/tests/twisted/avahi/file-transfer/receive-file.py6
-rw-r--r--salut/tests/twisted/avahi/file-transfer/send-file-and-cancel-immediately.py41
-rw-r--r--salut/tests/twisted/avahi/file-transfer/send-file-and-disconnect.py17
-rw-r--r--salut/tests/twisted/avahi/file-transfer/send-file-declined.py41
-rw-r--r--salut/tests/twisted/avahi/file-transfer/send-file-ipv6.py48
-rw-r--r--salut/tests/twisted/avahi/file-transfer/send-file-item-not-found.py40
-rw-r--r--salut/tests/twisted/avahi/file-transfer/send-file-provide-immediately.py30
-rw-r--r--salut/tests/twisted/avahi/file-transfer/send-file-to-unknown-contact.py29
-rw-r--r--salut/tests/twisted/avahi/file-transfer/send-file-wait-to-provide.py43
-rw-r--r--salut/tests/twisted/avahi/ichat-composing.py75
-rw-r--r--salut/tests/twisted/avahi/ichat-incoming-msg.py66
-rw-r--r--salut/tests/twisted/avahi/muc-invite.py120
-rw-r--r--salut/tests/twisted/avahi/olpc-activity-announcements.py115
-rw-r--r--salut/tests/twisted/avahi/register.py36
-rw-r--r--salut/tests/twisted/avahi/request-im.py163
-rw-r--r--salut/tests/twisted/avahi/request-muc.py157
-rw-r--r--salut/tests/twisted/avahi/roomlist.py174
-rw-r--r--salut/tests/twisted/avahi/set-presence.py61
-rw-r--r--salut/tests/twisted/avahi/text-channel.py87
-rw-r--r--salut/tests/twisted/avahi/tubes/disabled-1-1-tubes.py62
-rw-r--r--salut/tests/twisted/avahi/tubes/offer-private-stream-tube.py360
-rw-r--r--salut/tests/twisted/avahi/tubes/request-invalid-dbus-tube.py62
-rw-r--r--salut/tests/twisted/avahi/tubes/request-muc-tubes.py155
-rw-r--r--salut/tests/twisted/avahi/tubes/tube-close.py71
-rw-r--r--salut/tests/twisted/avahi/tubes/tubes-to-nonexistant-ids.py54
-rw-r--r--salut/tests/twisted/avahi/tubes/tubetestutil.py122
-rw-r--r--salut/tests/twisted/avahi/tubes/two-muc-dbus-tubes.py275
-rw-r--r--salut/tests/twisted/avahi/tubes/two-muc-stream-tubes.py376
-rw-r--r--salut/tests/twisted/avahi/tubes/two-private-stream-tubes.py329
-rwxr-xr-xsalut/tests/twisted/avahimock.py430
-rw-r--r--salut/tests/twisted/avahitest.py269
-rw-r--r--salut/tests/twisted/caps_helper.py377
-rw-r--r--salut/tests/twisted/cm/protocol.py66
-rw-r--r--salut/tests/twisted/constants.py444
-rw-r--r--salut/tests/twisted/ipv6.py181
-rw-r--r--salut/tests/twisted/ns.py80
-rw-r--r--salut/tests/twisted/saluttest.py332
-rw-r--r--salut/tests/twisted/servicetest.py631
-rw-r--r--salut/tests/twisted/sidecars.py61
-rw-r--r--salut/tests/twisted/tools/Makefile.am33
-rw-r--r--salut/tests/twisted/tools/exec-with-log.sh.in32
-rw-r--r--salut/tests/twisted/tools/run_and_bt.gdb3
-rw-r--r--salut/tests/twisted/tools/salut.service.in3
-rw-r--r--salut/tests/twisted/tools/tmp-session-bus.conf.in30
-rw-r--r--salut/tests/twisted/tools/with-session-bus.sh112
-rw-r--r--salut/tests/twisted/trivialstream.py70
-rw-r--r--salut/tests/twisted/xmppstream.py257
-rw-r--r--salut/tests/valgrind.supp711
-rw-r--r--salut/tools/Makefile.am51
-rw-r--r--salut/tools/c-constants-generator.xsl299
-rw-r--r--salut/tools/c-interfaces-generator.xsl84
-rw-r--r--salut/tools/check-c-style.sh69
-rw-r--r--salut/tools/check-coding-style.mk17
-rw-r--r--salut/tools/check-misc.sh13
-rw-r--r--salut/tools/check-whitespace.sh17
-rw-r--r--salut/tools/doc-generator.xsl758
-rw-r--r--salut/tools/glib-client-marshaller-gen.py59
-rw-r--r--salut/tools/glib-errors-enum-body.xsl72
-rw-r--r--salut/tools/glib-errors-enum-header.xsl73
-rw-r--r--salut/tools/glib-ginterface-gen.py711
-rw-r--r--salut/tools/glib-gtypes-generator.py230
-rw-r--r--salut/tools/glib-interfaces-body-generator.xsl47
-rw-r--r--salut/tools/glib-interfaces-generator.xsl55
-rw-r--r--salut/tools/glib-signals-marshal-gen.py55
-rw-r--r--salut/tools/identity.xsl7
-rw-r--r--salut/tools/libglibcodegen.py320
-rw-r--r--salut/tools/make-release-mail.py79
-rw-r--r--salut/tools/telepathy.am74
-rw-r--r--salut/tools/xep.xsl909
312 files changed, 77996 insertions, 0 deletions
diff --git a/salut/.gitignore b/salut/.gitignore
new file mode 100644
index 000000000..509272c10
--- /dev/null
+++ b/salut/.gitignore
@@ -0,0 +1,156 @@
+.*.swp
+*~
+
+tags
+cscope.out
+
+.deps
+.libs
+*.hi
+*.o
+*.lo
+*.la
+*.loT
+*.o.cmd
+*.ko.cmd
+*.mod.c
+*.tmp_versions
+CVS
+RCS
+*~
+_darcs
+*.bak
+*.BAK
+*.orig
+vssver.scc
+*.swp
+MT
+{arch}
+.arch-ids
+,
+*.class
+*.prof
+.DS_Store
+BitKeeper
+ChangeSet
+.svn
+*.pyc
+*.gcda
+*.gcno
+*.gcov
+
+*.pyo
+*#
+.\#*
+.cvsignore
+Thumbs.db
+autom4te.cache
+Android.mk
+Makefile
+Makefile.in
+INSTALL
+.libs
+.deps
+aclocal.m4
+autom4te.cache
+compile
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+depcomp
+
+enumtypes.c
+enumtypes.h
+
+m4/libtool.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
+
+/coverage/
+
+data/org.freedesktop.Telepathy.ConnectionManager.salut.service
+data/*.service
+data/salut.manager
+
+depcomp
+docs/xep.xsl
+docs/clique.html
+docs/telepathy-salut.8
+
+docs/clique.html
+extensions/_gen
+extensions/extensions.html
+
+lib/gibber/examples/test_{ssl,tcp}
+
+tests/telepathy-salut-debug
+install-sh
+lib/gibber/gibber-signals-marshal.c
+lib/gibber/gibber-signals-marshal.h
+lib/gibber/gibber-signals-marshal.list
+lib/gibber/*-enumtypes.c
+lib/gibber/*-enumtypes.h
+lib/gibber/examples/test_ssl
+lib/gibber/examples/test_tcp
+libtool
+ltmain.sh
+missing
+src/signals-marshal.c
+src/signals-marshal.h
+src/signals-marshal.list
+src/*-enumtypes.c
+src/*-enumtypes.h
+src/telepathy-salut
+src/write-mgr-file
+stamp-h1
+telepathy-salut-*.tar.gz
+tests/check-main
+tests/test-r-multicast-{packet,sender,transport-io}
+tests/twisted/config.py
+tests/twisted/tools/core
+tests/twisted/tools/exec-with-log.sh
+tests/twisted/tools/salut-testing.log*
+tests/twisted/tools/tmp-session-bus.conf
+tests/twisted/tools/org.freedesktop.Telepathy.ConnectionManager.salut.service
+tests/twisted/tools/refdbg.log
+/tests/twisted/with-session-bus*.address
+/tests/twisted/with-session-bus*.pid
+
+tools/libglibcodegen.pyc
+
+src/telepathy-salut
+src/write-mgr-file
+
+/telepathy-salut-*/
+/telepathy-salut-*.tar.gz
+/telepathy-salut-*.tar.gz.asc
+tests/test-r-multicast-packet
+tests/test-r-sender
+tests/test-r-transport-io
+tests/telepathy-salut-debug
+tests/check-node-properties
+
+lib/gibber/tests/check-main
+lib/gibber/tests/outputs
+lib/gibber/tests/sasl-test.db
+lib/gibber/tests/test-xmpp-connection
+lib/gibber/tests/test-r-multicast-transport-io
+lib/gibber/tests/check-gibber-listener
+lib/gibber/tests/check-gibber-r-multicast-causal-transport
+lib/gibber/tests/check-gibber-r-multicast-packet
+lib/gibber/tests/check-gibber-r-multicast-sender
+lib/gibber/tests/check-gibber-unix-transport
+
+salut/telepathy-salut.pc
+salut/telepathy-salut-uninstalled.pc
diff --git a/salut/.gitmodules b/salut/.gitmodules
new file mode 100644
index 000000000..f61abb4ef
--- /dev/null
+++ b/salut/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "lib/ext/wocky"]
+ path = lib/ext/wocky
+ url = git://anongit.freedesktop.org/wocky
diff --git a/salut/AUTHORS b/salut/AUTHORS
new file mode 100644
index 000000000..93b800c18
--- /dev/null
+++ b/salut/AUTHORS
@@ -0,0 +1 @@
+Sjoerd Simons <sjoerd@luon.net>
diff --git a/salut/Android.mk b/salut/Android.mk
new file mode 100644
index 000000000..a87e49843
--- /dev/null
+++ b/salut/Android.mk
@@ -0,0 +1,37 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+TELEPATHY_SALUT_BUILT_SOURCES := \
+ src/Android.mk \
+ extensions/Android.mk \
+ lib/gibber/Android.mk
+
+telepathy-salut-configure-real:
+ cd $(TELEPATHY_SALUT_TOP) ; \
+ CC="$(CONFIGURE_CC)" \
+ CFLAGS="$(CONFIGURE_CFLAGS)" \
+ LD=$(TARGET_LD) \
+ LDFLAGS="$(CONFIGURE_LDFLAGS)" \
+ CPP=$(CONFIGURE_CPP) \
+ CPPFLAGS="$(CONFIGURE_CPPFLAGS)" \
+ PKG_CONFIG_LIBDIR=$(CONFIGURE_PKG_CONFIG_LIBDIR) \
+ PKG_CONFIG_TOP_BUILD_DIR=$(PKG_CONFIG_TOP_BUILD_DIR) \
+ $(TELEPATHY_SALUT_TOP)/$(CONFIGURE) --host=arm-linux-androideabi \
+ --disable-submodules \
+ --disable-Werror && \
+ for file in $(TELEPATHY_SALUT_BUILT_SOURCES); do \
+ rm -f $$file && \
+ make -C $$(dirname $$file) $$(basename $$file) ; \
+ done
+
+telepathy-salut-configure: telepathy-salut-configure-real
+
+.PHONY: telepathy-salut-configure
+
+CONFIGURE_TARGETS += telepathy-salut-configure
+
+#include all the subdirs...
+-include $(TELEPATHY_SALUT_TOP)/src/Android.mk
+-include $(TELEPATHY_SALUT_TOP)/extensions/Android.mk
+-include $(TELEPATHY_SALUT_TOP)/lib/gibber/Android.mk
diff --git a/salut/COPYING b/salut/COPYING
new file mode 100644
index 000000000..e3819249c
--- /dev/null
+++ b/salut/COPYING
@@ -0,0 +1,542 @@
+Most of Salut is licensed under the GNU Lesser General Public License,
+as published by the Free Software Foundation and reproduced below:
+either version 2.1 of the License, or (at your option) any later version.
+
+xep.xsl in the the tools/ directory, used to generate HTML documentation
+for the Clique protocol, is copyright 1999-2008 by the
+XMPP Standards Foundation (XSF) and is released under a MIT-style
+license, reproduced below.
+
+------------------------------------------------------------------------
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+------------------------------------------------------------------------
+
+License of tools/xep.xsl:
+
+ Copyright (c) 1999 - 2008 XMPP Standards Foundation
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
diff --git a/salut/ChangeLog b/salut/ChangeLog
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/salut/ChangeLog
diff --git a/salut/Makefile.am b/salut/Makefile.am
new file mode 100644
index 000000000..f960e59fb
--- /dev/null
+++ b/salut/Makefile.am
@@ -0,0 +1,25 @@
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS = tools extensions lib src data m4 tests docs plugins salut
+
+DISTCHECK_CONFIGURE_FLAGS = --disable-debug --enable-gtk-doc
+
+valgrind:
+ cd tests && $(MAKE) valgrind
+
+torture:
+ cd tests && $(MAKE) torture
+
+forever:
+ cd tests && $(MAKE) forever
+
+include $(top_srcdir)/rules/lcov.mak
+
+# There doesn't seem to be a telepathy-salut-0.4.0 tag! :o
+CHANGELOG_RANGE = 22651d24..
+CHECK_FOR_UNRELEASED = \
+ $(srcdir)/NEWS \
+ $(wildcard $(srcdir)/salut/*.[ch]) \
+ $(wildcard $(srcdir)/src/*.[ch])
+
+include tools/telepathy.am
diff --git a/salut/NEWS b/salut/NEWS
new file mode 100644
index 000000000..d2f7d7f20
--- /dev/null
+++ b/salut/NEWS
@@ -0,0 +1,402 @@
+telepathy-salut 0.7.1 (UNRELEASED)
+==================================
+
+y u no fill news file?
+
+telepathy-salut 0.7.0 (2011-11-16)
+==================================
+
+This is the start of a new development branch that will lead to 0.8
+in roughly 6 months.
+
+Enhancements:
+
+• fd.o#42348: the Chan.I.FileTransfer.Metadata interface has been
+ implemented. (jonny)
+
+• Updated Wocky:
+ · The SASL auth server test now builds with new and old versions of
+ libsasl2.
+
+telepathy-salut 0.6.0 (2011-10-18)
+==================================
+
+The “the cleaning lady… cleans up… dust. She dusts. And she has weekends
+off, so… Monday. Right?” release. Please enjoy this refreshing stable
+branch.
+
+Enhancements since 0.5.2:
+
+• Salut implements the ContactList interface, which makes
+ TP_CONNECTION_FEATURE_CONTACT_LIST in telepathy-glib work with Salut!
+ (xclaesse)
+
+Big changes since 0.4.0:
+
+• Salut now mostly uses the Wocky XMPP library.
+
+• Protocol, ContactCapabilities and (half of) ContactInfo are
+ implemented.
+
+• Salut has grown a plugin API, rather like Gabble's.
+
+telepathy-salut 0.5.2 (2011-10-05)
+==================================
+
+Enhancements:
+
+ • fd.o#40035: the ContactInfo Connection interface has been
+ implemented, so extra information about contacts, like first and
+ last names, are now exposed in a nice way and can be shown in
+ clients. (wjt)
+
+Fixes:
+
+ • Ensure OLPC tests are included in releases. (jonny)
+
+ • Fix send_ll_pep_event public utility function to construct PEP
+ messages correctly. (jonny)
+
+Dependencies:
+
+ • xmldiff is no longer required for tests.
+
+telepathy-salut 0.5.1 (2011-07-07)
+==================================
+
+The “nyanit.com is high up in the referrals list” release. This release
+fixes a crash triggered by recent versions of telepathy-glib, and a
+crash triggered by portscanning yourself.
+
+Enhancements:
+
+ • More bits and pieces of Gibber have been replaced by Wocky. (jonny)
+
+Fixes:
+
+ • Assumptions made about the default value of
+ TpChannelIface:handle-type (which changed in a recent telepathy-glib
+ release) are now less specific, fixing assertion failures. (jonny,
+ sjoerd)
+
+ • Salut no longer crashes if you 'nmap localhost'. (fd.o#39018, jonny)
+
+ • The tests now pass with telepathy-glib >= 0.14.6, which corrected a
+ longstanding bug in MembersChangedDetailed which saluttest.py was
+ relying on. The tests should still work with older versions, too.
+ (wjt)
+
+
+telepathy-salut 0.5.0 (2011-05-03)
+==================================
+
+Enhancements:
+
+ * The wocky library used in telepathy-gabble has been added as a
+ submodule and is starting to replace bits of Gibber such As
+ GibberXmppNode → WockyNode, and GibberXmppStanza →
+ WockyStanza. Keep on reading for more. (smcv, jonny)
+
+ * The test suite now has a mock Avahi service, so running the tests
+ no longer uses the system avahi and therefore doesn't pop up
+ contacts on other users' contact lists. You can hit the actual
+ network if you want though, see tests/README. (tomeu, smcv, jonny)
+
+ * Implement ofdT.Protocol objects for exposed protocols. (smcv)
+
+ * Replace the capability channel manager interfaces with the new ones
+ already used in Gabble which make the caps situation a lot more
+ sane. (smcv)
+
+ * Implement ContactCapabilities. (jonny)
+
+ * fd.o#33833: Implement FileTransfer.FileURI (cassidy)
+
+ * Use Wocky's caps hashing code, and so throw out Salut's own code
+ for this. (jonny)
+
+ * Give Salut a plugin API, based on the Gabble one. The API consists
+ of a little bit of Salut, Wocky and tp-glib. Plugins can expose new
+ protocols and can add channel managers. Brill. (jonny)
+
+ * The C tests now use GLib instead of check. (stefw)
+
+ * Salut's XMPP connection manager has been replaced with Wocky's Meta
+ Porter. This is better in many ways. (jonny)
+
+ * Add salut_send_ll_pep_event() function to send a pep message (this
+ is basically the only step needed for link-local PEP
+ support). (jonny)
+
+Fixes:
+
+ * Ensure to reply to disco requests with the same id attribute and
+ other test reliability fixes. (smcv)
+
+ * fd.o#31665: use TpDBusDaemon, not tp_get_bus(). (smcv)
+
+Dependencies:
+
+ * xmldiff is now a required dependency otherwise the gibber tests
+ fail. It has always been required but it was never checked at
+ configure time.
+
+ * GLib ≥ 2.24 is now required.
+
+ * telepathy-glib ≥ 0.13.12 is now required.
+
+telepathy-salut 0.4.0 (2010-09-06)
+===================================
+
+The "a stable branch that doesn't go anywhere is the most stable of all"
+release.
+
+Fixes:
+
+* Remove the GibberResolver and all its infrastructure (smcv)
+* Use the normal, safer telepathy-glib priv idioms (smcv)
+* Add salut_connection_get_implemented_interfaces (smcv)
+* Make tests more robust (smcv)
+* Remove examples and OpenSSL support from libgibber (smcv)
+* remove SASL support (smcv)
+* remove now-unused MD5 implementation (smcv)
+* Remove the activity when its channel has been closed, as Gabble does (tomeu)
+
+telepathy-salut 0.3.13 (2010-08-19)
+===================================
+
+The "treitter has a list somewhere on the telepathy wiki if you're stuck"
+release.
+
+Enhancements:
+
+* Implement avatar requirements properties (Zdra)
+
+* Add a ActivityProperties.GetActivity method for retrieving an activity's room
+ handle from its id (tomeu)
+
+* Add a BuddyInfo.AddActivity method so activities can advertise themselves
+ without having to track all the other shared activities (tomeu)
+
+Fixes:
+
+* Update with-session-bus.sh from telepathy-glib, fixing a bashism (smcv)
+
+telepathy-salut 0.3.12 (2010-05-20)
+===================================
+
+The "maybe your browser has turned religious" release.
+
+Enhancements:
+
+* Merge back most of the portability improvements from Gabble's copy of Gibber
+ (smcv)
+
+* Use automake silent rules (--enable-silent-rules) instead of shave (smcv)
+
+Fixes:
+
+* fd.o #22970, Debian #565154: don't try to support credential-passing on
+ non-Linux platforms, fixing compilation there (smcv)
+
+* fd.o #27289: set the Properties flag on group channels for round-trip
+ reduction (cassidy)
+
+* fd.o #20732 (partial): allow the user to leave MUCs with RemoveMembers()
+ (smcv)
+
+* Implement the error path for ContactList channel requests correctly (smcv)
+
+* Fix compilation in the tr_TR.UTF-8 locale (Maiku)
+
+* Use the right timestamps on message delivery reports (Jonny)
+
+* Stop working around fd.o #15092, which was fixed in telepathy-glib (cassidy)
+
+* fd.o #26152: make critical warnings fatal by default (smcv)
+
+telepathy-salut 0.3.11 (2010-03-25)
+===================================
+The "Not really" release.
+
+Enhancements:
+* Implement TpMessageMixin and therefore the Channel.Interface.Messages.
+
+Fixes:
+* Fix strict aliasing warnings from gcc.
+* Fix assertion due to g_set_prgname being called twice.
+
+
+telepathy-salut 0.3.10 (2009-09-10)
+===================================
+The "The lampshade of doom" release.
+
+Requirements:
+* telepathy-glib >= 0.7.36 is now required so Salut emits the correct
+ D-Bus errors. This version is also requiered as we implement the
+ new tube API and the Debug interface.
+
+Enhancements:
+* Implement the final, stable new API for tubes. The NewConnection signal has
+ been renamed to NewRemoteConnection and now has a Connection_ID argument. The
+ StreamTube channel type now implements the NewLocalConnection and
+ ConnectionClosed signals. DBusTube.{Offer,Accept} now have an access_control
+ argument.
+
+* Use ContactCapabilities draft to communicate tube support.
+
+* In StreamTube, UNIX sockets created with the Localhost access control are
+ usable by any user of the system.
+
+* Implement Debug interface.
+
+Fixes:
+* Include muc tube channels in Requests's Channels property.
+
+* Implement setting presence before the connection is
+ established. This allows salut to be used with mission control >=
+ 5.2.2.
+
+* Make the "dnd" presence actually correspond to the correct telepathy
+ presence type.
+
+
+telepathy-salut 0.3.9 (2009-04-02)
+==================================
+The "My, what big branches you have, Grandma!" release.
+
+Enhancements:
+* Implement Connection.Interface.ContactCapabilities.DRAFT
+* Implement tube service specific contact capabilities.
+* Implement file transfer contact capabilities.
+* Implement Channel.Type.StreamTube.DRAFT
+
+Fixes:
+* As defined in the spec, {Accept,Provide}File should return an 'ay' and not
+ a string as UNIX socket address.
+* Fix a crash with newer versions of libsoup
+* Fix file transfer over IPv6 (b.fd.o #19163)
+
+
+telepathy-salut 0.3.8 (2009-02-17)
+==================================
+The "Thursday as if I walked in the dessert" release.
+
+Enhancements:
+* Switched to the stable version of the FileTransfer interface. Clients
+ (Empathy, Sugar...) have to be modified to use the stable interface instead
+ of the DRAFT one. Just the interface name has been renamed so changes
+ should be trivial.
+
+Fixes:
+* Fix a crash when receiving an incoming XMPP connection from a IPv6 address.
+
+
+telepathy-salut 0.3.7 (2009-01-05)
+==================================
+The "Post-Waffle Hibernation" release.
+
+Requirements:
+* libsoup-2.4 (instead of libsoup-2.2)
+
+Enhancements:
+* State of pending file transfer channels is now automatically changed to
+ "Cancelled" if peer is disconnected.
+
+Fixes:
+* Uses libsoup 2.4 instead of the obsolete 2.2 version (b.fd.o #18891).
+* Don't crash if the XMPP connection is disconnected during a file transfer.
+* Fix a race in stream tubes causing potential data lost.
+* Fix various leaks including one leading to a crash (b.fd.o #19181).
+
+
+telepathy-salut 0.3.6 (2008-12-02)
+==================================
+The "I accidentally 93MB of .rar files" release.
+
+Requirements:
+
+* glib >= 2.16
+* dbus >= 1.1.0
+* telepathy-glib >= 0.7.17
+* libsoup-2.2
+
+Enhancements:
+
+* Implement org.freedesktop.Telepathy.Channel.Type.FileTransfer.DRAFT.
+ This means you can now send files to your Salut contacts. Current
+ implementation uses OOB (XEP-0066) and is fully compatible with iChat.
+* All channels are now requestable using the new
+ org.freedesktop.Telepathy.Connection.Interface.Requests interface.
+
+Fixes:
+
+* Assertion failed in gibber_multicast_transport_disconnect when closing
+ a local pending room (b.fd.o #18552).
+
+
+telepathy-salut 0.3.5 (2008-09-17)
+==================================
+The "Please don't flood my network" release.
+
+This release fixes an annoying bug causing Salut announcing all the OLPC
+activities which are present on the network. You should consider upgrading
+if they are OLPC XO's running on your network.
+
+Enhancements:
+
+* Add a test framework
+
+Fixes:
+
+* Only announce OLPC activity we actually joined (dev.laptop.org #8441)
+
+telepathy-salut 0.3.4 (2008-08-20)
+==================================
+The "a ball of hair" release.
+
+Requirements:
+
+* telepathy-glib >= 0.7.14
+
+Enhancements:
+
+* Implement the Contacts interface
+* Implement the SimplePresence interface
+* Implement spec 0.17.9 (Add various properties to all channels)
+
+telepathy-salut 0.3.3 (2008-06-05)
+==================================
+This unstable release mainly brings an abstraction of the avahi layer making
+it easier to use different implementions of mdns or other completely different
+service discovery implementations. Furthermore various small bugfixes has been
+done and the complete code-base has been converted to telepathy coding style.
+
+telepathy-salut 0.3.2 (2008-04-09)
+==================================
+This unstable release doesn't contain any new features or major changes but
+fixes various Clique and tube bugs. These are basically the same fixes as in
+the 0.2.3 stable release.
+
+telepathy-salut 0.3.1 (2008-02-15)
+==================================
+This unstable release fixes few Clique crashers and some others issues.
+Most of these bugs were discovered thanks to hyperactivity, a
+collaboration stress testing tool we are developing for the OLPC project.
+
+This new version also introduces a new DNS resolver in Gibber but it's not
+used in Salut yet.
+
+telepathy-salut 0.3.0 (2008-01-08)
+==================================
+This is the first release in the unstable 0.3 series.
+
+Apart from some small bugfixes this updates the Avatar interface to comply with
+the current telepathy spec and uses avahi-gobject instead of the internal avahi
+gobject wrappers. This means at least avahi 0.6.22 is needed for this version.
+
+As salut doesn't have persistent avatar storage, the clients need to ensure the
+avatar is set upon each connection. See sf bug #1825366 [0] for the suggested
+changes to telepathy-mission-control.
+
+[0] http://sourceforge.net/tracker/index.php?func=detail&aid=1825366&group_id=190214&atid=932444
diff --git a/salut/README b/salut/README
new file mode 100644
index 000000000..081accc12
--- /dev/null
+++ b/salut/README
@@ -0,0 +1,77 @@
+===============
+telepathy-salut
+===============
+
+Salut is a link-local XMPP (XEP-0174) connection manager for the Telepathy
+framework, currently supporting presence and single-user chats with iChat
+interoperability, and multi-user chats and Tubes using the Clique protocol
+<http://telepathy.freedesktop.org/wiki/Clique>.
+
+Telepathy is a D-Bus framework for unifying real time communication,
+including instant messaging, voice calls and video calls. It abstracts
+differences between protocols to provide a unified interface for
+applications.
+
+Requirements
+============
+
+telepathy-salut requires:
+ telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/>
+ libxml2 <http://xmlsoft.org/>
+ avahi-gobject <http://avahi.org/download/>
+ GLib, GObject <http://ftp.gnome.org/pub/GNOME/sources/glib/>
+ libdbus <http://dbus.freedesktop.org/releases/dbus/>
+ The D-Bus GLib bindings <http://dbus.freedesktop.org/releases/dbus-glib/>
+and optionally uses:
+ libasyncns <http://0pointer.de/lennart/projects/libasyncns/>
+
+At build time, it also requires:
+ GNU make <http://www.gnu.org/software/make/>
+ pkg-config <http://ftp.gnome.org/pub/GNOME/sources/pkg-config/>
+ libxslt, xsltproc <http://xmlsoft.org/XSLT/>
+ Python <http://www.python.org/>
+
+See configure.ac for full details, including versions required.
+
+Building from Darcs also requires the GNU build system (Autoconf, Automake,
+libtool).
+
+Bugs, feature requests and to-do list
+=====================================
+
+Report all bugs, feature requests and "to-do" items here:
+ <https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=telepathy-salut>
+
+Versioning policy
+=================
+
+We use an "odd/even" versioning scheme where the minor version (the y in
+x.y.z) determines stability - stable branches have y even, development
+branches have y odd.
+
+Unreleased builds straight from Darcs identify themselves as version
+"x.y.z.1". These are compiled with -Werror, so they might stop working
+if your gcc version issues more warnings than ours. If this is a problem
+for you, use a release tarball.
+
+Contact info
+============
+
+This package is maintained by the Telepathy project:
+ <http://telepathy.freedesktop.org/>
+ <mailto:telepathy@lists.freedesktop.org>
+ <irc://irc.freenode.net/telepathy>
+
+Telepathy development is supported by Collabora Ltd.
+ <http://www.collabora.co.uk/>.
+
+Hacking
+=======
+
+The current version of telepathy-salut is always available from:
+ <http://git.collabora.co.uk/?p=telepathy-salut.git>
+
+Proposed patches awaiting review can usually be found in Merge Monkey:
+ <http://monkey.collabora.co.uk/>
+
+Please follow <http://telepathy.freedesktop.org/wiki/Style> in new code.
diff --git a/salut/autogen.sh b/salut/autogen.sh
new file mode 100755
index 000000000..4da1c1ffe
--- /dev/null
+++ b/salut/autogen.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+set -e
+
+if test -n "$AUTOMAKE"; then
+ : # don't override an explicit user request
+elif automake-1.11 --version >/dev/null 2>/dev/null && \
+ aclocal-1.11 --version >/dev/null 2>/dev/null; then
+ # If we have automake-1.11, use it. This is the oldest version (=> least
+ # likely to introduce undeclared dependencies) that will give us
+ # --enable-silent-rules support.
+ AUTOMAKE=automake-1.11
+ export AUTOMAKE
+ ACLOCAL=aclocal-1.11
+ export ACLOCAL
+fi
+
+autoreconf -i -f
+
+#Check if building submodules
+build_submodules=true
+for arg in $*; do
+ case $arg in
+ --disable-submodules)
+ build_submodules=false
+ ;;
+*)
+;;
+esac
+done
+
+if test $build_submodules = true; then
+ # Fetch Wocky if needed
+ if test ! -f lib/ext/wocky/autogen.sh;
+ then
+ echo "+ Setting up Wocky submodule"
+ git submodule init
+ fi
+ git submodule update
+
+ # launch Wocky's autogen.sh
+ cd lib/ext/wocky
+ sh autogen.sh --no-configure
+ cd ../../..
+fi
+
+run_configure=true
+for arg in $*; do
+ case $arg in
+ --no-configure)
+ run_configure=false
+ ;;
+ *)
+ ;;
+ esac
+done
+
+if test $run_configure = true; then
+ ./configure "$@"
+fi
diff --git a/salut/configure.ac b/salut/configure.ac
new file mode 100644
index 000000000..2c2ee9e1a
--- /dev/null
+++ b/salut/configure.ac
@@ -0,0 +1,297 @@
+AC_PREREQ([2.59])
+
+# Making releases:
+# set the new version number:
+# odd minor -> development series
+# even minor -> stable series
+# increment micro for each release within a series
+# set salut_nano_version to 0.
+
+m4_define([salut_major_version], [0])
+m4_define([salut_minor_version], [7])
+m4_define([salut_micro_version], [0])
+m4_define([salut_nano_version], [1])
+
+# Some magic
+m4_define([salut_base_version],
+ [salut_major_version.salut_minor_version.salut_micro_version])
+m4_define([salut_version],
+ [m4_if(salut_nano_version, 0, [salut_base_version], [salut_base_version].[salut_nano_version])])dnl
+
+AC_INIT([Telepathy Salut], [salut_version],
+[https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=telepathy-salut])
+
+AC_CONFIG_MACRO_DIR([m4])
+AC_LANG([C])
+AM_INIT_AUTOMAKE([1.9 -Wno-portability tar-ustar])
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES])
+AM_PROG_LIBTOOL
+AM_CONFIG_HEADER(config.h)
+
+dnl check for tools
+AC_PROG_CC
+AC_PROG_CC_STDC
+AM_PROG_AS
+AM_PROG_MKDIR_P
+
+dnl add common command line switches
+SALUT_ARG_DEBUG
+SALUT_ARG_VALGRIND
+SALUT_ARG_COVERAGE
+
+dnl decide error flags
+ifelse(salut_nano_version, 0,
+ [ official_release=yes ],
+ [ official_release=no ])
+
+TP_COMPILER_WARNINGS([ERROR_CFLAGS], [test "x$official_release" = xno],
+ [all \
+ extra \
+ declaration-after-statement \
+ shadow \
+ strict-prototypes \
+ missing-prototypes \
+ sign-compare \
+ nested-externs \
+ pointer-arith \
+ format-security \
+ init-self],
+ [missing-field-initializers \
+ unused-parameter])
+AC_SUBST([ERROR_CFLAGS])
+
+ifelse(salut_nano_version, 0,
+ [ # Salut is version x.y.z - disable coding style checks by default
+AC_ARG_ENABLE(coding-style-checks,
+ AC_HELP_STRING([--enable-coding-style-checks],
+ [check coding style using grep]),
+ [ENABLE_CODING_STYLE_CHECKS=$enableval], [ENABLE_CODING_STYLE_CHECKS=no] )
+ ],
+ [ # Salut is version x.y.z.1 - enable coding style checks by default
+AC_ARG_ENABLE(coding-style-checks,
+ AC_HELP_STRING([--disable-coding-style-checks],
+ [do not check coding style using grep]),
+ [ENABLE_CODING_STYLE_CHECKS=$enableval], [ENABLE_CODING_STYLE_CHECKS=yes])
+ ])
+
+if test x$enable_debug = xyes; then
+ AC_DEFINE(ENABLE_DEBUG, [], [Enable debug code])
+fi
+
+AC_SUBST([ENABLE_CODING_STYLE_CHECKS])
+
+dnl Check for code generation tools
+XSLTPROC=
+AC_CHECK_PROGS([XSLTPROC], [xsltproc])
+if test -z "$XSLTPROC"; then
+ AC_MSG_ERROR([xsltproc (from the libxslt source package) is required])
+fi
+
+AM_PATH_PYTHON([2.5])
+
+# Check for a python >= 2.5 with twisted to run python tests
+AC_MSG_CHECKING([for Python with Avahi, Twisted and XMPP protocol support])
+if $PYTHON -c "import twisted.words.xish.domish, twisted.words.protocols.jabber, twisted.internet.reactor, avahi" >/dev/null 2>&1; then
+ TEST_PYTHON="$PYTHON"
+else
+ TEST_PYTHON=false
+fi
+AC_MSG_RESULT([$TEST_PYTHON])
+AC_SUBST(TEST_PYTHON)
+AM_CONDITIONAL([WANT_TWISTED_TESTS], test false != "$TEST_PYTHON")
+
+dnl enable avahi tests
+AC_ARG_ENABLE(avahi-tests,
+ AC_HELP_STRING([ --enable-avahi-tests],
+ [Enable tests that use the system avahi to hit the network ]),
+ enable_avahi_tests=$enableval, enable_avahi_tests=yes)
+
+if test x$enable_avahi_tests = xyes; then
+ if test false = "$TEST_PYTHON"; then
+ AC_MSG_ERROR(
+ [Python with twisted support is needed for avahi twisted tests])
+ fi
+fi
+AM_CONDITIONAL(WANT_TWISTED_AVAHI_TESTS, test "x$enable_avahi_tests" = "xyes")
+
+dnl olpc specific code switch
+AC_ARG_ENABLE(olpc,
+ AC_HELP_STRING([--enable-olpc],[compile with olpc specific code]),
+ enable_olpc=$enableval, enable_olpc=no )
+
+if test x$enable_olpc = xyes; then
+ AC_DEFINE(ENABLE_OLPC, [], [Enable olpc code])
+fi
+
+AM_CONDITIONAL(ENABLE_OLPC, test "x$enable_olpc" = "xyes")
+
+AC_SUBST(ENABLE_OLPC)
+
+
+AC_HEADER_STDC([])
+AC_C_INLINE
+
+AC_CHECK_HEADERS_ONCE([
+ arpa/inet.h
+ arpa/nameser.h
+ fcntl.h
+ ifaddrs.h
+ netdb.h
+ netinet/in.h
+ sys/ioctl.h
+ sys/un.h
+ unistd.h
+ ])
+
+# on Darwin, these headers are interdependent, according to autoconf.info
+AC_CHECK_HEADERS([sys/socket.h], [], [],
+[
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+])
+
+# Autoconf has a handy macro for this, since it tends to have dependencies
+AC_HEADER_RESOLV
+
+dnl GTK docs
+GTK_DOC_CHECK
+
+dnl Check for Glib
+PKG_CHECK_MODULES(GLIB,
+ [glib-2.0 >= 2.24, gobject-2.0 >= 2.16, gthread-2.0 >= 2.4, gio-2.0])
+
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+
+GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0`
+AC_SUBST(GLIB_GENMARSHAL)
+
+dnl Check for D-Bus
+PKG_CHECK_MODULES(DBUS, [dbus-1 >= 1.1.0, dbus-glib-1 >= 0.61])
+
+AC_SUBST(DBUS_CFLAGS)
+AC_SUBST(DBUS_LIBS)
+
+dnl Check for libxml2
+PKG_CHECK_MODULES(LIBXML2, [libxml-2.0])
+
+AC_SUBST(LIBXML2_CFLAGS)
+AC_SUBST(LIBXML2_LIBS)
+
+dnl Check for telepathy-glib
+PKG_CHECK_MODULES(TELEPATHY_GLIB, [telepathy-glib >= 0.17.1])
+
+AC_SUBST(TELEPATHY_GLIB_CFLAGS)
+AC_SUBST(TELEPATHY_GLIB_LIBS)
+
+dnl Check for Avahi
+PKG_CHECK_MODULES(AVAHI, [avahi-gobject, avahi-client])
+AC_SUBST(AVAHI_CFLAGS)
+AC_SUBST(AVAHI_LIBS)
+
+dnl Check for libsoup
+PKG_CHECK_MODULES(LIBSOUP, [libsoup-2.4])
+AC_SUBST(LIBSOUP_CFLAGS)
+AC_SUBST(LIBSOUP_LIBS)
+
+dnl check for libuuid
+PKG_CHECK_MODULES([UUID], [uuid], [HAVE_UUID=yes], [HAVE_UUID=no])
+AC_SUBST([UUID_CFLAGS])
+AC_SUBST([UUID_LIBS])
+if test x"$HAVE_UUID" = xyes; then
+ AC_DEFINE([HAVE_UUID], [1], [Define if libuuid is available])
+else
+ AC_MSG_WARN([libuuid not found, falling back to generating random IDs])
+fi
+
+AC_ARG_ENABLE(submodules,
+ AS_HELP_STRING([--disable-submodules],
+ [Use system version of Wocky rather than a submodule]),
+ build_submodules=$enableval,
+ build_submodules=yes )
+
+dnl plugins
+AC_ARG_ENABLE(plugins,
+ AC_HELP_STRING([--disable-plugins],
+ [disable plugin loader]),
+ [enable_plugins=$enableval], [enable_plugins=yes])
+
+if test x$enable_plugins = xyes; then
+ AC_DEFINE(ENABLE_PLUGINS, [], [Enable plugins])
+ PKG_CHECK_MODULES(GMODULE, [gmodule-2.0])
+
+ AC_ARG_ENABLE(plugin-api,
+ AC_HELP_STRING([--enable-plugin-api],
+ [install headers for third-party plugins (experimental)]),
+ [
+ enable_plugin_api=$enableval
+ wocky_install_headers_dir="${includedir}/telepathy-salut-0"
+ ],
+ [enable_plugin_api=no])
+fi
+
+AC_SUBST(GMODULE_CFLAGS)
+AC_SUBST(GMODULE_LIBS)
+
+AM_CONDITIONAL(ENABLE_PLUGINS, test x$enable_plugins = xyes)
+AC_SUBST(ENABLE_PLUGINS)
+
+AM_CONDITIONAL(ENABLE_PLUGIN_API, test x$enable_plugin_api = xyes)
+
+# We have to run Wocky's configure *before* looking for it with
+# PKG_CHECK_MODULES so wocky-uninstalled.pc has been generated
+
+# We tell Wocky to install its headers alongside gabble's so that an actual
+# separate Wocky installation won't clash with them. This is a bit of a hack.
+# AX_CONFIG_DIR doesn't make it very easy to pass extra arguments to the
+# submodule's configure.
+
+prev_ac_configure_args=$ac_configure_args
+ac_configure_args="$ac_configure_args --with-installed-headers=${wocky_install_headers_dir}"
+
+if test "x$ENABLE_CODING_STYLE_CHECKS" = xyes ; then
+ ac_configure_args+=" --enable-coding-style-checks"
+else
+ ac_configure_args+=" --disable-coding-style-checks"
+fi
+
+if test "x$tp_werror" = xyes && test "x$official_release" = xno; then
+ ac_configure_args+=" --enable-Werror"
+else
+ ac_configure_args+=" --disable-Werror"
+fi
+
+prev_top_build_prefix=$ac_top_build_prefix
+AX_CONFIG_DIR([lib/ext/wocky])
+ac_top_build_prefix=$prev_top_build_prefix
+
+ac_configure_args=$prev_ac_configure_args
+
+PKG_CONFIG_PATH="${PKG_CONFIG_PATH:+"$PKG_CONFIG_PATH:"}${ac_abs_top_builddir}/lib/ext/wocky/wocky"
+export PKG_CONFIG_PATH
+PKG_CHECK_MODULES([WOCKY], [wocky >= 0.0.0])
+AC_SUBST([WOCKY_CFLAGS])
+AC_SUBST([WOCKY_LIBS])
+
+AC_SUBST(PACKAGE_STRING)
+
+AC_OUTPUT( Makefile \
+ docs/Makefile \
+ lib/Makefile \
+ lib/ext/Makefile \
+ lib/gibber/Makefile \
+ lib/gibber/tests/Makefile \
+ src/Makefile \
+ m4/Makefile \
+ data/Makefile \
+ extensions/Makefile \
+ tools/Makefile \
+ tests/Makefile \
+ tests/twisted/Makefile \
+ tests/twisted/tools/Makefile \
+ plugins/Makefile \
+ salut/Makefile \
+ salut/telepathy-salut-uninstalled.pc \
+ salut/telepathy-salut.pc
+)
diff --git a/salut/data/Makefile.am b/salut/data/Makefile.am
new file mode 100644
index 000000000..6bc592c36
--- /dev/null
+++ b/salut/data/Makefile.am
@@ -0,0 +1,22 @@
+EXTRA_DIST = salut.service.in
+
+managerdir = $(datadir)/telepathy/managers
+manager_DATA = salut.manager
+
+servicedir = $(datadir)/dbus-1/services
+service_DATA = org.freedesktop.Telepathy.ConnectionManager.salut.service
+
+# We don't use the full filename for the .in because > 99 character filenames
+# in tarballs are non-portable (and automake 1.8 doesn't let us build
+# non-archaic tarballs)
+org.freedesktop.Telepathy.ConnectionManager.salut.service: salut.service.in \
+ Makefile
+ $(AM_V_GEN)sed -e "s|[@]libexecdir[@]|$(libexecdir)|" $< > $@
+
+CLEANFILES = $(service_DATA) $(manager_DATA)
+
+$(manager_DATA): always-build
+ $(MAKE) -C ../src write-mgr-file
+ $(AM_V_GEN)../src/write-mgr-file > $@
+
+.PHONY: always-build
diff --git a/salut/data/salut.service.in b/salut/data/salut.service.in
new file mode 100644
index 000000000..322aea761
--- /dev/null
+++ b/salut/data/salut.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.ConnectionManager.salut
+Exec=@libexecdir@/telepathy-salut
diff --git a/salut/docs/Makefile.am b/salut/docs/Makefile.am
new file mode 100644
index 000000000..87f549a94
--- /dev/null
+++ b/salut/docs/Makefile.am
@@ -0,0 +1,22 @@
+tools_dir = $(top_srcdir)/tools
+
+man_MANS = telepathy-salut.8
+html_DATA = clique.html
+
+EXTRA_DIST = \
+ $(man_MANS:.8=.8.in) \
+ $(html_DATA:.html=.xml)
+
+CLEANFILES = \
+ $(man_MANS) \
+ $(html_DATA)
+
+%.8: %.8.in Makefile
+ $(AM_V_GEN)sed -e 's,[@]libexecdir[@],@libexecdir@,' < $< > $@
+
+$(html_DATA): %.html: %.xml $(tools_dir)/xep.xsl
+ $(AM_V_GEN)$(XSLTPROC) $(tools_dir)/xep.xsl $< > $@
+
+proto-xep-upload: $(html_DATA)
+ rsync -P $(html_DATA) people.collabora.co.uk:public_html/
+.PHONY: proto-xep-upload
diff --git a/salut/docs/clique.xml b/salut/docs/clique.xml
new file mode 100644
index 000000000..1de3b78eb
--- /dev/null
+++ b/salut/docs/clique.xml
@@ -0,0 +1,168 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<?xml-stylesheet type='text/xsl' href='xep.xsl'?>
+<xep>
+<header>
+ <title>Clique - link-local chat rooms</title>
+ <abstract>A protocol for serverless link-local multi-user chat over
+ reliable multicast.</abstract>
+ <legal>Copyright (c) 2007 Collabora Limited. This document may be
+ distributed under the same terms as the Telepathy specification.</legal>
+ <number>proto-clique</number>
+ <status>ProtoXEP</status>
+ <type>Extension</type>
+ <sig>Standards</sig>
+ <approver>Telepathy project</approver>
+ <dependencies>
+ <spec>XMPP Core</spec>
+ <spec>XEP-0174</spec>
+ </dependencies>
+ <supersedes/>
+ <supersededby/>
+ <shortname>NOT YET ASSIGNED</shortname>
+ <author>
+ <firstname>Simon</firstname>
+ <surname>McVittie</surname>
+ <email>simon.mcvittie@collabora.co.uk</email>
+ <jid>simon.mcvittie@collabora.co.uk</jid>
+ </author>
+ <revision>
+ <version>0.0.1</version>
+ <date>2007-11-02</date>
+ <initials>smcv</initials>
+ <remark><p>First draft.</p></remark>
+ </revision>
+</header>
+<section1 topic='Introduction' anchor='intro'>
+ <p>Clique is an extension of XEP-0174 to support multi-user chat.
+ It consists of the following components:</p>
+
+ <ul>
+ <li>a binary multicast message-passing protocol sometimes referred to
+ as rMulticast, with retransmission/reliability,
+ fragmentation/reassembly, causal ordering, and reliable membership
+ semantics, in which messages are associated with a stream ID in the
+ range 0 to 65535</li>
+ <li>XMPP-like XML messages sent with stream ID 0</li>
+ <li>optional auxiliary data (e.g. Tubes) sent with stream IDs
+ 1-65535, with semantics signalled by XML messages in stream 0</li>
+ </ul>
+
+ <p>The chat room behaves a lot like XMPP, and namespaces used in XMPP
+ have their usual semantics. However, instead of a stream of XML
+ as in XMPP, Clique's XMPP-like messages are individually well-formed
+ XML documents.</p>
+
+ <p>XEP-0174 instance names are used for identification in a chat
+ room.</p>
+
+ <p>The Clique XML namespace can also be used for certain elements sent
+ over XEP-0174 unicast TCP streams, for instance to send invitations.</p>
+</section1>
+<section1 topic='Requirements' anchor='reqs'>
+ <p>STRONGLY RECOMMENDED.</p>
+</section1>
+<section1 topic='Glossary' anchor='glossary'>
+ <p>OPTIONAL.</p>
+</section1>
+<section1 topic='Use Cases' anchor='usecases'>
+ <p>FIXME: there need to be some examples of the actual Clique protocol
+ here</p>
+
+ <section2 topic='Advertising a public link-local chatroom via mDNS'>
+ <p>In this example a chatroom called "Witchcraft" uses the multicast
+ group 239.255.71.66, port 13251 (arbitrarily chosen).</p>
+ <p>We recommend that chatrooms use a random high port number
+ in a random multicast group in the range 239.255.71.x.</p>
+ <code>
+ <![CDATA[
+ ; A dummy A record for the chatroom's multicast group
+ Witchcraft._clique._udp.local. IN A 239.255.71.66
+
+ ; A DNS-SD service of type _clique._udp
+ _clique._udp.local. IN PTR Witchcraft._clique._udp.local.
+ Witchcraft._clique._udp.local. IN SRV 13251 ._clique._udp.local.
+
+ ; For future expansion - implementations SHOULD NOT resolve this,
+ ; unless they implement a future version of this protocol that
+ ; defines some TXT keys for _clique._udp
+ Witchcraft._clique._udp.local. IN TXT "txtvers=0"
+ ]]>
+ </code>
+ </section2>
+
+ <section2 topic='Inviting a contact to a public or private room'>
+ <p>In this example crone@desktop invites hecate@broom to the
+ chatroom given above.</p>
+ <p>This would work just as well if the chatroom was not advertised
+ in mDNS; this can be used to make "private" chatrooms. Note however
+ that anyone with a network traffic sniffer can see the chatroom
+ itself.</p>
+ <code>
+ <![CDATA[
+ <!-- Sent via XEP-0174 unicast TCP from crone@desktop to
+ hecate@broom -->
+ <message from='crone@desktop' to='hecate@broom'>
+ <invite xmlns='http://telepathy.freedesktop.org/xmpp/clique'>
+ <roomname>Witchcraft</roomname>
+ <address>239.255.71.66</address>
+ <port>13251</port>
+ <!-- reason is optional -->
+ <reason>We need to plot Macbeth's doom, but the XMPP server is
+ down</reason>
+ </invite>
+ <!-- Displayed by iChat and other non-Clique-compatible clients -->
+ <body>You got a Clique chatroom invitation</body>
+ </message>
+ ]]>
+ </code>
+ </section2>
+</section1>
+<section1 topic='Business Rules' anchor='rules'>
+ <p>OPTIONAL.</p>
+</section1>
+<section1 topic='Implementation Notes' anchor='impl'>
+ <p>OPTIONAL.</p>
+</section1>
+<section1 topic='Internationalization Considerations' anchor='i18n'>
+ <p>OPTIONAL.</p>
+</section1>
+<section1 topic='Security Considerations' anchor='security'>
+ <p>REQUIRED.</p>
+</section1>
+<section1 topic='IANA Considerations' anchor='iana'>
+ <p>REQUIRED.</p>
+</section1>
+<section1 topic='XMPP Registrar Considerations' anchor='registrar'>
+ <p>None.</p>
+</section1>
+<section1 topic='XML Schema' anchor='schema'>
+ <code>
+ <![CDATA[
+ <xs:schema
+ xmlns:xs='http://www.w3.org/2001/XMLSchema'
+ targetNamespace='http://telepathy.freedesktop.org/xmpp/clique'
+ xmlns='http://telepathy.freedesktop.org/xmpp/clique'
+ elementFormDefault='qualified'>
+
+ <!-- LLMUC invitation. Sent over the IM socket -->
+ <xs:element name='invite'>
+ <xs:complexType>
+ <xs:all>
+ <xs:element name='roomname' type='xs:string'/>
+ <xs:element name='reason' minOccurs='0' type='xs:string'/>
+
+ <xs:element name='address' type='xs:string'/>
+ <xs:element name='port' type='xs:unsignedShort'/>
+
+ <!-- In OLPC builds, <properties> with NS_OLPC_ACTIVITY_PROPS
+ goes here -->
+ <xs:any minOccurs='0' maxOccurs='unbounded' namespace='##other'>
+ </xs:all>
+ </xs:complexType>
+ </xs:element>
+
+ </xs:schema>
+ ]]>
+ </code>
+</section1>
+</xep>
diff --git a/salut/docs/telepathy-salut.8.in b/salut/docs/telepathy-salut.8.in
new file mode 100644
index 000000000..403d8bca5
--- /dev/null
+++ b/salut/docs/telepathy-salut.8.in
@@ -0,0 +1,39 @@
+.TH TELEPATHY-SALUT "8" "October 2007" "Telepathy" "D-Bus services"
+\" This man page was written by Simon McVittie for the Debian project,
+\" but may be used by others.
+\" Copyright © 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+\" It may be distributed under the same terms as telepathy-salut itself.
+.SH NAME
+telepathy-salut \- Telepathy connection manager for link-local XMPP
+.SH SYNOPSIS
+\fB@libexecdir@/telepathy\-salut\fR
+.SH DESCRIPTION
+Salut implements the Telepathy D-Bus specification for link-local XMPP
+(XEP-0174, often called "Bonjour"), allowing Telepathy clients like
+.BR empathy (1)
+to communicate with other implementations of link-local XMPP, such as
+iChat. It also extends the protocol to support multicast-based chat rooms
+and collaboration.
+.PP
+It is a D-Bus service which runs on the session bus, and should usually be
+started automatically by D-Bus activation. However, it might be useful to
+start it manually for debugging.
+.SH OPTIONS
+There are no command-line options.
+.SH ENVIRONMENT
+.TP
+\fBSALUT_LOGFILE\fR=\fIfilename\fR
+If set, debug output will go to the given file rather than to stderr.
+.TP
+\fBSALUT_DEBUG\fR=\fItype\fR
+May be set to "all" for full debug output, or various undocumented options
+(which may change from release to release) to filter the output.
+\fBGIBBER_DEBUG\fR=\fItype\fR
+May be set to "all" for full debug output from the Gibber XMPP library used by
+Salut, or various undocumented options (which may change from release to
+release) to filter the output.
+.SH SEE ALSO
+.IR http://telepathy.freedesktop.org/ ,
+.IR http://telepathy.freedesktop.org/wiki/CategorySalut ,
+.IR http://www.xmpp.org/extensions/xep-0174.html ,
+.BR empathy (1)
diff --git a/salut/docs/tubes.txt b/salut/docs/tubes.txt
new file mode 100644
index 000000000..5c741587a
--- /dev/null
+++ b/salut/docs/tubes.txt
@@ -0,0 +1,59 @@
+Implementation of tubes in telepathy-salut
+==========================================
+
+- Stream tubes and D-Bus tubes in a chatroom
+
+Implemented. The XMPP spec is available on:
+http://telepathy.freedesktop.org/xmpp/tubes.html
+This XMPP spec is generated from the telepathy-gabble source code.
+
+- 1-1 D-Bus tubes
+
+Not implemented
+
+- 1-1 stream tubes
+
+Implemented. The XMPP spec is considered as experimental. It may change before
+being considered final.
+
+Initiator to receptor: offer the tube:
+ <iq type="set"
+ from="alban_test01@alban-hp"
+ to="alban_test02@alban-hp"
+ id="8849419577">
+ <tube type="stream"
+ service="TicTacTube"
+ id="364091438"
+ xmlns="http://telepathy.freedesktop.org/xmpp/tubes">
+ <transport port="47246"/>
+ </tube>
+ <parameters/>
+ </iq>
+
+Receptor to initiator: acknowledgment of the offer
+ <iq type="result"
+ from="alban_test02@alban-hp"
+ to="alban_test01@alban-hp"
+ id="8849419577"/>
+
+This acknowledgment is used to accept the tube.
+
+
+-- Tube is used --
+The receptor uses the port given by the iq stanza and the IP address used by the XMPP connection.
+
+Receptor to initiator: close the tube
+ <iq type="set"
+ from="alban_test02@alban-hp"
+ to="alban_test01@alban-hp"
+ id="67843">
+ <close id="364091438"
+ xmlns="http://telepathy.freedesktop.org/xmpp/tubes">
+ </iq>
+
+Initiator to receptor: acknowledges the close request
+ <iq type="result"
+ from="alban_test01@alban-hp"
+ to="alban_test02@alban-hp"
+ id="67843"/>
+
diff --git a/salut/extensions/Connection_Future.xml b/salut/extensions/Connection_Future.xml
new file mode 100644
index 000000000..110479832
--- /dev/null
+++ b/salut/extensions/Connection_Future.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" ?>
+<node name="/Connection_FUTURE"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ >
+ <tp:copyright>Copyright © 2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>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.1 of the License, or (at your option) any later version.</p>
+
+<p>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.</p>
+
+<p>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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.FUTURE"
+ tp:causes-havoc='experimental'>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <method name="EnsureSidecar" tp:name-for-bindings="Ensure_Sidecar">
+ <tp:added version="0.19.UNRELEASED"/>
+
+ <arg direction="in" name="Main_Interface" type="s"
+ tp:type="DBus_Interface">
+ <tp:docstring>
+ The "primary" interface implemented by an object attached
+ to a connection. For example, a Gabble plugin implementing
+ fine-grained control of XEP-0016 privacy lists might expose an object
+ implementing <tt>com.example.PrivacyLists</tt>.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Path" type="o">
+ <tp:docstring>The object path of the sidecar, exported by the same bus
+ name as the Connection to which it is attached.</tp:docstring>
+ </arg>
+ <arg direction="out" name="Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring>Immutable properties of the sidecar.</tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request an object with a particular interface providing additional
+ connection-specific functionality, together with its immutable
+ properties. These will often be implemented by plug-ins to the
+ connection managers; for example, support for an XMPP XEP for which
+ no generic Telepathy interface exists might be implemented by a
+ Gabble plugin exposing a sidecar with a particular interface.</p>
+
+ <p>This method may be called at any point during the lifetime of a
+ connection, even before its <tp:type>Connection_Status</tp:type>
+ changes to Connected. It MAY take a long time to
+ return—perhaps it needs to wait for a connection to be established
+ and for all the services supported by the server to be discovered
+ before determining whether necessary server-side support is
+ available—so callers SHOULD override the default method timeout (25
+ seconds) with a much higher value (perhaps even MAX_INT32, meaning
+ “no timeout” in recent versions of libdbus).</p>
+
+ <tp:rationale>
+ <p>There is an implicit assumption that any connection
+ manager plugin will only want to export one “primary” object per
+ feature it implements, since there is a one-to-one mapping between
+ interface and object. This is reasonable since Sidecars are
+ (intended to be) analogous to extra interfaces on the connection,
+ providing once-per-connection shared functionality; it also makes
+ client code straightforward (look up the interface you care about
+ in a dictionary, build a proxy object from the value). More
+ “plural” plugins are likely to want to implement new types of
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref>
+ instead.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The requested sidecar is not implemented by this connection manager,
+ or a necessary server-side component does not exist. (FIXME: split
+ these two errors out? Then again, once we list the guaranteed and
+ possible sidecars on a Protocol object, clients can tell the
+ difference themselves, because they shouldn't be calling this in the
+ first case.)
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="org.freedesktop.Telepathy.Error.ServiceBusy">
+ <tp:docstring>
+ A server-side component needed by the requested sidecar reported it
+ is currently too busy, or did not respond for some
+ implementation-defined time. The caller may wish to try again later.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="org.freedesktop.Telepathy.Error.Cancelled">
+ <tp:docstring>
+ The connection was disconnected while the sidecar was being set up.
+ </tp:docstring>
+ </tp:error>
+ </method>
+
+ </interface>
+</node>
diff --git a/salut/extensions/Makefile.am b/salut/extensions/Makefile.am
new file mode 100644
index 000000000..8e2e7679b
--- /dev/null
+++ b/salut/extensions/Makefile.am
@@ -0,0 +1,123 @@
+tools_dir = $(top_srcdir)/tools
+
+EXTRA_DIST = \
+ OLPC_Buddy_Info.xml \
+ OLPC_Activity_Properties.xml \
+ connection.xml \
+ Salut_Plugin_Test.xml \
+ Connection_Future.xml \
+ all.xml
+
+noinst_LTLIBRARIES = libsalut-extensions.la
+
+libsalut_extensions_la_SOURCES = \
+ extensions.c \
+ extensions.h
+
+nodist_libsalut_extensions_la_SOURCES = \
+ _gen/signals-marshal.c \
+ _gen/signals-marshal.h \
+ _gen/signals-marshal.list \
+ _gen/enums.h \
+ _gen/gtypes.h \
+ _gen/gtypes-body.h \
+ _gen/interfaces.h \
+ _gen/interfaces-body.h \
+ _gen/svc.h \
+ _gen/svc.c
+
+BUILT_SOURCES = \
+ _gen/all.xml \
+ _gen/connection.xml \
+ $(nodist_libsalut_extensions_la_SOURCES) \
+ extensions.html
+
+CLEANFILES = $(BUILT_SOURCES) _gen/.exists
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(GCOV_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(TELEPATHY_GLIB_CFLAGS)
+
+AM_LDFLAGS = $(GCOV_LIBS) @DBUS_LIBS@ @GLIB_LIBS@ @TELEPATHY_GLIB_LIBS@
+
+# Generated files which can be generated for all categories simultaneously
+
+XSLTPROCFLAGS = --nonet --novalid
+
+_gen/%.xml: %.xml $(wildcard *.xml) $(tools_dir)/identity.xsl
+ $(mkdir_p) _gen
+ $(AM_V_GEN)$(XSLTPROC) $(XSLTPROCFLAGS) --xinclude $(tools_dir)/identity.xsl \
+ $< > $@
+
+extensions.html: _gen/all.xml $(tools_dir)/doc-generator.xsl
+ $(AM_V_GEN)$(XSLTPROC) $(XSLTPROCFLAGS) --xinclude \
+ $(tools_dir)/doc-generator.xsl \
+ $< > $@
+
+_gen/signals-marshal.list: _gen/all.xml \
+ $(tools_dir)/glib-signals-marshal-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-signals-marshal-gen.py $< > $@
+
+_gen/signals-marshal.h: _gen/signals-marshal.list Makefile.am
+ $(AM_V_GEN)$(GLIB_GENMARSHAL) --header --prefix=_salut_ext_marshal $< > $@
+
+_gen/signals-marshal.c: _gen/signals-marshal.list Makefile.am
+ $(AM_V_GEN){ echo '#include "_gen/signals-marshal.h"' && \
+ $(GLIB_GENMARSHAL) --body --prefix=_salut_ext_marshal $< ; } > $@
+
+_gen/register-dbus-glib-marshallers-body.h: _gen/all.xml \
+ $(tools_dir)/glib-client-marshaller-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-marshaller-gen.py $< \
+ _salut_ext > $@
+
+_gen/enums.h: _gen/all.xml $(tools_dir)/c-constants-generator.xsl
+ $(AM_V_GEN)$(XSLTPROC) $(XSLTPROCFLAGS) \
+ --stringparam mixed-case-prefix Salut \
+ $(tools_dir)/c-constants-generator.xsl \
+ $< > $@
+
+_gen/gtypes.h _gen/gtypes-body.h: _gen/all.xml \
+ $(tools_dir)/glib-gtypes-generator.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-gtypes-generator.py \
+ $< _gen/gtypes Salut
+
+_gen/interfaces.h: _gen/all.xml \
+ $(tools_dir)/glib-interfaces-generator.xsl \
+ $(tools_dir)/c-interfaces-generator.xsl
+ $(AM_V_GEN)$(XSLTPROC) $(XSLTPROCFLAGS) \
+ --stringparam mixed-case-prefix Salut \
+ $(tools_dir)/glib-interfaces-generator.xsl \
+ $< > $@
+
+_gen/interfaces-body.h: _gen/all.xml \
+ $(tools_dir)/glib-interfaces-body-generator.xsl \
+ $(tools_dir)/c-interfaces-generator.xsl
+ $(AM_V_GEN)$(XSLTPROC) $(XSLTPROCFLAGS) \
+ --stringparam mixed-case-prefix Salut \
+ $(tools_dir)/glib-interfaces-body-generator.xsl \
+ $< > $@
+
+_gen/svc.c _gen/svc.h: _gen/all.xml $(tools_dir)/glib-ginterface-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-ginterface-gen.py \
+ --filename=_gen/svc \
+ --signal-marshal-prefix=_salut_ext \
+ --include='<telepathy-glib/dbus.h>' \
+ --include='"_gen/signals-marshal.h"' \
+ --not-implemented-func='tp_dbus_g_method_return_not_implemented' \
+ --allow-unstable \
+ $< Salut_Svc_
+
+Android.mk: Makefile.am $(BUILT_SOURCES)
+ androgenizer -:PROJECT telepathy-salut -:SHARED salut-extensions \
+ -:TAGS eng debug \
+ -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+ -:SOURCES $(nodist_libsalut_extensions_la_SOURCES) \
+ $(libsalut_extensions_la_SOURCES) \
+ -:CFLAGS $(DEFS) $(CFLAGS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CFLAGS) \
+ -:CPPFLAGS $(CPPFLAGS) $(AM_CPPFLAGS) \
+ -:LDFLAGS $(AM_LDFLAGS) \
+ > $@
diff --git a/salut/extensions/OLPC_Activity_Properties.xml b/salut/extensions/OLPC_Activity_Properties.xml
new file mode 100644
index 000000000..1976fc630
--- /dev/null
+++ b/salut/extensions/OLPC_Activity_Properties.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" ?>
+<node name="/OLPC_Activity_Properties" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2007 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.1 of the License, or (at your option) any later version.</p>
+
+<p>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.</p>
+
+<p>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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA</p>
+ </tp:license>
+ <interface name="org.laptop.Telepathy.ActivityProperties">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <method name="SetProperties">
+ <arg direction="in" name="room" type="u">
+ <tp:docstring>
+ An integer handle representing the room of the activity
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="properties" type="a{sv}">
+ <tp:docstring>
+ A dictionary mapping properties names to the desired values
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Set the properties of the activity associated to the given room for this connection.
+ You have to be the owner of this activity.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetProperties">
+ <arg direction="in" name="room" type="u">
+ <tp:docstring>
+ An integer handle for the activity's room to request his properties for
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="properties" type="a{sv}">
+ <tp:docstring>
+ A dictionary mapping properties names to their values
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get the properties of a particular activity.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetActivity">
+ <arg direction="in" name="activity_id" type="s">
+ <tp:docstring>
+ An activity id
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="room" type="u">
+ <tp:docstring>
+ A room handle
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Returns the handle of the room associated with this activity
+ <tp:rationale>
+ <p>When an activity starts up, it knows its activity_id but doesn't
+ know yet if it's shared or not, much less the room.</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="ActivityPropertiesChanged">
+ <arg name="room" type="u">
+ <tp:docstring>
+ An integer handle representing the room of the activity
+ </tp:docstring>
+ </arg>
+ <arg name="properties" type="a{sv}">
+ <tp:docstring>
+ A dictionary mapping properties names to their new values
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal emitted when the properties of an activity are changed.
+ </tp:docstring>
+ </signal>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface on connections to associate OLPC activity properties
+ with rooms.</p>
+
+ <p>The following types and names are used to request and set properties:</p>
+ <dl>
+ <dt>s:color</dt>
+ <dd>The color of the activity. Format used is #RRGGBB,#RRGGBB (stroke,fill).</dd>
+
+ <dt>s:name</dt>
+ <dd>The name of the activity.</dd>
+
+ <dt>s:type</dt>
+ <dd>The type of the activity.</dd>
+ </dl>
+
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/salut/extensions/OLPC_Buddy_Info.xml b/salut/extensions/OLPC_Buddy_Info.xml
new file mode 100644
index 000000000..d7ed2ccd1
--- /dev/null
+++ b/salut/extensions/OLPC_Buddy_Info.xml
@@ -0,0 +1,269 @@
+<?xml version="1.0" ?>
+<node name="/OLPC_Buddy_Info" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2007 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.1 of the License, or (at your option) any later version.</p>
+
+<p>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.</p>
+
+<p>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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA</p>
+ </tp:license>
+ <interface name="org.laptop.Telepathy.BuddyInfo">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <method name="SetProperties">
+ <arg direction="in" name="properties" type="a{sv}">
+ <tp:docstring>
+ A dictionary mapping information names to the desired values.
+ This replaces any existing buddy properties completely: any keys
+ which were previously present, but are not present in this dictionary,
+ are deleted.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Set the information of the local user for this connection.</p>
+ <p>This method may be called before Connect(), in which case the given
+ properties will be advertised as soon as possible after connection
+ (possibly immediately).</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetProperties">
+ <arg direction="in" name="contact" type="u">
+ <tp:docstring>
+ An integer handle for the contact to request his properties for
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="properties" type="a{sv}">
+ <tp:docstring>
+ A dictionary mapping information names to their values
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get the properties of a particular contact.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="PropertiesChanged">
+ <arg name="contact" type="u">
+ <tp:docstring>
+ An integer handle representing the contact
+ </tp:docstring>
+ </arg>
+ <arg name="properties" type="a{sv}">
+ <tp:docstring>
+ A dictionary mapping information names to their new values
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal emitted when the properties of a contact from your 'subscribe'
+ contact list are changed.
+ </tp:docstring>
+ </signal>
+
+ <method name="SetActivities">
+ <arg direction="in" name="activities" type="a(su)">
+ <tp:docstring>
+ An array of structs containing:
+ <ul>
+ <li>the identifier of the activity</li>
+ <li>the room handle of the activity channel</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Set the activities of the local user for this connection.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="AddActivity">
+ <arg direction="in" name="id" type="s">
+ <tp:docstring>
+ An activity id
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="handle" type="u">
+ <tp:docstring>
+ A room handle
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Advertise an activity associated to a muc room
+ <tp:rationale>
+ <p>Once an activity shares itself, needs to be advertised if it's not
+ private. SetActivities could be used for this but it would mean that
+ the activity would need to call GetActivities then add itself.</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetActivities">
+ <arg direction="in" name="contact" type="u">
+ <tp:docstring>
+ An integer handle for the contact whose activities are to be returned
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="activities" type="a(su)">
+ <tp:docstring>
+ An array of structs containing:
+ <ul>
+ <li>the identifier of the activity</li>
+ <li>the room handle of the activity channel</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get the activities of a particular contact.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="ActivitiesChanged">
+ <arg name="contact" type="u">
+ <tp:docstring>
+ An integer handle representing the contact
+ </tp:docstring>
+ </arg>
+ <arg name="activities" type="a(su)">
+ <tp:docstring>
+ An array of structs containing:
+ <ul>
+ <li>the identifier of the activity</li>
+ <li>the handle of the activity channel</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal emitted when the activities of a contact from your 'subscribe'
+ contact list are changed.
+ </tp:docstring>
+ </signal>
+
+ <method name="SetCurrentActivity">
+ <arg direction="in" name="activity" type="s">
+ <tp:docstring>
+ The identifier of the activity, or the empty string if there is no
+ current activity
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="channel" type="u">
+ <tp:docstring>
+ The room handle of the activity channel, or 0 if there is no current
+ activity
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Set the current activity of the local user for this connection.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetCurrentActivity">
+ <arg direction="in" name="contact" type="u">
+ <tp:docstring>
+ An integer handle for the contact whose current activity is to be
+ returned
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="activity" type="s">
+ <tp:docstring>
+ The identifier of the activity, or "" if there is no current activity
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="channel" type="u">
+ <tp:docstring>
+ The room handle of the activity, or 0 if there is no current activity
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get the current activity of a particular contact.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="CurrentActivityChanged">
+ <arg name="contact" type="u">
+ <tp:docstring>
+ An integer handle representing the contact
+ </tp:docstring>
+ </arg>
+ <arg name="activity" type="s">
+ <tp:docstring>
+ The identifier of the activity, or "" if there is no current activity
+ </tp:docstring>
+ </arg>
+ <arg name="channel" type="u">
+ <tp:docstring>
+ The room handle of the activity channel, or 0 if there is no current
+ activity
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal emitted when the current activity of a contact from your 'subscribe'
+ contact list is changed.
+ </tp:docstring>
+ </signal>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface on connections to associate OLPC buddy information
+ with contacts, providing methods for the user to set their own
+ information and retrieve information of contacts.
+ The user is automatically notified when information of contacts that
+ are in his 'subscribe' contact list change.</p>
+
+ <p>The following types and names are used to request and set information
+ (except for activities):</p>
+ <dl>
+ <dt>s:color</dt>
+ <dd>The color name of the buddy. Format used is #RRGGBB,#RRGGBB
+ (stroke,fill).
+ </dd>
+
+ <dt>ay:key</dt>
+ <dd>The public key of the buddy.</dd>
+
+ <dt>s:jid</dt>
+ <dd>For link-local connections, the JID of the buddy's server account.</dd>
+ </dl>
+
+ <p>Activities are represented by a struct containing:</p>
+ <ul>
+ <li>the identifier of the activity</li>
+ <li>the handle of the activity channel</li>
+ </ul>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/salut/extensions/Salut_Plugin_Test.xml b/salut/extensions/Salut_Plugin_Test.xml
new file mode 100644
index 000000000..265c2a841
--- /dev/null
+++ b/salut/extensions/Salut_Plugin_Test.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" ?>
+<node name="/Salut_Plugin_Test"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.1 of the License, or (at your option) any later version.</p>
+
+<p>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.</p>
+
+<p>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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Salut.Plugin.Test">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A sidecar interface implemented by a plugin used by the test
+ suite.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/salut/extensions/all.xml b/salut/extensions/all.xml
new file mode 100644
index 000000000..ee496336e
--- /dev/null
+++ b/salut/extensions/all.xml
@@ -0,0 +1,66 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Salut-specific extensions to the Telepathy interfaces</tp:title>
+
+<tp:copyright>Copyright (C) 2007 Collabora Limited</tp:copyright>
+
+<tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>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.1 of the License, or (at your option) any later version.</p>
+
+<p>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.</p>
+
+<p>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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA</p>
+</tp:license>
+
+<xi:include href="connection.xml"/>
+<xi:include href="Salut_Plugin_Test.xml"/>
+<xi:include href="Connection_Future.xml"/>
+
+<tp:generic-types>
+ <tp:external-type name="Contact_Handle" type="u"
+ from="Telepathy specification"/>
+ <tp:external-type name="Socket_Address_Type" type="u"
+ from="Telepathy specification"/>
+ <tp:external-type name="DBus_Interface" type="s"
+ from="Telepathy specification"/>
+ <tp:external-type name="DBus_Error_Name" type="s"
+ from="Telepathy specification"/>
+ <tp:external-type name="Socket_Access_Control" type="u"
+ from="Telepathy specification"/>
+ <tp:external-type name="DBus_Qualified_Member" type="s"
+ from="Telepathy specification"/>
+ <tp:external-type name="Qualified_Property_Value_Map" type="a{sv}"
+ from="Telepathy specification"/>
+ <tp:external-type name="String_Variant_Map" type="a{sv}"
+ from="Telepathy specification"/>
+ <tp:external-type name="String_String_Map" type="a{ss}"
+ from="Telepathy specification"/>
+ <tp:external-type name="Unix_Timestamp64" type="t"
+ from="Telepathy specification"/>
+ <tp:external-type name="Requestable_Channel_Class" type="a(a{sv}as)"
+ from="Telepathy specification"/>
+ <tp:external-type name="Channel_Class" type="a{sv}"
+ from="Telepathy specification"/>
+
+ <!-- use types from Channel_Type_Tubes -->
+ <tp:external-type name="DBus_Tube_Member" type="(us)"
+ from="Telepathy specification"/>
+ <tp:external-type name="Supported_Socket_Map" type="a{uau}"
+ from="Telepathy specification"/>
+ <tp:external-type name="Unix_Timestamp64" type="t"
+ from="Telepathy specification"/>
+ <tp:simple-type name="DBus_Unique_Name" type="s"
+ from="Telepathy specification"/>
+</tp:generic-types>
+
+</tp:spec>
diff --git a/salut/extensions/connection.xml b/salut/extensions/connection.xml
new file mode 100644
index 000000000..8e72821ce
--- /dev/null
+++ b/salut/extensions/connection.xml
@@ -0,0 +1,28 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Salut-specific extensions to Connection interfaces</tp:title>
+
+<tp:copyright>Copyright (C) 2007 Collabora Limited</tp:copyright>
+
+<tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>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.1 of the License, or (at your option) any later version.</p>
+
+<p>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.</p>
+
+<p>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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA</p>
+</tp:license>
+
+<xi:include href="OLPC_Buddy_Info.xml"/>
+<xi:include href="OLPC_Activity_Properties.xml"/>
+
+</tp:spec>
diff --git a/salut/extensions/extensions.c b/salut/extensions/extensions.c
new file mode 100644
index 000000000..eeda4623c
--- /dev/null
+++ b/salut/extensions/extensions.c
@@ -0,0 +1,6 @@
+#include "extensions.h"
+
+/* include auto-generated stubs for things common to service and client */
+#include "_gen/gtypes-body.h"
+#include "_gen/interfaces-body.h"
+#include "_gen/signals-marshal.h"
diff --git a/salut/extensions/extensions.h b/salut/extensions/extensions.h
new file mode 100644
index 000000000..da87accd7
--- /dev/null
+++ b/salut/extensions/extensions.h
@@ -0,0 +1,10 @@
+#ifndef _SALUT_EXTENSIONS_H
+#define _SALUT_EXTENSIONS_H
+
+#include "extensions/_gen/svc.h"
+#include "extensions/_gen/enums.h"
+#include "extensions/_gen/gtypes.h"
+#include "extensions/_gen/interfaces.h"
+
+#endif /* _SALUT_EXTENSIONS_H */
+
diff --git a/salut/lib/Makefile.am b/salut/lib/Makefile.am
new file mode 100644
index 000000000..9755d06b8
--- /dev/null
+++ b/salut/lib/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS=ext gibber
diff --git a/salut/lib/ext/Makefile.am b/salut/lib/ext/Makefile.am
new file mode 100644
index 000000000..48e4e2d5e
--- /dev/null
+++ b/salut/lib/ext/Makefile.am
@@ -0,0 +1,16 @@
+SUBDIRS =
+DIST_SUBDIRS = wocky
+
+all-local:
+ @cd wocky && $(MAKE)
+
+clean-local:
+ if test -e wocky/Makefile ; then \
+ cd wocky && $(MAKE) clean ; \
+ fi
+
+uninstall-local:
+ @cd wocky/wocky && $(MAKE) uninstall
+
+install-data-local:
+ @cd wocky/wocky && $(MAKE) install
diff --git a/salut/lib/ext/wocky b/salut/lib/ext/wocky
new file mode 160000
+Subproject a0e74a1cd04ab5e35922536f047664a5ce69c11
diff --git a/salut/lib/gibber/AUTHORS b/salut/lib/gibber/AUTHORS
new file mode 100644
index 000000000..93b800c18
--- /dev/null
+++ b/salut/lib/gibber/AUTHORS
@@ -0,0 +1 @@
+Sjoerd Simons <sjoerd@luon.net>
diff --git a/salut/lib/gibber/COPYING b/salut/lib/gibber/COPYING
new file mode 100644
index 000000000..e3819249c
--- /dev/null
+++ b/salut/lib/gibber/COPYING
@@ -0,0 +1,542 @@
+Most of Salut is licensed under the GNU Lesser General Public License,
+as published by the Free Software Foundation and reproduced below:
+either version 2.1 of the License, or (at your option) any later version.
+
+xep.xsl in the the tools/ directory, used to generate HTML documentation
+for the Clique protocol, is copyright 1999-2008 by the
+XMPP Standards Foundation (XSF) and is released under a MIT-style
+license, reproduced below.
+
+------------------------------------------------------------------------
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+------------------------------------------------------------------------
+
+License of tools/xep.xsl:
+
+ Copyright (c) 1999 - 2008 XMPP Standards Foundation
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
diff --git a/salut/lib/gibber/Makefile.am b/salut/lib/gibber/Makefile.am
new file mode 100644
index 000000000..d036c72d6
--- /dev/null
+++ b/salut/lib/gibber/Makefile.am
@@ -0,0 +1,123 @@
+SUBDIRS = tests
+
+noinst_LTLIBRARIES = libgibber.la
+
+BUILT_SOURCES = \
+ gibber-file-transfer-enumtypes.c \
+ gibber-file-transfer-enumtypes.h \
+ gibber-signals-marshal.list \
+ gibber-signals-marshal.h \
+ gibber-signals-marshal.c
+
+HANDWRITTEN_SOURCES = \
+ gibber-muc-connection.c \
+ gibber-muc-connection.h \
+ gibber-bytestream-iface.h \
+ gibber-bytestream-iface.c \
+ gibber-bytestream-muc.h \
+ gibber-bytestream-muc.c \
+ gibber-bytestream-oob.h \
+ gibber-bytestream-oob.c \
+ gibber-bytestream-direct.h \
+ gibber-bytestream-direct.c \
+ gibber-debug.c \
+ gibber-debug.h \
+ gibber-transport.c \
+ gibber-transport.h \
+ gibber-fd-transport.c \
+ gibber-fd-transport.h \
+ gibber-tcp-transport.c \
+ gibber-tcp-transport.h \
+ gibber-unix-transport.c \
+ gibber-unix-transport.h \
+ gibber-multicast-transport.c \
+ gibber-multicast-transport.h \
+ gibber-r-multicast-transport.c \
+ gibber-r-multicast-transport.h \
+ gibber-r-multicast-causal-transport.c \
+ gibber-r-multicast-causal-transport.h \
+ gibber-r-multicast-transport.h \
+ gibber-r-multicast-packet.c \
+ gibber-r-multicast-packet.h \
+ gibber-r-multicast-sender.c \
+ gibber-r-multicast-sender.h \
+ gibber-linklocal-transport.c \
+ gibber-linklocal-transport.h \
+ gibber-file-transfer.c \
+ gibber-file-transfer.h \
+ gibber-oob-file-transfer.c \
+ gibber-oob-file-transfer.h \
+ gibber-listener.c \
+ gibber-listener.h \
+ gibber-sockets.c \
+ gibber-sockets.h \
+ gibber-sockets-unix.h \
+ gibber-sockets-win32.h \
+ gibber-util.h \
+ gibber-util.c
+
+libgibber_la_SOURCES = $(HANDWRITTEN_SOURCES) $(BUILT_SOURCES)
+
+# Coding style checks
+check_c_sources = \
+ $(HANDWRITTEN_SOURCES)
+
+include $(top_srcdir)/tools/check-coding-style.mk
+check-local: check-coding-style
+
+CLEANFILES=$(BUILT_SOURCES)
+dist-hook:
+ $(shell for x in $(BUILT_SOURCES); do rm -f $(distdir)/$$x ; done)
+
+gibber-signals-marshal.list: $(HANDWRITTEN_SOURCES) Makefile.am
+ $(AM_V_GEN)( cd $(srcdir) && \
+ sed -n -e 's/.*_gibber_signals_marshal_\([[:upper:][:digit:]]*__[[:upper:][:digit:]_]*\).*/\1/p' \
+ $(HANDWRITTEN_SOURCES) ) \
+ | sed -e 's/__/:/' -e 'y/_/,/' | sort -u > $@.tmp
+ if cmp -s $@.tmp $@; then \
+ rm $@.tmp; \
+ else \
+ mv $@.tmp $@; \
+ fi
+
+%-signals-marshal.h: %-signals-marshal.list Makefile.am
+ $(AM_V_GEN)glib-genmarshal --header --prefix=_$(subst -,_,$*)_signals_marshal $< > $@
+
+%-signals-marshal.c: %-signals-marshal.list Makefile.am
+ $(AM_V_GEN){ echo '#include "$*-signals-marshal.h"' && \
+ glib-genmarshal --body --prefix=_$(subst -,_,$*)_signals_marshal $< ; \
+ } > $@
+
+
+AM_CFLAGS = $(ERROR_CFLAGS) $(GCOV_CFLAGS) @GLIB_CFLAGS@ @LIBXML2_CFLAGS@ @WOCKY_CFLAGS@ @LIBSOUP_CFLAGS@
+
+AM_LDFLAGS = $(GCOV_LIBS) @GLIB_LIBS@ @LIBXML2_LIBS@ @WOCKY_LIBS@ @LIBSOUP_LIBS@
+
+# rules for making the glib enum objects
+%-enumtypes.h: %.h Makefile.in
+ $(AM_V_GEN)glib-mkenums \
+ --fhead "#ifndef __$(shell echo $* | LC_ALL=C tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__\n#define __$(shell echo $* | LC_ALL=C tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+ --fprod "/* enumerations from \"@filename@\" */\n" \
+ --vhead "GType @enum_name@_get_type (void);\n#define $(shell echo $* | LC_ALL=C tr [:lower:]- [:upper:]_ | sed 's/_.*//')_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \
+ --ftail "G_END_DECLS\n\n#endif /* __$(shell echo $* | LC_ALL=C tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__ */" \
+ $< > $@
+
+%-enumtypes.c: %.h Makefile.in
+ $(AM_V_GEN)glib-mkenums \
+ --fhead "#include <$*.h>\n#include <$*-enumtypes.h>" \
+ --fprod "\n/* enumerations from \"@filename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@VALUENAME@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \
+ $< > $@
+
+Android.mk: Makefile.am $(BUILT_SOURCES)
+ androgenizer -:PROJECT telepathy-salut -:STATIC gibber-salut \
+ -:TAGS eng debug \
+ -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+ -:SOURCES $(libgibber_la_SOURCES) \
+ -:CFLAGS $(DEFS) $(CFLAGS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CFLAGS) \
+ -:CPPFLAGS $(CPPFLAGS) $(AM_CPPFLAGS) \
+ -:LDFLAGS $(AM_LDFLAGS) \
+ > $@
diff --git a/salut/lib/gibber/gibber-bytestream-direct.c b/salut/lib/gibber/gibber-bytestream-direct.c
new file mode 100644
index 000000000..2537d32c9
--- /dev/null
+++ b/salut/lib/gibber/gibber-bytestream-direct.c
@@ -0,0 +1,639 @@
+/*
+ * gibber-bytestream-direct.c - Source for GibberBytestreamDirect
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "gibber-bytestream-direct.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib.h>
+
+#include <wocky/wocky-xmpp-error.h>
+
+#include "gibber-sockets.h"
+#include "gibber-linklocal-transport.h"
+#include "gibber-util.h"
+
+#define DEBUG_FLAG DEBUG_BYTESTREAM
+#include "gibber-debug.h"
+
+static void
+bytestream_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (GibberBytestreamDirect, gibber_bytestream_direct,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIBBER_TYPE_BYTESTREAM_IFACE,
+ bytestream_iface_init));
+
+/* properties */
+enum
+{
+ PROP_ADDRESSES = 1,
+ PROP_SELF_ID,
+ PROP_PEER_ID,
+ PROP_STREAM_ID,
+ PROP_STATE,
+
+ /* relevent only on recipient side to connect to the initiator */
+ PROP_PORT,
+
+ PROP_PROTOCOL,
+ LAST_PROPERTY
+};
+
+typedef struct _GibberBytestreamDirectPrivate GibberBytestreamDirectPrivate;
+struct _GibberBytestreamDirectPrivate
+{
+ /* A list of struct sockaddr_storage to try to connect to, if
+ * this GibberBytestreamDirect object is on the initiator side.
+ * GibberBytestreamDirect own theses structures and must free them. */
+ GArray *addresses;
+
+ gchar *self_id;
+ gchar *peer_id;
+ gchar *stream_id;
+ GibberBytestreamState state;
+
+ guint portnum;
+
+ /* Are we the recipient of this bytestream?
+ * If not we are the sender */
+ gboolean recipient;
+ GibberTransport *transport;
+ gboolean write_blocked;
+ gboolean read_blocked;
+
+ GibberBytestreamDirectCheckAddrFunc check_addr_func;
+ gpointer check_addr_func_data;
+
+ gboolean dispose_has_run;
+};
+
+#define GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE(obj) \
+ ((GibberBytestreamDirectPrivate *) obj->priv)
+
+static void
+gibber_bytestream_direct_init (GibberBytestreamDirect *self)
+{
+ GibberBytestreamDirectPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ GIBBER_TYPE_BYTESTREAM_DIRECT, GibberBytestreamDirectPrivate);
+
+ self->priv = priv;
+}
+
+static void
+gibber_bytestream_direct_dispose (GObject *object)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (object);
+ GibberBytestreamDirectPrivate *priv = GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE
+ (self);
+
+ if (priv->dispose_has_run)
+ return;
+ priv->dispose_has_run = TRUE;
+
+ if (priv->state != GIBBER_BYTESTREAM_STATE_CLOSED)
+ {
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
+ }
+
+ if (priv->transport != NULL)
+ {
+ g_object_unref (priv->transport);
+ priv->transport = NULL;
+ }
+
+ G_OBJECT_CLASS (gibber_bytestream_direct_parent_class)->dispose (object);
+}
+
+static void
+gibber_bytestream_direct_finalize (GObject *object)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (object);
+ GibberBytestreamDirectPrivate *priv = GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE
+ (self);
+
+ g_free (priv->stream_id);
+ g_free (priv->self_id);
+ g_free (priv->peer_id);
+
+ if (priv->addresses != NULL)
+ g_array_unref (priv->addresses);
+
+ G_OBJECT_CLASS (gibber_bytestream_direct_parent_class)->finalize (object);
+}
+
+static void
+gibber_bytestream_direct_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (object);
+ GibberBytestreamDirectPrivate *priv = GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE
+ (self);
+
+ switch (property_id)
+ {
+ case PROP_ADDRESSES:
+ g_value_set_pointer (value, priv->addresses);
+ break;
+ case PROP_SELF_ID:
+ g_value_set_string (value, priv->self_id);
+ break;
+ case PROP_PEER_ID:
+ g_value_set_string (value, priv->peer_id);
+ break;
+ case PROP_STREAM_ID:
+ g_value_set_string (value, priv->stream_id);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case PROP_PORT:
+ g_value_set_uint (value, priv->portnum);
+ break;
+ case PROP_PROTOCOL:
+ /* this property is not used because direct bytestream are not
+ * negociated on XMPP using SI */
+ g_value_set_string (value, (const gchar *)"");
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gibber_bytestream_direct_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (object);
+ GibberBytestreamDirectPrivate *priv = GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE
+ (self);
+
+ switch (property_id)
+ {
+ case PROP_ADDRESSES:
+ priv->addresses = g_value_get_pointer (value);
+ break;
+ case PROP_SELF_ID:
+ g_free (priv->self_id);
+ priv->self_id = g_value_dup_string (value);
+ break;
+ case PROP_PEER_ID:
+ g_free (priv->peer_id);
+ priv->peer_id = g_value_dup_string (value);
+ break;
+ case PROP_STREAM_ID:
+ g_free (priv->stream_id);
+ priv->stream_id = g_value_dup_string (value);
+ break;
+ case PROP_STATE:
+ if (priv->state != g_value_get_uint (value))
+ {
+ priv->state = g_value_get_uint (value);
+ g_signal_emit_by_name (object, "state-changed", priv->state);
+ }
+ break;
+ case PROP_PORT:
+ priv->portnum = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+gibber_bytestream_direct_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ GibberBytestreamDirectPrivate *priv;
+
+ obj = G_OBJECT_CLASS (gibber_bytestream_direct_parent_class)->
+ constructor (type, n_props, props);
+
+ priv = GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (GIBBER_BYTESTREAM_DIRECT (obj));
+
+ g_assert (priv->self_id != NULL);
+ g_assert (priv->peer_id != NULL);
+
+ return obj;
+}
+
+static void
+gibber_bytestream_direct_class_init (
+ GibberBytestreamDirectClass *gibber_bytestream_direct_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_bytestream_direct_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (gibber_bytestream_direct_class,
+ sizeof (GibberBytestreamDirectPrivate));
+
+ object_class->dispose = gibber_bytestream_direct_dispose;
+ object_class->finalize = gibber_bytestream_direct_finalize;
+
+ object_class->get_property = gibber_bytestream_direct_get_property;
+ object_class->set_property = gibber_bytestream_direct_set_property;
+ object_class->constructor = gibber_bytestream_direct_constructor;
+
+ g_object_class_override_property (object_class, PROP_SELF_ID,
+ "self-id");
+ g_object_class_override_property (object_class, PROP_PEER_ID,
+ "peer-id");
+ g_object_class_override_property (object_class, PROP_STREAM_ID,
+ "stream-id");
+ g_object_class_override_property (object_class, PROP_STATE,
+ "state");
+ g_object_class_override_property (object_class, PROP_PROTOCOL,
+ "protocol");
+
+ param_spec = g_param_spec_pointer (
+ "addresses",
+ "Array of addresses",
+ "GArray of struct sockaddr_storage used to find the IP address to "
+ "connect in this bytestream if it's a private one",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ADDRESSES,
+ param_spec);
+
+ param_spec = g_param_spec_uint (
+ "port",
+ "port",
+ "port for the recipient to connect on the initiator",
+ 0,
+ G_MAXUINT32,
+ 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_PORT,
+ param_spec);
+}
+
+void
+gibber_bytestream_direct_set_check_addr_func (
+ GibberBytestreamDirect *self,
+ GibberBytestreamDirectCheckAddrFunc func,
+ gpointer user_data)
+{
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+
+ priv->check_addr_func = func;
+ priv->check_addr_func_data = user_data;
+}
+
+static void
+transport_handler (GibberTransport *transport,
+ GibberBuffer *data,
+ gpointer user_data)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (user_data);
+ GString *buffer;
+
+ DEBUG ("GibberBytestreamDirect emit DATA_RECEIVED.");
+
+ buffer = g_string_new_len ((const gchar *) data->data, data->length);
+
+ g_signal_emit_by_name (G_OBJECT (self), "data-received", NULL, buffer);
+
+ g_string_free (buffer, TRUE);
+}
+
+static void
+transport_connected_cb (GibberTransport *transport,
+ GibberBytestreamDirect *self)
+{
+ DEBUG ("transport connected. Bytestream is now open");
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_OPEN,
+ NULL);
+}
+
+static void
+transport_disconnected_cb (GibberTransport *transport,
+ GibberBytestreamDirect *self)
+{
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ return;
+
+ DEBUG ("transport disconnected. close the bytestream");
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_ACCEPTED)
+ {
+ /* Connection to host failed */
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND,
+ "connection failed" };
+
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e);
+ }
+ else
+ {
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
+ }
+}
+
+static void
+change_write_blocked_state (GibberBytestreamDirect *self,
+ gboolean blocked)
+{
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+
+ if (priv->write_blocked == blocked)
+ return;
+
+ priv->write_blocked = blocked;
+ g_signal_emit_by_name (self, "write-blocked", blocked);
+}
+
+static void
+bytestream_closed (GibberBytestreamDirect *self)
+{
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+
+ if (priv->transport != NULL)
+ {
+ g_signal_handlers_disconnect_matched (priv->transport,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+ gibber_transport_disconnect (priv->transport);
+ g_object_unref (priv->transport);
+ priv->transport = NULL;
+ }
+
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSED, NULL);
+}
+
+static void
+transport_buffer_empty_cb (GibberTransport *transport,
+ GibberBytestreamDirect *self)
+{
+ GibberBytestreamDirectPrivate *priv = GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE
+ (self);
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSING)
+ {
+ DEBUG ("buffer is now empty. Bytestream can be closed");
+ bytestream_closed (self);
+ }
+ else if (priv->write_blocked)
+ {
+ DEBUG ("buffer is empty, unblock write to the bytestream");
+ change_write_blocked_state (self, FALSE);
+ }
+}
+
+static void
+set_transport (GibberBytestreamDirect *self,
+ GibberTransport *transport)
+{
+ GibberBytestreamDirectPrivate *priv = GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE
+ (self);
+
+ g_assert (priv->transport == NULL);
+
+ priv->transport = g_object_ref (transport);
+ gibber_transport_set_handler (transport, transport_handler, self);
+
+ /* The transport will already be connected if it is created from
+ * GibberListener. In this case, set the bytestream to open, otherwise
+ * it will be done in transport_connected_cb. */
+ if (gibber_transport_get_state (transport) == GIBBER_TRANSPORT_CONNECTED)
+ {
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_OPEN,
+ NULL);
+ }
+
+ g_signal_connect (transport, "connected",
+ G_CALLBACK (transport_connected_cb), self);
+ g_signal_connect (transport, "disconnected",
+ G_CALLBACK (transport_disconnected_cb), self);
+ g_signal_connect (priv->transport, "buffer-empty",
+ G_CALLBACK (transport_buffer_empty_cb), self);
+}
+
+gboolean
+gibber_bytestream_direct_accept_socket (GibberBytestreamIface *bytestream,
+ GibberTransport *transport)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (bytestream);
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+
+ if (priv->state != GIBBER_BYTESTREAM_STATE_LOCAL_PENDING)
+ {
+ DEBUG ("bytestream is not is the initiating state (state %d)",
+ priv->state);
+ return FALSE;
+ }
+
+ set_transport (self, transport);
+
+ return TRUE;
+}
+
+static void
+gibber_bytestream_direct_block_reading (GibberBytestreamIface *bytestream,
+ gboolean block)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (bytestream);
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+
+ if (priv->read_blocked == block)
+ return;
+
+ priv->read_blocked = block;
+
+ DEBUG ("%s the transport bytestream", block ? "block": "unblock");
+ gibber_transport_block_receiving (priv->transport, block);
+}
+
+/*
+ * gibber_bytestream_direct_send
+ *
+ * Implements gibber_bytestream_iface_send on GibberBytestreamIface
+ */
+static gboolean
+gibber_bytestream_direct_send (GibberBytestreamIface *bytestream,
+ guint len,
+ const gchar *str)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (bytestream);
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (priv->state != GIBBER_BYTESTREAM_STATE_OPEN)
+ {
+ DEBUG ("can't send data through a not open bytestream (state: %d)",
+ priv->state);
+ return FALSE;
+ }
+
+ if (priv->write_blocked)
+ {
+ DEBUG ("sending data while the bytestream was blocked");
+ }
+
+ DEBUG ("send %u bytes through bytestream", len);
+ if (!gibber_transport_send (priv->transport, (const guint8 *) str, len,
+ &error))
+ {
+ DEBUG ("sending failed: %s", error->message);
+ g_error_free (error);
+
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
+ return FALSE;
+ }
+
+ if (!gibber_transport_buffer_is_empty (priv->transport))
+ {
+ /* We >don't want to send more data while the buffer isn't empty */
+ DEBUG ("buffer isn't empty. Block write to the bytestream");
+ change_write_blocked_state (self, TRUE);
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * gibber_bytestream_direct_accept
+ *
+ * Implements gibber_bytestream_iface_accept on GibberBytestreamIface
+ */
+static void
+gibber_bytestream_direct_accept (GibberBytestreamIface *bytestream,
+ GibberBytestreamAugmentSiAcceptReply func,
+ gpointer user_data)
+{
+ /* nothing to do: GibberBytestreamDirect don't use Stream Initiation, so it
+ * does not have to send any SI iq stanza to accept the bytestream */
+}
+
+/*
+ * gibber_bytestream_direct_close
+ *
+ * Implements gibber_bytestream_iface_close on GibberBytestreamIface
+ */
+static void
+gibber_bytestream_direct_close (GibberBytestreamIface *bytestream,
+ GError *error)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (bytestream);
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ /* bytestream already closed, do nothing */
+ return;
+
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSING, NULL);
+ if (priv->transport != NULL &&
+ !gibber_transport_buffer_is_empty (priv->transport))
+ {
+ DEBUG ("Wait transport buffer is empty before close the bytestream");
+ }
+ else
+ {
+ DEBUG ("Transport buffer is empty, we can close the bytestream");
+ bytestream_closed (self);
+ }
+}
+
+/*
+ * gibber_bytestream_direct_initiate
+ * connect to the remote end
+ *
+ * Implements gibber_bytestream_iface_initiate on GibberBytestreamIface
+ */
+static gboolean
+gibber_bytestream_direct_initiate (GibberBytestreamIface *bytestream)
+{
+ GibberBytestreamDirect *self = GIBBER_BYTESTREAM_DIRECT (bytestream);
+ GibberLLTransport *ll_transport;
+ /* never cast addr but type-punning to avoid strict-aliasing issues
+ * (see -fstrict-aliasing in man gcc) */
+ union {
+ struct sockaddr_storage storage;
+ struct sockaddr_in6 in6;
+ } addr;
+ GibberBytestreamDirectPrivate *priv =
+ GIBBER_BYTESTREAM_DIRECT_GET_PRIVATE (self);
+ guint i;
+ gboolean ret;
+
+ g_assert (priv->addresses != NULL);
+ if (priv->addresses->len < 1)
+ {
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND,
+ "Unsable get socket address for this contact" };
+ DEBUG ("Could not get socket address for this contact" );
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e);
+ return FALSE;
+ }
+
+ ll_transport = gibber_ll_transport_new ();
+ set_transport (self, GIBBER_TRANSPORT (ll_transport));
+ g_object_unref (ll_transport);
+
+ ret = FALSE;
+ for (i = 0 ; i < priv->addresses->len && !ret ; i++)
+ {
+ addr.storage = g_array_index (priv->addresses, struct sockaddr_storage,
+ i);
+ addr.in6.sin6_port = g_htons ((guint16) priv->portnum);
+
+ ret = gibber_ll_transport_open_sockaddr (ll_transport, &addr.storage,
+ NULL);
+ }
+
+ return ret;
+}
+
+static void
+bytestream_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ GibberBytestreamIfaceClass *klass = (GibberBytestreamIfaceClass *) g_iface;
+
+ klass->initiate = gibber_bytestream_direct_initiate;
+ klass->send = gibber_bytestream_direct_send;
+ klass->close = gibber_bytestream_direct_close;
+ klass->accept = gibber_bytestream_direct_accept;
+ klass->block_reading = gibber_bytestream_direct_block_reading;
+}
diff --git a/salut/lib/gibber/gibber-bytestream-direct.h b/salut/lib/gibber/gibber-bytestream-direct.h
new file mode 100644
index 000000000..843e8f333
--- /dev/null
+++ b/salut/lib/gibber/gibber-bytestream-direct.h
@@ -0,0 +1,75 @@
+/*
+ * gibber-bytestream-direct.h - Header for GibberBytestreamDirect
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_BYTESTREAM_DIRECT_H__
+#define __GIBBER_BYTESTREAM_DIRECT_H__
+
+#include <glib-object.h>
+#include "gibber-bytestream-iface.h"
+#include "gibber-sockets.h"
+#include "gibber-transport.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberBytestreamDirect GibberBytestreamDirect;
+typedef struct _GibberBytestreamDirectClass GibberBytestreamDirectClass;
+
+typedef gboolean (* GibberBytestreamDirectCheckAddrFunc) (
+ GibberBytestreamDirect *bytestream, struct sockaddr *addr,
+ socklen_t addrlen, gpointer user_data);
+
+struct _GibberBytestreamDirectClass {
+ GObjectClass parent_class;
+};
+
+struct _GibberBytestreamDirect {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType gibber_bytestream_direct_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_BYTESTREAM_DIRECT \
+ (gibber_bytestream_direct_get_type ())
+#define GIBBER_BYTESTREAM_DIRECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_BYTESTREAM_DIRECT,\
+ GibberBytestreamDirect))
+#define GIBBER_BYTESTREAM_DIRECT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_BYTESTREAM_DIRECT,\
+ GibberBytestreamDirectClass))
+#define GIBBER_IS_BYTESTREAM_DIRECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_BYTESTREAM_DIRECT))
+#define GIBBER_IS_BYTESTREAM_DIRECT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_BYTESTREAM_DIRECT))
+#define GIBBER_BYTESTREAM_DIRECT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_BYTESTREAM_DIRECT,\
+ GibberBytestreamDirectClass))
+
+void gibber_bytestream_direct_set_check_addr_func (
+ GibberBytestreamDirect *bytestream,
+ GibberBytestreamDirectCheckAddrFunc func, gpointer user_data);
+
+gboolean gibber_bytestream_direct_accept_socket (
+ GibberBytestreamIface *bytestream, GibberTransport *transport);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_BYTESTREAM_DIRECT_H__ */
diff --git a/salut/lib/gibber/gibber-bytestream-iface.c b/salut/lib/gibber/gibber-bytestream-iface.c
new file mode 100644
index 000000000..b8ce0ed24
--- /dev/null
+++ b/salut/lib/gibber/gibber-bytestream-iface.c
@@ -0,0 +1,184 @@
+/*
+ * bytestream-iface.c - Source for GibberBytestream interface
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "gibber-bytestream-iface.h"
+#include "gibber-signals-marshal.h"
+
+#include <glib.h>
+
+gboolean
+gibber_bytestream_iface_initiate (GibberBytestreamIface *self)
+{
+ gboolean (*virtual_method)(GibberBytestreamIface *) =
+ GIBBER_BYTESTREAM_IFACE_GET_CLASS (self)->initiate;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self);
+}
+
+gboolean
+gibber_bytestream_iface_send (GibberBytestreamIface *self,
+ guint len,
+ const gchar *data)
+{
+ gboolean (*virtual_method)(GibberBytestreamIface *, guint, const gchar *) =
+ GIBBER_BYTESTREAM_IFACE_GET_CLASS (self)->send;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, len, data);
+}
+
+void
+gibber_bytestream_iface_close (GibberBytestreamIface *self,
+ GError *error)
+{
+ void (*virtual_method)(GibberBytestreamIface *, GError *error) =
+ GIBBER_BYTESTREAM_IFACE_GET_CLASS (self)->close;
+ g_assert (virtual_method != NULL);
+ virtual_method (self, error);
+}
+
+void
+gibber_bytestream_iface_accept (GibberBytestreamIface *self,
+ GibberBytestreamAugmentSiAcceptReply func,
+ gpointer user_data)
+{
+ void (*virtual_method)(GibberBytestreamIface *,
+ GibberBytestreamAugmentSiAcceptReply, gpointer) =
+ GIBBER_BYTESTREAM_IFACE_GET_CLASS (self)->accept;
+ g_assert (virtual_method != NULL);
+ virtual_method (self, func, user_data);
+}
+
+void
+gibber_bytestream_iface_block_reading (GibberBytestreamIface *self,
+ gboolean block)
+{
+ void (*virtual_method)(GibberBytestreamIface *, gboolean) =
+ GIBBER_BYTESTREAM_IFACE_GET_CLASS (self)->block_reading;
+ if (virtual_method != NULL)
+ virtual_method (self, block);
+ /* else: do nothing. Some bytestreams like IBB does not have read_block. */
+}
+
+static void
+gibber_bytestream_iface_base_init (gpointer klass)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ GParamSpec *param_spec;
+
+ param_spec = g_param_spec_string (
+ "self-id",
+ "self ID",
+ "the ID of the local user",
+ "",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_string (
+ "peer-id",
+ "peer ID",
+ "the ID of the muc or the remote user associated with this "
+ "bytestream",
+ "",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_string (
+ "stream-id",
+ "stream ID",
+ "the ID of the stream",
+ "",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "state",
+ "Bytestream state",
+ "An enum (BytestreamIBBState) signifying the current state of"
+ "this bytestream object",
+ 0, NUM_GIBBER_BYTESTREAM_STATES - 1,
+ GIBBER_BYTESTREAM_STATE_LOCAL_PENDING,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_string (
+ "protocol",
+ "protocol",
+ "the name of the protocol implemented by this bytestream",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ g_signal_new ("data-received",
+ G_TYPE_FROM_INTERFACE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _gibber_signals_marshal_VOID__STRING_POINTER,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER);
+
+ g_signal_new ("state-changed",
+ G_TYPE_FROM_INTERFACE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ g_signal_new ("write-blocked",
+ G_TYPE_FROM_INTERFACE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+
+ initialized = TRUE;
+ }
+}
+
+GType
+gibber_bytestream_iface_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof (GibberBytestreamIfaceClass),
+ gibber_bytestream_iface_base_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE, "GibberBytestreamIface",
+ &info, 0);
+ }
+
+ return type;
+}
diff --git a/salut/lib/gibber/gibber-bytestream-iface.h b/salut/lib/gibber/gibber-bytestream-iface.h
new file mode 100644
index 000000000..5f17ba70d
--- /dev/null
+++ b/salut/lib/gibber/gibber-bytestream-iface.h
@@ -0,0 +1,94 @@
+/*
+ * bytestream-iface.h - Header for GibberBytestream interface
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_BYTESTREAM_IFACE_H__
+#define __GIBBER_BYTESTREAM_IFACE_H__
+
+#include <glib-object.h>
+
+#include <wocky/wocky-stanza.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ /* Received a SI request, response not yet sent */
+ GIBBER_BYTESTREAM_STATE_LOCAL_PENDING = 0,
+ /* We accepted SI request.
+ * bytestream specific init steps not yet performed */
+ GIBBER_BYTESTREAM_STATE_ACCEPTED,
+ /* Remote contact accepted the SI request.
+ * bytestream specific initiation started */
+ GIBBER_BYTESTREAM_STATE_INITIATING,
+ /* Bytestream open */
+ GIBBER_BYTESTREAM_STATE_OPEN,
+ GIBBER_BYTESTREAM_STATE_CLOSING,
+ GIBBER_BYTESTREAM_STATE_CLOSED,
+ NUM_GIBBER_BYTESTREAM_STATES,
+} GibberBytestreamState;
+
+typedef void (* GibberBytestreamAugmentSiAcceptReply) (
+ WockyNode *si, gpointer user_data);
+
+typedef struct _GibberBytestreamIface GibberBytestreamIface;
+typedef struct _GibberBytestreamIfaceClass GibberBytestreamIfaceClass;
+
+struct _GibberBytestreamIfaceClass {
+ GTypeInterface parent;
+
+ gboolean (*initiate) (GibberBytestreamIface *bytestream);
+ gboolean (*send) (GibberBytestreamIface *bytestream, guint len,
+ const gchar *data);
+ void (*close) (GibberBytestreamIface *bytestream, GError *error);
+ void (*accept) (GibberBytestreamIface *bytestream,
+ GibberBytestreamAugmentSiAcceptReply func, gpointer user_data);
+ void (*block_reading) (GibberBytestreamIface *bytestream, gboolean block);
+};
+
+GType gibber_bytestream_iface_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_BYTESTREAM_IFACE \
+ (gibber_bytestream_iface_get_type ())
+#define GIBBER_BYTESTREAM_IFACE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_BYTESTREAM_IFACE, \
+ GibberBytestreamIface))
+#define GIBBER_IS_BYTESTREAM_IFACE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_BYTESTREAM_IFACE))
+#define GIBBER_BYTESTREAM_IFACE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIBBER_TYPE_BYTESTREAM_IFACE,\
+ GibberBytestreamIfaceClass))
+
+gboolean gibber_bytestream_iface_initiate (GibberBytestreamIface *bytestream);
+
+gboolean gibber_bytestream_iface_send (GibberBytestreamIface *bytestream,
+ guint len, const gchar *data);
+
+void gibber_bytestream_iface_close (GibberBytestreamIface *bytestream,
+ GError *error);
+
+void gibber_bytestream_iface_accept (GibberBytestreamIface *bytestream,
+ GibberBytestreamAugmentSiAcceptReply func, gpointer user_data);
+
+void gibber_bytestream_iface_block_reading (GibberBytestreamIface *bytestream,
+ gboolean block);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_BYTESTREAM_IFACE_H__ */
diff --git a/salut/lib/gibber/gibber-bytestream-muc.c b/salut/lib/gibber/gibber-bytestream-muc.c
new file mode 100644
index 000000000..a9b79ce7f
--- /dev/null
+++ b/salut/lib/gibber/gibber-bytestream-muc.c
@@ -0,0 +1,426 @@
+/*
+ * gibber-bytestream-muc.c - Source for GibberBytestreamMuc
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "gibber-bytestream-muc.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib.h>
+
+#include "gibber-bytestream-iface.h"
+#include "gibber-muc-connection.h"
+#include "gibber-linklocal-transport.h"
+
+#include <wocky/wocky-namespaces.h>
+
+#define DEBUG_FLAG DEBUG_BYTESTREAM
+#include "gibber-debug.h"
+
+#include "gibber-signals-marshal.h"
+
+static void bytestream_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (GibberBytestreamMuc, gibber_bytestream_muc,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIBBER_TYPE_BYTESTREAM_IFACE,
+ bytestream_iface_init));
+
+/* properties */
+enum
+{
+ PROP_MUC_CONNECTION = 1,
+ PROP_SELF_ID,
+ PROP_PEER_ID,
+ PROP_STREAM_ID,
+ PROP_STATE,
+ PROP_PROTOCOL,
+ LAST_PROPERTY
+};
+
+typedef struct _GibberBytestreamMucPrivate GibberBytestreamMucPrivate;
+struct _GibberBytestreamMucPrivate
+{
+ GibberMucConnection *muc_connection;
+ gchar *self_id;
+ gchar *peer_id;
+ gchar *stream_id;
+ GibberBytestreamState state;
+ guint16 stream_id_multicast;
+ /* gchar *sender -> guint16 stream-id */
+ GHashTable *senders;
+
+ gboolean dispose_has_run;
+};
+
+#define GIBBER_BYTESTREAM_MUC_GET_PRIVATE(obj) \
+ ((GibberBytestreamMucPrivate *) obj->priv)
+
+static void
+gibber_bytestream_muc_init (GibberBytestreamMuc *self)
+{
+ GibberBytestreamMucPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ GIBBER_TYPE_BYTESTREAM_MUC, GibberBytestreamMucPrivate);
+
+ self->priv = priv;
+
+ priv->senders = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+muc_connection_received_data_cb (GibberMucConnection *muc_connection,
+ const gchar *sender,
+ guint stream_id,
+ const guint16 *data,
+ gsize length,
+ GibberBytestreamMuc *self)
+{
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+ GString *str;
+ guint sender_stream_id;
+
+ sender_stream_id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->senders,
+ sender));
+
+ if (sender_stream_id == 0 || (guint16) sender_stream_id != stream_id)
+ return;
+
+ str = g_string_new_len ((const gchar *) data, length);
+ g_signal_emit_by_name (G_OBJECT (self), "data-received", sender,
+ str);
+
+ g_string_free (str, TRUE);
+}
+
+static void
+gibber_bytestream_muc_dispose (GObject *object)
+{
+ GibberBytestreamMuc *self = GIBBER_BYTESTREAM_MUC (object);
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->state != GIBBER_BYTESTREAM_STATE_CLOSED)
+ {
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
+ }
+
+ g_hash_table_unref (priv->senders);
+
+ G_OBJECT_CLASS (gibber_bytestream_muc_parent_class)->dispose (object);
+}
+
+static void
+gibber_bytestream_muc_finalize (GObject *object)
+{
+ GibberBytestreamMuc *self = GIBBER_BYTESTREAM_MUC (object);
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+
+ g_free (priv->stream_id);
+ g_free (priv->self_id);
+ g_free (priv->peer_id);
+
+ G_OBJECT_CLASS (gibber_bytestream_muc_parent_class)->finalize (object);
+}
+
+static gboolean
+check_stream_id (GibberBytestreamMuc *self)
+{
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+
+ if (priv->stream_id_multicast != 0)
+ return TRUE;
+
+ /* No stream allocated yet. Request one now */
+ priv->stream_id_multicast = gibber_muc_connection_new_stream (
+ priv->muc_connection);
+ if (priv->stream_id_multicast == 0)
+ {
+ DEBUG ("Can't allocate a new stream. Bytestream closed");
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
+ return FALSE;
+ }
+
+ g_assert (priv->stream_id == NULL);
+ priv->stream_id = g_strdup_printf ("%u", priv->stream_id_multicast);
+
+ return TRUE;
+}
+
+static void
+gibber_bytestream_muc_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GibberBytestreamMuc *self = GIBBER_BYTESTREAM_MUC (object);
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_MUC_CONNECTION:
+ g_value_set_object (value, priv->muc_connection);
+ break;
+ case PROP_SELF_ID:
+ g_value_set_string (value, priv->self_id);
+ break;
+ case PROP_PEER_ID:
+ g_value_set_string (value, priv->peer_id);
+ break;
+ case PROP_STREAM_ID:
+ check_stream_id (self);
+ g_value_set_string (value, priv->stream_id);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case PROP_PROTOCOL:
+ /* We use the Clique namespace to signify that we're using streams
+ * 1 to 65535 of the reliable multicast layer */
+ g_value_set_string (value, WOCKY_TELEPATHY_NS_CLIQUE);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gibber_bytestream_muc_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GibberBytestreamMuc *self = GIBBER_BYTESTREAM_MUC (object);
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_MUC_CONNECTION:
+ priv->muc_connection = g_value_get_object (value);
+ if (priv->muc_connection != NULL)
+ {
+ /* FIXME: at some point it could be useful to modify Gibber's
+ * API to only be notified for data from a given stream */
+ g_signal_connect (priv->muc_connection, "received-data",
+ G_CALLBACK (muc_connection_received_data_cb), self);
+ }
+ break;
+ case PROP_SELF_ID:
+ g_free (priv->self_id);
+ priv->self_id = g_value_dup_string (value);
+ break;
+ case PROP_PEER_ID:
+ g_free (priv->peer_id);
+ priv->peer_id = g_value_dup_string (value);
+ break;
+ case PROP_STREAM_ID:
+ /* Stream ID isn't supposed to be defined by the bytestream user as we
+ * use gibber_muc_connection_new_stream to generate one */
+ break;
+ case PROP_STATE:
+ if (priv->state != g_value_get_uint (value))
+ {
+ priv->state = g_value_get_uint (value);
+ g_signal_emit_by_name (object, "state-changed", priv->state);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+gibber_bytestream_muc_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ GibberBytestreamMucPrivate *priv;
+
+ obj = G_OBJECT_CLASS (gibber_bytestream_muc_parent_class)->
+ constructor (type, n_props, props);
+
+ priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (GIBBER_BYTESTREAM_MUC (obj));
+
+ g_assert (priv->muc_connection != NULL);
+ g_assert (priv->self_id != NULL);
+ g_assert (priv->peer_id != NULL);
+
+ return obj;
+}
+
+static void
+gibber_bytestream_muc_class_init (
+ GibberBytestreamMucClass *gibber_bytestream_muc_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_bytestream_muc_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (gibber_bytestream_muc_class,
+ sizeof (GibberBytestreamMucPrivate));
+
+ object_class->dispose = gibber_bytestream_muc_dispose;
+ object_class->finalize = gibber_bytestream_muc_finalize;
+
+ object_class->get_property = gibber_bytestream_muc_get_property;
+ object_class->set_property = gibber_bytestream_muc_set_property;
+ object_class->constructor = gibber_bytestream_muc_constructor;
+
+ g_object_class_override_property (object_class, PROP_SELF_ID,
+ "self-id");
+ g_object_class_override_property (object_class, PROP_PEER_ID,
+ "peer-id");
+ g_object_class_override_property (object_class, PROP_STREAM_ID,
+ "stream-id");
+ g_object_class_override_property (object_class, PROP_STATE,
+ "state");
+ g_object_class_override_property (object_class, PROP_PROTOCOL,
+ "protocol");
+
+ param_spec = g_param_spec_object (
+ "muc-connection",
+ "GibberMucConnection object",
+ "Gibber MUC connection object used for communication by this "
+ "bytestream if it's a muc one",
+ GIBBER_TYPE_MUC_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MUC_CONNECTION,
+ param_spec);
+}
+
+/*
+ * gibber_bytestream_muc_send
+ *
+ * Implements gibber_bytestream_iface_send on GibberBytestreamIface
+ */
+static gboolean
+gibber_bytestream_muc_send (GibberBytestreamIface *bytestream,
+ guint len,
+ const gchar *str)
+{
+ GibberBytestreamMuc *self = GIBBER_BYTESTREAM_MUC (bytestream);
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (!check_stream_id (self))
+ {
+ return FALSE;
+ }
+
+ if (!gibber_muc_connection_send_raw (priv->muc_connection,
+ priv->stream_id_multicast, (const guint8 *) str, len, &error))
+ {
+ DEBUG ("send failed: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * gibber_bytestream_muc_accept
+ *
+ * Implements gibber_bytestream_iface_accept on GibberBytestreamIface
+ */
+static void
+gibber_bytestream_muc_accept (GibberBytestreamIface *bytestream,
+ GibberBytestreamAugmentSiAcceptReply func,
+ gpointer user_data)
+{
+ /* Don't have to accept a muc bytestream */
+}
+
+/*
+ * gibber_bytestream_muc_close
+ *
+ * Implements gibber_bytestream_iface_close on GibberBytestreamIface
+ */
+static void
+gibber_bytestream_muc_close (GibberBytestreamIface *bytestream,
+ GError *error)
+{
+ GibberBytestreamMuc *self = GIBBER_BYTESTREAM_MUC (bytestream);
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ /* bytestream already closed, do nothing */
+ return;
+
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSING, NULL);
+ if (priv->stream_id_multicast != 0)
+ {
+ gibber_muc_connection_free_stream (priv->muc_connection,
+ priv->stream_id_multicast);
+ }
+
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSED, NULL);
+}
+
+/*
+ * gibber_bytestream_muc_initiate
+ *
+ * Implements gibber_bytestream_iface_initiate on GibberBytestreamIface
+ */
+static gboolean
+gibber_bytestream_muc_initiate (GibberBytestreamIface *bytestream)
+{
+ /* Nothing to do */
+ return TRUE;
+}
+
+void
+gibber_bytestream_muc_add_sender (GibberBytestreamMuc *self,
+ const gchar *sender,
+ guint16 stream_id)
+{
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+
+ g_hash_table_insert (priv->senders, g_strdup (sender),
+ GUINT_TO_POINTER ((guint) stream_id));
+}
+
+void gibber_bytestream_muc_remove_sender (GibberBytestreamMuc *self,
+ const gchar *sender)
+{
+ GibberBytestreamMucPrivate *priv = GIBBER_BYTESTREAM_MUC_GET_PRIVATE (self);
+
+ g_hash_table_remove (priv->senders, sender);
+}
+
+static void
+bytestream_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ GibberBytestreamIfaceClass *klass = (GibberBytestreamIfaceClass *) g_iface;
+
+ klass->initiate = gibber_bytestream_muc_initiate;
+ klass->send = gibber_bytestream_muc_send;
+ klass->close = gibber_bytestream_muc_close;
+ klass->accept = gibber_bytestream_muc_accept;
+}
diff --git a/salut/lib/gibber/gibber-bytestream-muc.h b/salut/lib/gibber/gibber-bytestream-muc.h
new file mode 100644
index 000000000..7d71c5dff
--- /dev/null
+++ b/salut/lib/gibber/gibber-bytestream-muc.h
@@ -0,0 +1,69 @@
+/*
+ * gibber-bytestream-muc.h - Header for GibberBytestreamMuc
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_BYTESTREAM_MUC_H__
+#define __GIBBER_BYTESTREAM_MUC_H__
+
+#include <glib-object.h>
+
+#include "gibber-bytestream-iface.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberBytestreamMuc GibberBytestreamMuc;
+typedef struct _GibberBytestreamMucClass GibberBytestreamMucClass;
+
+struct _GibberBytestreamMucClass {
+ GObjectClass parent_class;
+};
+
+struct _GibberBytestreamMuc {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType gibber_bytestream_muc_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_BYTESTREAM_MUC \
+ (gibber_bytestream_muc_get_type ())
+#define GIBBER_BYTESTREAM_MUC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_BYTESTREAM_MUC,\
+ GibberBytestreamMuc))
+#define GIBBER_BYTESTREAM_MUC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_BYTESTREAM_MUC,\
+ GibberBytestreamMucClass))
+#define GIBBER_IS_BYTESTREAM_MUC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_BYTESTREAM_MUC))
+#define GIBBER_IS_BYTESTREAM_MUC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_BYTESTREAM_MUC))
+#define GIBBER_BYTESTREAM_MUC_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_BYTESTREAM_MUC,\
+ GibberBytestreamMucClass))
+
+void gibber_bytestream_muc_add_sender (GibberBytestreamMuc *bytestream,
+ const gchar *sender, guint16 stream_id);
+
+void gibber_bytestream_muc_remove_sender (GibberBytestreamMuc *bytestream,
+ const gchar *sender);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_BYTESTREAM_MUC_H__ */
diff --git a/salut/lib/gibber/gibber-bytestream-oob.c b/salut/lib/gibber/gibber-bytestream-oob.c
new file mode 100644
index 000000000..7952fc1ee
--- /dev/null
+++ b/salut/lib/gibber/gibber-bytestream-oob.c
@@ -0,0 +1,1105 @@
+/*
+ * gibber-bytestream-oob.c - Source for GibberBytestreamOOB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "gibber-bytestream-oob.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-porter.h>
+#include <wocky/wocky-meta-porter.h>
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-xmpp-error.h>
+
+#include "gibber-sockets.h"
+#include "gibber-bytestream-iface.h"
+#include "gibber-linklocal-transport.h"
+#include "gibber-util.h"
+#include "gibber-transport.h"
+#include "gibber-fd-transport.h"
+#include "gibber-listener.h"
+
+#define DEBUG_FLAG DEBUG_BYTESTREAM
+#include "gibber-debug.h"
+
+#include "gibber-signals-marshal.h"
+
+static void
+bytestream_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (GibberBytestreamOOB, gibber_bytestream_oob,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIBBER_TYPE_BYTESTREAM_IFACE,
+ bytestream_iface_init));
+
+/* properties */
+enum
+{
+ PROP_PORTER = 1,
+ PROP_CONTACT,
+ PROP_SELF_ID,
+ PROP_PEER_ID,
+ PROP_STREAM_ID,
+ PROP_STREAM_INIT_IQ,
+ PROP_STATE,
+ PROP_HOST,
+ PROP_PROTOCOL,
+ LAST_PROPERTY
+};
+
+typedef struct _GibberBytestreamIBBPrivate GibberBytestreamOOBPrivate;
+struct _GibberBytestreamIBBPrivate
+{
+ WockyPorter *porter;
+ WockyContact *contact;
+ gchar *self_id;
+ gchar *peer_id;
+ gchar *stream_id;
+ WockyStanza *stream_init_iq;
+ /* ID of the OOB opening stanza. We'll reply to
+ * it when we the bytestream is closed */
+ gchar *stream_open_id;
+ guint stanza_received_id;
+ GibberBytestreamState state;
+ gchar *host;
+ gchar *url;
+
+ /* Are we the recipient of this bytestream?
+ * If not we are the sender */
+ gboolean recipient;
+ GibberTransport *transport;
+ gboolean write_blocked;
+ gboolean read_blocked;
+ GibberListener *listener;
+
+ GibberBytestreamOOBCheckAddrFunc check_addr_func;
+ gpointer check_addr_func_data;
+
+ gboolean dispose_has_run;
+};
+
+#define GIBBER_BYTESTREAM_OOB_GET_PRIVATE(obj) \
+ ((GibberBytestreamOOBPrivate *) (GibberBytestreamOOB *) obj->priv)
+
+static void gibber_bytestream_oob_do_close (GibberBytestreamOOB *self,
+ GError *error, gboolean can_wait);
+static void bytestream_closed (GibberBytestreamOOB *self);
+
+static void
+gibber_bytestream_oob_init (GibberBytestreamOOB *self)
+{
+ GibberBytestreamOOBPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ GIBBER_TYPE_BYTESTREAM_OOB, GibberBytestreamOOBPrivate);
+
+ self->priv = priv;
+ priv->dispose_has_run = FALSE;
+}
+
+static WockyStanza *
+make_iq_oob_sucess_response (const gchar *from,
+ const gchar *to,
+ const gchar *id)
+{
+ return wocky_stanza_build (
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_RESULT,
+ from, to,
+ WOCKY_NODE_ATTRIBUTE, "id", id,
+ NULL);
+}
+
+static void
+transport_handler (GibberTransport *transport,
+ GibberBuffer *data,
+ gpointer user_data)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (user_data);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ GString *buffer;
+
+ buffer = g_string_new_len ((const gchar *) data->data, data->length);
+
+ g_signal_emit_by_name (G_OBJECT (self), "data-received", priv->peer_id,
+ buffer);
+
+ g_string_free (buffer, TRUE);
+}
+
+static void
+transport_connected_cb (GibberTransport *transport,
+ GibberBytestreamOOB *self)
+{
+ DEBUG ("transport connected. Bytestream is now open");
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_OPEN,
+ NULL);
+}
+
+static void
+transport_disconnected_cb (GibberTransport *transport,
+ GibberBytestreamOOB *self)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ return;
+
+ DEBUG ("transport disconnected. close the bytestream");
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_ACCEPTED)
+ {
+ /* Connection to host failed */
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND,
+ "connection failed" };
+
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e);
+ }
+ else
+ {
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
+ }
+}
+
+static void
+change_write_blocked_state (GibberBytestreamOOB *self,
+ gboolean blocked)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ if (priv->write_blocked == blocked)
+ return;
+
+ priv->write_blocked = blocked;
+ g_signal_emit_by_name (self, "write-blocked", blocked);
+}
+
+static void
+transport_buffer_empty_cb (GibberTransport *transport,
+ GibberBytestreamOOB *self)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSING)
+ {
+ DEBUG ("buffer is now empty. Bytestream can be closed");
+ bytestream_closed (self);
+ }
+ else if (priv->write_blocked)
+ {
+ DEBUG ("buffer is empty, unblock write to the bytestream");
+ change_write_blocked_state (self, FALSE);
+ }
+}
+
+static void
+set_transport (GibberBytestreamOOB *self,
+ GibberTransport *transport)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ g_assert (priv->transport == NULL);
+
+ priv->transport = g_object_ref (transport);
+ gibber_transport_set_handler (transport, transport_handler, self);
+
+ /* The transport will already be connected if it is created from
+ * GibberListener. In this case, set the bytestream to open, otherwise
+ * it will be done in transport_connected_cb. */
+ if (gibber_transport_get_state (transport) == GIBBER_TRANSPORT_CONNECTED)
+ {
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_OPEN,
+ NULL);
+ }
+
+ g_signal_connect (transport, "connected",
+ G_CALLBACK (transport_connected_cb), self);
+ g_signal_connect (transport, "disconnected",
+ G_CALLBACK (transport_disconnected_cb), self);
+ g_signal_connect (transport, "buffer-empty",
+ G_CALLBACK (transport_buffer_empty_cb), self);
+}
+
+static void
+connect_to_url (GibberBytestreamOOB *self)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ GibberLLTransport *ll_transport;
+ GSocketConnection *conn;
+ GSocketAddress *socket_address = NULL;
+ gchar **tokens;
+ union {
+ struct sockaddr_storage storage;
+ struct sockaddr_in in;
+ } addr;
+ const gchar *port;
+ gint portnum = 0;
+ const gchar *url;
+ GError *error = NULL;
+
+ /* TODO: if we want to support IPv6 literals, we have to remove
+ * [] around the address */
+
+ url = priv->url + strlen ("x-tcp://");
+ tokens = g_strsplit (url, ":", 2);
+ port = tokens[1];
+
+ if (port != NULL)
+ portnum = atoi (port);
+
+ if (portnum <= 0 || portnum > G_MAXUINT16)
+ {
+ /* I'm too lazy to create more specific errors for this as it should
+ * never happen while using salut anyway.. */
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_NOT_ACCEPTABLE,
+ "Invalid port number" };
+ DEBUG ("Invalid port number: %s", port);
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e);
+ goto out;
+ }
+
+ conn = wocky_meta_porter_borrow_connection (WOCKY_META_PORTER (priv->porter),
+ WOCKY_LL_CONTACT (priv->contact));
+
+ if (conn != NULL)
+ socket_address = g_socket_connection_get_remote_address (conn, NULL);
+
+ if (conn == NULL || socket_address == NULL)
+ {
+ /* I'm too lazy to create more specific errors for this as it should
+ * never happen while using salut anyway.. */
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND,
+ "Unsable get socket address for the control connection" };
+ DEBUG ("Could not get socket address for the control connection" );
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e);
+ goto out;
+ }
+
+ if (!g_socket_address_to_native (G_SOCKET_ADDRESS (socket_address), &(addr.storage),
+ sizeof (addr.storage), &error))
+ {
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND,
+ "Failed to turn socket address into bytes" };
+ DEBUG ("Failed to get native socket address: %s", error->message);
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e);
+ g_clear_error (&error);
+ goto out;
+ }
+
+ /* FIXME: this is a hack until we get the normalization of v6-in-v4
+ * addresses in GLib. See bgo#646082 */
+ gibber_normalize_address (&(addr.storage));
+
+ addr.in.sin_port = g_htons ((guint16) portnum);
+
+ ll_transport = gibber_ll_transport_new ();
+ set_transport (self, GIBBER_TRANSPORT (ll_transport));
+ gibber_ll_transport_open_sockaddr (ll_transport, &addr.storage, NULL);
+ g_object_unref (ll_transport);
+
+out:
+ if (socket_address != NULL)
+ g_object_unref (socket_address);
+
+ g_strfreev (tokens);
+}
+
+static void
+opened_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyMetaPorter *porter = WOCKY_META_PORTER (source_object);
+ GibberBytestreamOOB *self = user_data;
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (!wocky_meta_porter_open_finish (porter, result, &error))
+ {
+ DEBUG ("failed to open connection to contact");
+ g_clear_error (&error);
+ }
+ else
+ {
+ connect_to_url (self);
+ }
+
+ g_free (priv->url);
+
+ wocky_meta_porter_unhold (porter, priv->contact);
+}
+
+static gboolean
+parse_oob_init_iq (GibberBytestreamOOB *self,
+ WockyStanza *stanza)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ WockyNode *query_node, *url_node;
+ WockyStanzaType type;
+ WockyStanzaSubType sub_type;
+ const gchar *stream_id, *url;
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+
+ wocky_stanza_get_type_info (stanza, &type, &sub_type);
+
+ if (type != WOCKY_STANZA_TYPE_IQ ||
+ sub_type != WOCKY_STANZA_SUB_TYPE_SET)
+ return FALSE;
+
+ query_node = wocky_node_get_child_ns (node, "query",
+ WOCKY_XMPP_NS_IQ_OOB);
+ if (query_node == NULL)
+ return FALSE;
+
+ stream_id = wocky_node_get_attribute (query_node, "sid");
+ if (stream_id == NULL || strcmp (stream_id, priv->stream_id) != 0)
+ return FALSE;
+
+ url_node = wocky_node_get_child (query_node, "url");
+ if (url_node == NULL)
+ return FALSE;
+ url = url_node->content;
+
+ priv->recipient = TRUE;
+ priv->stream_open_id = g_strdup (wocky_node_get_attribute (
+ node, "id"));
+
+ if (!g_str_has_prefix (url, "x-tcp://"))
+ {
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND,
+ "URL is not a TCP URL" };
+
+ DEBUG ("URL is not a TCP URL: %s. Close the bytestream", priv->url);
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e);
+ return TRUE;
+ }
+
+ priv->url = g_strdup (url);
+
+ wocky_meta_porter_open_async (WOCKY_META_PORTER (priv->porter),
+ WOCKY_LL_CONTACT (priv->contact), NULL, opened_cb, self);
+
+ return TRUE;
+}
+
+static gboolean
+parse_oob_iq_result (GibberBytestreamOOB *self,
+ WockyStanza *stanza)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ WockyStanzaType type;
+ WockyStanzaSubType sub_type;
+ const gchar *id;
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+
+ if (priv->recipient)
+ /* Only the sender have to wait for the IQ reply */
+ return FALSE;
+
+ wocky_stanza_get_type_info (stanza, &type, &sub_type);
+
+ if (type != WOCKY_STANZA_TYPE_IQ ||
+ sub_type != WOCKY_STANZA_SUB_TYPE_RESULT)
+ return FALSE;
+
+ /* FIXME: we should check if it's the right sender */
+ id = wocky_node_get_attribute (node, "id");
+
+ if (id == NULL || strcmp (id, priv->stream_open_id) != 0)
+ return FALSE;
+
+ DEBUG ("received OOB close stanza - ignoring");
+
+ return TRUE;
+}
+
+static gboolean
+received_stanza_cb (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ GibberBytestreamOOB *self = (GibberBytestreamOOB *) user_data;
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+ const gchar *from;
+
+ /* discard invalid stanza */
+ from = wocky_node_get_attribute (node, "from");
+ if (from == NULL)
+ {
+ DEBUG ("got a message without a from field");
+ return FALSE;
+ }
+
+ if (parse_oob_init_iq (self, stanza))
+ return TRUE;
+
+ if (parse_oob_iq_result (self, stanza))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+gibber_bytestream_oob_dispose (GObject *object)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (object);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->state != GIBBER_BYTESTREAM_STATE_CLOSED)
+ {
+ if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSING)
+ {
+ bytestream_closed (self);
+ }
+ else
+ {
+ gibber_bytestream_oob_do_close (self, NULL, FALSE);
+ }
+ }
+
+ if (priv->listener != NULL)
+ {
+ g_object_unref (priv->listener);
+ priv->listener = NULL;
+ }
+
+ if (priv->porter != NULL)
+ {
+ wocky_porter_unregister_handler (priv->porter, priv->stanza_received_id);
+ priv->stanza_received_id = 0;
+ g_object_unref (priv->porter);
+ priv->porter = NULL;
+ }
+
+ if (priv->contact != NULL)
+ {
+ g_object_unref (priv->contact);
+ priv->contact = NULL;
+ }
+
+ if (priv->stream_init_iq != NULL)
+ {
+ g_object_unref (priv->stream_init_iq);
+ priv->stream_init_iq = NULL;
+ }
+
+ G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->dispose (object);
+}
+
+static void
+gibber_bytestream_oob_finalize (GObject *object)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (object);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ g_free (priv->stream_id);
+ g_free (priv->stream_open_id);
+ g_free (priv->host);
+ g_free (priv->self_id);
+ g_free (priv->peer_id);
+
+ G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->finalize (object);
+}
+
+static void
+gibber_bytestream_oob_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (object);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_PORTER:
+ g_value_set_object (value, priv->porter);
+ break;
+ case PROP_CONTACT:
+ g_value_set_object (value, priv->contact);
+ break;
+ case PROP_SELF_ID:
+ g_value_set_string (value, priv->self_id);
+ break;
+ case PROP_PEER_ID:
+ g_value_set_string (value, priv->peer_id);
+ break;
+ case PROP_STREAM_ID:
+ g_value_set_string (value, priv->stream_id);
+ break;
+ case PROP_STREAM_INIT_IQ:
+ g_value_set_object (value, priv->stream_init_iq);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case PROP_HOST:
+ g_value_set_string (value, priv->host);
+ break;
+ case PROP_PROTOCOL:
+ g_value_set_string (value, WOCKY_XMPP_NS_IQ_OOB);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+make_porter_connections (GibberBytestreamOOB *self)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ gchar *jid;
+
+ jid = wocky_contact_dup_jid (priv->contact);
+
+ priv->stanza_received_id = wocky_porter_register_handler_from (priv->porter,
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_TYPE_NONE, jid,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, received_stanza_cb, self, NULL);
+
+ g_free (jid);
+}
+
+static void
+gibber_bytestream_oob_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (object);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_PORTER:
+ priv->porter = g_value_dup_object (value);
+ break;
+ case PROP_CONTACT:
+ priv->contact = g_value_dup_object (value);
+ break;
+ case PROP_SELF_ID:
+ g_free (priv->self_id);
+ priv->self_id = g_value_dup_string (value);
+ break;
+ case PROP_PEER_ID:
+ g_free (priv->peer_id);
+ priv->peer_id = g_value_dup_string (value);
+ break;
+ case PROP_STREAM_ID:
+ g_free (priv->stream_id);
+ priv->stream_id = g_value_dup_string (value);
+ break;
+ case PROP_STREAM_INIT_IQ:
+ priv->stream_init_iq = g_value_dup_object (value);
+ break;
+ case PROP_STATE:
+ if (priv->state != g_value_get_uint (value))
+ {
+ priv->state = g_value_get_uint (value);
+ g_signal_emit_by_name (object, "state-changed", priv->state);
+ }
+ break;
+ case PROP_HOST:
+ g_free (priv->host);
+ priv->host = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+gibber_bytestream_oob_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ GibberBytestreamOOBPrivate *priv;
+
+ obj = G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->
+ constructor (type, n_props, props);
+
+ priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (GIBBER_BYTESTREAM_OOB (obj));
+
+ g_assert (priv->self_id != NULL);
+ g_assert (priv->peer_id != NULL);
+ g_assert (priv->stream_id != NULL);
+ g_assert (priv->porter != NULL);
+ g_assert (priv->contact != NULL);
+
+ return obj;
+}
+
+static void
+gibber_bytestream_oob_constructed (GObject *obj)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (obj);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ if (G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->constructed (obj);
+
+ if (priv->porter != NULL && priv->contact != NULL)
+ make_porter_connections (self);
+}
+
+
+static void
+gibber_bytestream_oob_class_init (
+ GibberBytestreamOOBClass *gibber_bytestream_oob_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_bytestream_oob_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (gibber_bytestream_oob_class,
+ sizeof (GibberBytestreamOOBPrivate));
+
+ object_class->dispose = gibber_bytestream_oob_dispose;
+ object_class->finalize = gibber_bytestream_oob_finalize;
+
+ object_class->get_property = gibber_bytestream_oob_get_property;
+ object_class->set_property = gibber_bytestream_oob_set_property;
+ object_class->constructor = gibber_bytestream_oob_constructor;
+ object_class->constructed = gibber_bytestream_oob_constructed;
+
+ g_object_class_override_property (object_class, PROP_SELF_ID,
+ "self-id");
+ g_object_class_override_property (object_class, PROP_PEER_ID,
+ "peer-id");
+ g_object_class_override_property (object_class, PROP_STREAM_ID,
+ "stream-id");
+ g_object_class_override_property (object_class, PROP_STATE,
+ "state");
+ g_object_class_override_property (object_class, PROP_PROTOCOL,
+ "protocol");
+
+ param_spec = g_param_spec_object (
+ "porter",
+ "WockyPorter object",
+ "Wocky porter object used for communication by this "
+ "bytestream if it's a private one",
+ WOCKY_TYPE_PORTER,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PORTER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "contact",
+ "WockyContact object",
+ "Contact object used for communication by this "
+ "bytestream if it's a private one",
+ WOCKY_TYPE_CONTACT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "stream-init-iq",
+ "stream init IQ",
+ "the iq of the SI request",
+ WOCKY_TYPE_STANZA,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAM_INIT_IQ,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "host",
+ "host",
+ "The hostname to use in the OOB URL. Literal are not allowed",
+ "",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HOST,
+ param_spec);
+}
+
+/*
+ * gibber_bytestream_oob_send
+ *
+ * Implements gibber_bytestream_iface_send on GibberBytestreamIface
+ */
+static gboolean
+gibber_bytestream_oob_send (GibberBytestreamIface *bytestream,
+ guint len,
+ const gchar *str)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (bytestream);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (priv->state != GIBBER_BYTESTREAM_STATE_OPEN)
+ {
+ DEBUG ("can't send data through a not open bytestream (state: %d)",
+ priv->state);
+ return FALSE;
+ }
+
+ if (priv->write_blocked)
+ {
+ DEBUG ("sending data while the bytestream was blocked");
+ }
+
+ DEBUG ("send %u bytes through bytestream", len);
+ if (!gibber_transport_send (priv->transport, (const guint8 *) str, len,
+ &error))
+ {
+ DEBUG ("sending failed: %s", error->message);
+ g_error_free (error);
+
+ gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
+ return FALSE;
+ }
+
+ if (!gibber_transport_buffer_is_empty (priv->transport))
+ {
+ /* We >don't want to send more data while the buffer isn't empty */
+ DEBUG ("buffer isn't empty. Block write to the bytestream");
+ change_write_blocked_state (self, TRUE);
+ }
+
+ return TRUE;
+}
+
+static WockyStanza *
+create_si_accept_iq (GibberBytestreamOOB *self)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ return wocky_stanza_build_iq_result (priv->stream_init_iq,
+ '(', "si",
+ ':', WOCKY_XMPP_NS_SI,
+ '(', "feature",
+ ':', WOCKY_XMPP_NS_FEATURENEG,
+ '(', "x",
+ ':', WOCKY_XMPP_NS_DATA,
+ '@', "type", "submit",
+ '(', "field",
+ '@', "var", "stream-method",
+ '(', "value",
+ '$', WOCKY_XMPP_NS_IQ_OOB,
+ ')',
+ ')',
+ ')',
+ ')',
+ ')', NULL);
+}
+
+/*
+ * gibber_bytestream_oob_accept
+ *
+ * Implements gibber_bytestream_iface_accept on GibberBytestreamIface
+ */
+static void
+gibber_bytestream_oob_accept (GibberBytestreamIface *bytestream,
+ GibberBytestreamAugmentSiAcceptReply func,
+ gpointer user_data)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (bytestream);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ WockyStanza *stanza;
+ WockyNode *node;
+ WockyNode *si;
+
+ if (priv->state != GIBBER_BYTESTREAM_STATE_LOCAL_PENDING)
+ {
+ /* The stream was previoulsy or automatically accepted */
+ DEBUG ("stream was already accepted");
+ return;
+ }
+
+ stanza = create_si_accept_iq (self);
+ node = wocky_stanza_get_top_node (stanza);
+ si = wocky_node_get_child_ns (node, "si", WOCKY_XMPP_NS_SI);
+ g_assert (si != NULL);
+
+ if (func != NULL)
+ {
+ /* let the caller add his profile specific data */
+ func (si, user_data);
+ }
+
+ wocky_porter_send (priv->porter, stanza);
+
+ DEBUG ("stream is now accepted");
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_ACCEPTED, NULL);
+ g_object_unref (stanza);
+}
+
+static void
+bytestream_closed (GibberBytestreamOOB *self)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ if (priv->recipient)
+ {
+ WockyStanza *stanza;
+
+ /* As described in the XEP, we send result IQ when we have
+ * finished to use the OOB */
+ stanza = make_iq_oob_sucess_response (priv->self_id,
+ priv->peer_id, priv->stream_open_id);
+ wocky_stanza_set_to_contact (stanza, priv->contact);
+
+ DEBUG ("send OOB close stanza");
+
+ wocky_porter_send (priv->porter, stanza);
+ g_object_unref (stanza);
+ }
+ else
+ {
+ /* We are the sender. Don't have to send anything */
+ }
+
+ if (priv->transport != NULL)
+ {
+ g_signal_handlers_disconnect_matched (priv->transport,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+ gibber_transport_disconnect (priv->transport);
+ g_object_unref (priv->transport);
+ priv->transport = NULL;
+ }
+
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSED, NULL);
+}
+
+static void
+gibber_bytestream_oob_decline (GibberBytestreamOOB *self,
+ GError *error)
+ {
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ g_return_if_fail (priv->state == GIBBER_BYTESTREAM_STATE_LOCAL_PENDING);
+
+ if (error != NULL
+ && (error->domain == WOCKY_XMPP_ERROR || error->domain == WOCKY_SI_ERROR))
+ {
+ wocky_porter_send_iq_gerror (priv->porter, priv->stream_init_iq, error);
+ }
+ else
+ {
+ wocky_porter_send_iq_error (priv->porter, priv->stream_init_iq,
+ WOCKY_XMPP_ERROR_FORBIDDEN, "Offer declined");
+ }
+}
+
+static void
+gibber_bytestream_oob_do_close (GibberBytestreamOOB *self,
+ GError *error,
+ gboolean can_wait)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ /* bytestream already closed, do nothing */
+ return;
+
+ if (priv->state == GIBBER_BYTESTREAM_STATE_LOCAL_PENDING)
+ {
+ /* Stream was created using SI so we decline the request */
+ gibber_bytestream_oob_decline (self, error);
+ }
+
+ g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSING, NULL);
+ if (can_wait && priv->transport != NULL &&
+ !gibber_transport_buffer_is_empty (priv->transport))
+ {
+ DEBUG ("Wait transport buffer is empty before close the bytestream");
+ }
+ else
+ {
+ DEBUG ("Transport buffer is empty, we can close the bytestream");
+ bytestream_closed (self);
+ }
+}
+
+/*
+ * gibber_bytestream_oob_close
+ *
+ * Implements gibber_bytestream_iface_close on GibberBytestreamIface
+ */
+static void
+gibber_bytestream_oob_close (GibberBytestreamIface *bytestream,
+ GError *error)
+{
+ gibber_bytestream_oob_do_close (GIBBER_BYTESTREAM_OOB (bytestream), error,
+ TRUE);
+}
+
+static WockyStanza *
+make_oob_init_iq (const gchar *from,
+ const gchar *to,
+ const gchar *stream_id,
+ const gchar *url)
+{
+ return wocky_stanza_build (
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
+ from, to,
+ '(', "query",
+ ':', WOCKY_XMPP_NS_IQ_OOB,
+ '@', "sid", stream_id,
+ '(', "url",
+ '$', url,
+ ')',
+ ')', NULL);
+}
+
+static void
+new_connection_cb (GibberListener *listener,
+ GibberTransport *transport,
+ struct sockaddr *addr,
+ guint size,
+ gpointer user_data)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (user_data);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ if (priv->check_addr_func != NULL && !priv->check_addr_func (self, addr,
+ size, priv->check_addr_func_data))
+ {
+ DEBUG ("connection refused by the bytestream user");
+ return;
+ }
+
+ DEBUG("New connection..");
+
+ set_transport (self, transport);
+}
+
+/*
+ * gibber_bytestream_oob_initiate
+ *
+ * Implements gibber_bytestream_iface_initiate on GibberBytestreamIface
+ */
+static gboolean
+gibber_bytestream_oob_initiate (GibberBytestreamIface *bytestream)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (bytestream);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+ WockyStanza *stanza;
+ WockyNode *node;
+ const gchar *id;
+ int port;
+ gchar *url;
+
+ if (priv->state != GIBBER_BYTESTREAM_STATE_INITIATING)
+ {
+ DEBUG ("bytestream is not is the initiating state (state %d)",
+ priv->state);
+ return FALSE;
+ }
+ g_assert (priv->host != NULL);
+
+ priv->recipient = FALSE;
+
+ g_assert (priv->listener == NULL);
+ priv->listener = gibber_listener_new ();
+
+ g_signal_connect (priv->listener, "new-connection",
+ G_CALLBACK (new_connection_cb), self);
+
+ if (!gibber_listener_listen_tcp (priv->listener, 0, NULL))
+ {
+ DEBUG ("can't listen for incoming connection");
+ return FALSE;
+ }
+ port = gibber_listener_get_port (priv->listener);
+
+ url = g_strdup_printf ("x-tcp://%s:%d", priv->host, port);
+
+ stanza = make_oob_init_iq (priv->self_id, priv->peer_id,
+ priv->stream_id, url);
+ g_free (url);
+ wocky_stanza_set_to_contact (stanza, priv->contact);
+ node = wocky_stanza_get_top_node (stanza);
+
+ id = wocky_node_get_attribute (node, "id");
+ if (id == NULL)
+ {
+ /* let the porter generate the IQ id for us */
+ wocky_porter_send_iq_async (priv->porter, stanza,
+ NULL, NULL, NULL);
+
+ priv->stream_open_id = g_strdup (
+ wocky_node_get_attribute (node, "id"));
+ }
+ else
+ {
+ /* save the stream open ID */
+ priv->stream_open_id = g_strdup (id);
+
+ wocky_porter_send (priv->porter, stanza);
+ }
+
+ g_object_unref (stanza);
+
+ return TRUE;
+}
+
+void
+gibber_bytestream_oob_set_check_addr_func (
+ GibberBytestreamOOB *self,
+ GibberBytestreamOOBCheckAddrFunc func,
+ gpointer user_data)
+{
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ priv->check_addr_func = func;
+ priv->check_addr_func_data = user_data;
+}
+
+static void
+gibber_bytestream_oob_block_reading (GibberBytestreamIface *bytestream,
+ gboolean block)
+{
+ GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (bytestream);
+ GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self);
+
+ if (priv->read_blocked == block)
+ return;
+
+ priv->read_blocked = block;
+
+ DEBUG ("%s the transport bytestream", block ? "block": "unblock");
+ gibber_transport_block_receiving (priv->transport, block);
+}
+
+static void
+bytestream_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ GibberBytestreamIfaceClass *klass = (GibberBytestreamIfaceClass *) g_iface;
+
+ klass->initiate = gibber_bytestream_oob_initiate;
+ klass->send = gibber_bytestream_oob_send;
+ klass->close = gibber_bytestream_oob_close;
+ klass->accept = gibber_bytestream_oob_accept;
+ klass->block_reading = gibber_bytestream_oob_block_reading;
+}
diff --git a/salut/lib/gibber/gibber-bytestream-oob.h b/salut/lib/gibber/gibber-bytestream-oob.h
new file mode 100644
index 000000000..66b960e65
--- /dev/null
+++ b/salut/lib/gibber/gibber-bytestream-oob.h
@@ -0,0 +1,73 @@
+/*
+ * gibber-bytestream-oob.h - Header for GibberBytestreamOOB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_BYTESTREAM_OOB_H__
+#define __GIBBER_BYTESTREAM_OOB_H__
+
+#include <glib-object.h>
+
+#include "gibber-bytestream-iface.h"
+#include "gibber-sockets.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberBytestreamOOB GibberBytestreamOOB;
+typedef struct _GibberBytestreamOOBClass GibberBytestreamOOBClass;
+
+typedef gboolean (* GibberBytestreamOOBCheckAddrFunc) (
+ GibberBytestreamOOB *bytestream, struct sockaddr *addr,
+ socklen_t addrlen, gpointer user_data);
+
+
+struct _GibberBytestreamOOBClass {
+ GObjectClass parent_class;
+};
+
+struct _GibberBytestreamOOB {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType gibber_bytestream_oob_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_BYTESTREAM_OOB \
+ (gibber_bytestream_oob_get_type ())
+#define GIBBER_BYTESTREAM_OOB(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_BYTESTREAM_OOB,\
+ GibberBytestreamOOB))
+#define GIBBER_BYTESTREAM_OOB_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_BYTESTREAM_OOB,\
+ GibberBytestreamOOBClass))
+#define GIBBER_IS_BYTESTREAM_OOB(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_BYTESTREAM_OOB))
+#define GIBBER_IS_BYTESTREAM_OOB_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_BYTESTREAM_OOB))
+#define GIBBER_BYTESTREAM_OOB_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_BYTESTREAM_OOB,\
+ GibberBytestreamOOBClass))
+
+void gibber_bytestream_oob_set_check_addr_func (
+ GibberBytestreamOOB *bytestream, GibberBytestreamOOBCheckAddrFunc func,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_BYTESTREAM_OOB_H__ */
diff --git a/salut/lib/gibber/gibber-debug.c b/salut/lib/gibber/gibber-debug.c
new file mode 100644
index 000000000..47cc8742a
--- /dev/null
+++ b/salut/lib/gibber/gibber-debug.c
@@ -0,0 +1,99 @@
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+#include "gibber-debug.h"
+
+#ifdef ENABLE_DEBUG
+
+static DebugFlags flags = 0;
+static gboolean initialized = FALSE;
+
+static GDebugKey keys[] = {
+ { "transport", DEBUG_TRANSPORT },
+ { "net", DEBUG_NET },
+ { "xmpp", DEBUG_XMPP },
+ { "xmpp-reader", DEBUG_XMPP_READER },
+ { "xmpp-writer", DEBUG_XMPP_WRITER },
+ { "sasl", DEBUG_SASL },
+ { "ssl", DEBUG_SSL },
+ { "rmulticast", DEBUG_RMULTICAST },
+ { "rmulticast-sender", DEBUG_RMULTICAST_SENDER },
+ { "muc-connection", DEBUG_MUC_CONNECTION },
+ { "bytestream", DEBUG_BYTESTREAM },
+ { "ft", DEBUG_FILE_TRANSFER },
+ { "all", ~0 },
+ { 0, },
+};
+
+void gibber_debug_set_flags_from_env ()
+{
+ guint nkeys;
+ const gchar *flags_string;
+
+ for (nkeys = 0; keys[nkeys].value; nkeys++);
+
+ flags_string = g_getenv ("GIBBER_DEBUG");
+
+ if (flags_string)
+ gibber_debug_set_flags (g_parse_debug_string (flags_string, keys, nkeys));
+
+ initialized = TRUE;
+}
+
+void gibber_debug_set_flags (DebugFlags new_flags)
+{
+ flags |= new_flags;
+ initialized = TRUE;
+}
+
+gboolean gibber_debug_flag_is_set (DebugFlags flag)
+{
+ return flag & flags;
+}
+
+void gibber_debug (DebugFlags flag,
+ const gchar *format,
+ ...)
+{
+ if (G_UNLIKELY(!initialized))
+ gibber_debug_set_flags_from_env ();
+ if (flag & flags)
+ {
+ va_list args;
+ va_start (args, format);
+ g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format, args);
+ va_end (args);
+ }
+}
+
+void
+gibber_debug_stanza (DebugFlags flag,
+ WockyStanza *stanza,
+ const gchar *format,
+ ...)
+{
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+
+ if (G_UNLIKELY(!initialized))
+ gibber_debug_set_flags_from_env ();
+ if (flag & flags)
+ {
+ va_list args;
+ gchar *msg, *node_str;
+
+ va_start (args, format);
+ msg = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ node_str = wocky_node_to_string (node);
+
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%s\n%s", msg, node_str);
+
+ g_free (msg);
+ g_free (node_str);
+ }
+}
+
+#endif
diff --git a/salut/lib/gibber/gibber-debug.h b/salut/lib/gibber/gibber-debug.h
new file mode 100644
index 000000000..47bd7ba90
--- /dev/null
+++ b/salut/lib/gibber/gibber-debug.h
@@ -0,0 +1,81 @@
+
+#ifndef __DEBUG_H__
+#define __DEBUG_H__
+
+#include "config.h"
+
+#include <glib.h>
+
+#include <wocky/wocky-stanza.h>
+
+G_BEGIN_DECLS
+
+#ifdef ENABLE_DEBUG
+
+typedef enum
+{
+ DEBUG_TRANSPORT = 1 << 0,
+ DEBUG_NET = 1 << 1,
+ DEBUG_XMPP_READER = 1 << 2,
+ DEBUG_XMPP_WRITER = 1 << 3,
+ DEBUG_SASL = 1 << 4,
+ DEBUG_SSL = 1 << 5,
+ DEBUG_RMULTICAST = 1 << 6,
+ DEBUG_RMULTICAST_SENDER = 1 << 7,
+ DEBUG_MUC_CONNECTION = 1 << 8,
+ DEBUG_BYTESTREAM = 1 << 9,
+ DEBUG_FILE_TRANSFER = 1 << 10,
+} DebugFlags;
+
+#define DEBUG_XMPP (DEBUG_XMPP_READER | DEBUG_XMPP_WRITER)
+
+void gibber_debug_set_flags_from_env (void);
+void gibber_debug_set_flags (DebugFlags flags);
+gboolean gibber_debug_flag_is_set (DebugFlags flag);
+void gibber_debug (DebugFlags flag, const gchar *format, ...)
+ G_GNUC_PRINTF (2, 3);
+void gibber_debug_stanza (DebugFlags flag, WockyStanza *stanza,
+ const gchar *format, ...)
+ G_GNUC_PRINTF (3, 4);
+
+#ifdef DEBUG_FLAG
+
+#define DEBUG(format, ...) \
+ gibber_debug (DEBUG_FLAG, "%s: " format, G_STRFUNC, ##__VA_ARGS__)
+
+#define DEBUG_STANZA(stanza, format, ...) \
+ gibber_debug_stanza (DEBUG_FLAG, stanza, "%s: " format, G_STRFUNC,\
+ ##__VA_ARGS__)
+
+#define DEBUGGING debug_flag_is_set(DEBUG_FLAG)
+
+#endif /* DEBUG_FLAG */
+
+#else /* ENABLE_DEBUG */
+
+#ifdef DEBUG_FLAG
+
+static inline void
+DEBUG (
+ const gchar *format,
+ ...)
+{
+}
+
+static inline void
+DEBUG_STANZA (
+ WockyStanza *stanza,
+ const gchar *format,
+ ...)
+{
+}
+
+#define DEBUGGING 0
+
+#endif /* DEBUG_FLAG */
+
+#endif /* ENABLE_DEBUG */
+
+G_END_DECLS
+
+#endif
diff --git a/salut/lib/gibber/gibber-fd-transport.c b/salut/lib/gibber/gibber-fd-transport.c
new file mode 100644
index 000000000..e23f1bc4a
--- /dev/null
+++ b/salut/lib/gibber/gibber-fd-transport.c
@@ -0,0 +1,548 @@
+/*
+ * gibber-fd-transport.c - Source for GibberFdTransport
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "gibber-sockets.h"
+#include "gibber-fd-transport.h"
+
+#define DEBUG_FLAG DEBUG_NET
+#include "gibber-debug.h"
+
+static gboolean _channel_io_out (GIOChannel *source,
+ GIOCondition condition, gpointer data);
+
+
+static gboolean gibber_fd_transport_send (GibberTransport *transport,
+ const guint8 *data, gsize size, GError **error);
+
+static void gibber_fd_transport_disconnect (GibberTransport *transport);
+
+static gboolean gibber_fd_transport_get_peeraddr (GibberTransport *transport,
+ struct sockaddr_storage *addr, socklen_t *len);
+
+static gboolean gibber_fd_transport_get_sockaddr (GibberTransport *transport,
+ struct sockaddr_storage *addr,
+ socklen_t *len);
+
+static void _do_disconnect (GibberFdTransport *self);
+
+static gboolean gibber_fd_transport_buffer_is_empty (
+ GibberTransport *transport);
+
+static void gibber_fd_transport_block_receiving (GibberTransport *transport,
+ gboolean block);
+
+G_DEFINE_TYPE(GibberFdTransport, gibber_fd_transport, GIBBER_TYPE_TRANSPORT)
+
+GQuark
+gibber_fd_transport_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string ("gibber_fd_transport_error");
+
+ return quark;
+}
+
+/* private structure */
+typedef struct _GibberFdTransportPrivate GibberFdTransportPrivate;
+
+struct _GibberFdTransportPrivate
+{
+ GIOChannel *channel;
+ gboolean dispose_has_run;
+ guint watch_in;
+ guint watch_out;
+ guint watch_err;
+ GString *output_buffer;
+ gboolean receiving_blocked;
+};
+
+#define GIBBER_FD_TRANSPORT_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_FD_TRANSPORT, \
+ GibberFdTransportPrivate))
+
+
+static void
+gibber_fd_transport_init (GibberFdTransport *self)
+{
+ GibberFdTransportPrivate *priv = GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+ self->fd = -1;
+ priv->channel = NULL;
+ priv->output_buffer = NULL;
+ priv->watch_in = 0;
+ priv->watch_out = 0;
+ priv->watch_err = 0;
+}
+
+static void gibber_fd_transport_dispose (GObject *object);
+static void gibber_fd_transport_finalize (GObject *object);
+static GibberFdIOResult gibber_fd_transport_write (
+ GibberFdTransport *fd_transport, GIOChannel *channel, const guint8 *data,
+ int len, gsize *written, GError **error);
+
+static void
+gibber_fd_transport_class_init (
+ GibberFdTransportClass *gibber_fd_transport_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_fd_transport_class);
+ GibberTransportClass *transport_class =
+ GIBBER_TRANSPORT_CLASS (gibber_fd_transport_class);
+
+ g_type_class_add_private (gibber_fd_transport_class,
+ sizeof (GibberFdTransportPrivate));
+
+ object_class->dispose = gibber_fd_transport_dispose;
+ object_class->finalize = gibber_fd_transport_finalize;
+
+ transport_class->send = gibber_fd_transport_send;
+ transport_class->disconnect = gibber_fd_transport_disconnect;
+ transport_class->get_peeraddr = gibber_fd_transport_get_peeraddr;
+ transport_class->get_sockaddr = gibber_fd_transport_get_sockaddr;
+ transport_class->buffer_is_empty = gibber_fd_transport_buffer_is_empty;
+ transport_class->block_receiving = gibber_fd_transport_block_receiving;
+
+ gibber_fd_transport_class->read = gibber_fd_transport_read;
+ gibber_fd_transport_class->write = gibber_fd_transport_write;
+}
+
+void
+gibber_fd_transport_dispose (GObject *object)
+{
+ GibberFdTransport *self = GIBBER_FD_TRANSPORT (object);
+ GibberFdTransportPrivate *priv = GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ _do_disconnect (self);
+
+ if (G_OBJECT_CLASS (gibber_fd_transport_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_fd_transport_parent_class)->dispose (object);
+}
+
+void
+gibber_fd_transport_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (gibber_fd_transport_parent_class)->finalize (object);
+}
+
+static void
+_do_disconnect (GibberFdTransport *self)
+{
+ GibberFdTransportPrivate *priv = GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+
+ if (GIBBER_TRANSPORT(self)->state == GIBBER_TRANSPORT_DISCONNECTED)
+ {
+ return;
+ }
+
+ DEBUG ("Closing the fd transport");
+
+ if (priv->channel != NULL)
+ {
+ if (priv->watch_in != 0)
+ g_source_remove (priv->watch_in);
+
+ if (priv->watch_out)
+ g_source_remove (priv->watch_out);
+
+ g_source_remove (priv->watch_err);
+ g_io_channel_shutdown (priv->channel, FALSE, NULL);
+ g_io_channel_unref (priv->channel);
+ priv->channel = NULL;
+ }
+ else
+ {
+ close (self->fd);
+ }
+ self->fd = -1;
+
+ if (priv->output_buffer)
+ {
+ g_string_free (priv->output_buffer, TRUE);
+ priv->output_buffer = NULL;
+ }
+
+ if (!priv->dispose_has_run)
+ /* If we are disposing we don't care about the state anymore */
+ gibber_transport_set_state (GIBBER_TRANSPORT (self),
+ GIBBER_TRANSPORT_DISCONNECTED);
+}
+
+static gboolean
+_try_write (GibberFdTransport *self, const guint8 *data, int len,
+ gsize *written, GError **err)
+{
+ GibberFdTransportPrivate *priv = GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+ GibberFdTransportClass *cls = GIBBER_FD_TRANSPORT_GET_CLASS (self);
+ GibberFdIOResult result;
+ GError *error = NULL;
+
+ result = cls->write (self, priv->channel, data, len, written, &error);
+
+ switch (result)
+ {
+ case GIBBER_FD_IO_RESULT_SUCCESS:
+ case GIBBER_FD_IO_RESULT_AGAIN:
+ break;
+ case GIBBER_FD_IO_RESULT_ERROR:
+ gibber_transport_emit_error (GIBBER_TRANSPORT (self), error);
+ /* fallthrough */
+ case GIBBER_FD_IO_RESULT_EOF:
+ DEBUG ("Writing data failed, closing the transport");
+ _do_disconnect (self);
+
+ g_propagate_error (err, error);
+ return FALSE;
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_writeout (GibberFdTransport *self, const guint8 *data, gsize len,
+ GError **error)
+{
+ GibberFdTransportPrivate *priv = GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+ gsize written = 0;
+
+ DEBUG ("Writing out %" G_GSIZE_FORMAT " bytes", len);
+ if (priv->output_buffer == NULL || priv->output_buffer->len == 0)
+ {
+ /* We've got nothing buffer yet so try to write out directly */
+ if (!_try_write (self, data, len, &written, error))
+ {
+ return FALSE;
+ }
+ }
+
+ if (written == len)
+ {
+ gibber_transport_emit_buffer_empty (GIBBER_TRANSPORT (self));
+ return TRUE;
+ }
+
+ if (priv->output_buffer)
+ {
+ g_string_append_len (priv->output_buffer, (gchar *) data + written,
+ len - written);
+ }
+ else
+ {
+ priv->output_buffer = g_string_new_len ((gchar *) data + written,
+ len - written);
+ }
+
+ if (!priv->watch_out)
+ {
+ priv->watch_out =
+ g_io_add_watch (priv->channel, G_IO_OUT, _channel_io_out, self);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_channel_io_in (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ GibberFdTransport *self = GIBBER_FD_TRANSPORT (data);
+ GibberFdTransportPrivate *priv =
+ GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+ GibberFdIOResult result;
+ GError *error = NULL;
+ GibberFdTransportClass *cls = GIBBER_FD_TRANSPORT_GET_CLASS(self);
+
+ result = cls->read (self, priv->channel, &error);
+
+ switch (result)
+ {
+ case GIBBER_FD_IO_RESULT_SUCCESS:
+ case GIBBER_FD_IO_RESULT_AGAIN:
+ break;
+ case GIBBER_FD_IO_RESULT_ERROR:
+ gibber_transport_emit_error (GIBBER_TRANSPORT(self), error);
+ /* Deliberately falling through */
+ case GIBBER_FD_IO_RESULT_EOF:
+ DEBUG("Failed to read from the transport, closing..");
+ _do_disconnect (self);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_channel_io_out (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ GibberFdTransport *self = GIBBER_FD_TRANSPORT (data);
+ GibberFdTransportPrivate *priv =
+ GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+ gsize written;
+
+ g_assert (priv->output_buffer);
+ if (!_try_write (self, (guint8 *) priv->output_buffer->str,
+ priv->output_buffer->len, &written, NULL))
+ {
+ return FALSE;
+ }
+
+ if (written > 0 )
+ {
+ priv->output_buffer = g_string_erase (priv->output_buffer, 0, written);
+ }
+
+ if (priv->output_buffer->len == 0)
+ {
+ priv->watch_out = 0;
+ gibber_transport_emit_buffer_empty (GIBBER_TRANSPORT (self));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_channel_io_err (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ GibberFdTransport *self = GIBBER_FD_TRANSPORT (data);
+ GError *error = NULL;
+ gint code;
+ const gchar *msg;
+
+ if (condition & G_IO_ERR)
+ {
+ DEBUG ("Error on GIOChannel.Closing the transport");
+ /* We can't use g_io_channel_error_from_errno because it seems errno is
+ * not always set when we got a G_IO_ERR. */
+ code = GIBBER_FD_TRANSPORT_ERROR_FAILED;
+ msg = "Error on GIOChannel";
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ error = g_error_new_literal (GIBBER_FD_TRANSPORT_ERROR, code, msg);
+ gibber_transport_emit_error (GIBBER_TRANSPORT (self), error);
+ g_error_free (error);
+
+ _do_disconnect (self);
+ return FALSE;
+}
+
+/* Default read and write implementations */
+static GibberFdIOResult
+gibber_fd_transport_write (GibberFdTransport *fd_transport,
+ GIOChannel *channel, const guint8 *data, int len,
+ gsize *written, GError **error)
+{
+ GIOStatus status;
+
+ status = g_io_channel_write_chars (channel, (gchar *) data, len,
+ written, error);
+
+ switch (status)
+ {
+ case G_IO_STATUS_NORMAL:
+ return GIBBER_FD_IO_RESULT_SUCCESS;
+ case G_IO_STATUS_AGAIN:
+ return GIBBER_FD_IO_RESULT_AGAIN;
+ case G_IO_STATUS_ERROR:
+ return GIBBER_FD_IO_RESULT_ERROR;
+ case G_IO_STATUS_EOF:
+ return GIBBER_FD_IO_RESULT_EOF;
+ }
+
+ g_assert_not_reached ();
+}
+
+#define BUFSIZE 1024
+
+GibberFdIOResult
+gibber_fd_transport_read (GibberFdTransport *transport,
+ GIOChannel *channel, GError **error)
+{
+ guint8 buf[BUFSIZE + 1];
+ GIOStatus status;
+ gsize bytes_read;
+
+ status = g_io_channel_read_chars (channel, (gchar *) buf, BUFSIZE,
+ &bytes_read, error);
+
+ switch (status)
+ {
+ case G_IO_STATUS_NORMAL:
+ buf[bytes_read] = '\0';
+ DEBUG ("Received %" G_GSIZE_FORMAT " bytes", bytes_read);
+ gibber_transport_received_data (GIBBER_TRANSPORT (transport),
+ buf, bytes_read);
+ return GIBBER_FD_IO_RESULT_SUCCESS;
+ case G_IO_STATUS_ERROR:
+ return GIBBER_FD_IO_RESULT_ERROR;
+ case G_IO_STATUS_EOF:
+ return GIBBER_FD_IO_RESULT_EOF;
+ case G_IO_STATUS_AGAIN:
+ return GIBBER_FD_IO_RESULT_AGAIN;
+ }
+
+ g_assert_not_reached ();
+}
+
+
+void
+gibber_fd_transport_set_fd (GibberFdTransport *self, int fd,
+ gboolean is_socket)
+{
+ GibberFdTransportPrivate *priv = GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+
+ g_assert (self->fd == -1 && fd >= 0);
+
+ self->fd = fd;
+
+ if (is_socket)
+ {
+ gibber_socket_set_nonblocking (fd);
+ priv->channel = gibber_io_channel_new_from_socket (fd);
+ }
+ else
+ {
+#ifndef G_OS_WIN32
+ fcntl (fd, F_SETFL, O_NONBLOCK);
+#endif
+ priv->channel = g_io_channel_unix_new (fd);
+ }
+
+ g_io_channel_set_close_on_unref (priv->channel, TRUE);
+ g_io_channel_set_encoding (priv->channel, NULL, NULL);
+ g_io_channel_set_buffered (priv->channel, FALSE);
+
+ if (!priv->receiving_blocked)
+ {
+ priv->watch_in =
+ g_io_add_watch (priv->channel, G_IO_IN, _channel_io_in, self);
+ }
+
+ priv->watch_err =
+ g_io_add_watch (priv->channel, G_IO_ERR, _channel_io_err, self);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT(self),
+ GIBBER_TRANSPORT_CONNECTED);
+}
+
+gboolean
+gibber_fd_transport_send (GibberTransport *transport,
+ const guint8 *data, gsize size, GError **error)
+{
+ return _writeout (GIBBER_FD_TRANSPORT (transport), data, size, error);
+}
+
+void
+gibber_fd_transport_disconnect (GibberTransport *transport)
+{
+ DEBUG("Connection close requested");
+ _do_disconnect (GIBBER_FD_TRANSPORT (transport));
+}
+
+static gboolean
+gibber_fd_transport_get_peeraddr (GibberTransport *transport,
+ struct sockaddr_storage *addr, socklen_t *len)
+{
+ GibberFdTransport *self = GIBBER_FD_TRANSPORT (transport);
+
+ if (self->fd == -1)
+ {
+ DEBUG ("Someone requested the sockaddr while we're not connected");
+ return FALSE;
+ }
+
+ *len = sizeof (struct sockaddr_storage);
+
+ return (getpeername (self->fd, (struct sockaddr *) addr, len) == 0);
+}
+
+static gboolean
+gibber_fd_transport_get_sockaddr (GibberTransport *transport,
+ struct sockaddr_storage *addr,
+ socklen_t *len)
+{
+ GibberFdTransport *self = GIBBER_FD_TRANSPORT (transport);
+
+ if (self->fd == -1)
+ {
+ DEBUG ("Someone requested the sockaddr while we're not connected");
+ return FALSE;
+ }
+
+ *len = sizeof (struct sockaddr_storage);
+
+ return (getsockname (self->fd, (struct sockaddr *) addr, len) == 0);
+}
+
+static gboolean
+gibber_fd_transport_buffer_is_empty (GibberTransport *transport)
+{
+ GibberFdTransport *self = GIBBER_FD_TRANSPORT (transport);
+ GibberFdTransportPrivate *priv =
+ GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+
+ return (priv->output_buffer == NULL || priv->output_buffer->len == 0);
+}
+
+static void
+gibber_fd_transport_block_receiving (GibberTransport *transport,
+ gboolean block)
+{
+ GibberFdTransport *self = GIBBER_FD_TRANSPORT (transport);
+ GibberFdTransportPrivate *priv = GIBBER_FD_TRANSPORT_GET_PRIVATE (self);
+
+ if (block && priv->watch_in != 0)
+ {
+ DEBUG ("block receiving from the transport");
+ g_source_remove (priv->watch_in);
+ priv->watch_in = 0;
+ }
+ else if (!block && priv->watch_in == 0)
+ {
+ DEBUG ("unblock receiving from the transport");
+ if (priv->channel != NULL)
+ {
+ priv->watch_in = g_io_add_watch (priv->channel, G_IO_IN,
+ _channel_io_in, self);
+ }
+ /* else the transport isn't connected yet */
+ }
+
+ priv->receiving_blocked = block;
+}
diff --git a/salut/lib/gibber/gibber-fd-transport.h b/salut/lib/gibber/gibber-fd-transport.h
new file mode 100644
index 000000000..545e00a32
--- /dev/null
+++ b/salut/lib/gibber/gibber-fd-transport.h
@@ -0,0 +1,96 @@
+/*
+ * gibber-fd-transport.h - Header for GibberFdTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_FD_TRANSPORT_H__
+#define __GIBBER_FD_TRANSPORT_H__
+
+#include <glib-object.h>
+
+#include "gibber-sockets.h"
+#include "gibber-transport.h"
+
+typedef enum {
+ GIBBER_FD_IO_RESULT_SUCCESS,
+ GIBBER_FD_IO_RESULT_AGAIN,
+ GIBBER_FD_IO_RESULT_ERROR,
+ GIBBER_FD_IO_RESULT_EOF,
+} GibberFdIOResult;
+
+G_BEGIN_DECLS
+
+GQuark gibber_fd_transport_error_quark (void);
+#define GIBBER_FD_TRANSPORT_ERROR gibber_fd_transport_error_quark()
+
+typedef enum
+{
+ GIBBER_FD_TRANSPORT_ERROR_PIPE,
+ GIBBER_FD_TRANSPORT_ERROR_FAILED,
+} GibberFdTransportError;
+
+typedef struct _GibberFdTransport GibberFdTransport;
+typedef struct _GibberFdTransportClass GibberFdTransportClass;
+
+
+struct _GibberFdTransportClass {
+ GibberTransportClass parent_class;
+ /* Called when fd is ready for reading */
+ GibberFdIOResult (*read) (GibberFdTransport *fd_transport,
+ GIOChannel *channel, GError **error);
+ /* Called when something needs to be written*/
+ GibberFdIOResult (*write) (GibberFdTransport *fd_transport,
+ GIOChannel *channel, const guint8 *data, int len,
+ gsize *written, GError **error);
+};
+
+struct _GibberFdTransport {
+ GibberTransport parent;
+ int fd;
+};
+
+GType gibber_fd_transport_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_FD_TRANSPORT \
+ (gibber_fd_transport_get_type ())
+#define GIBBER_FD_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_FD_TRANSPORT, \
+ GibberFdTransport))
+#define GIBBER_FD_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_FD_TRANSPORT, \
+ GibberFdTransportClass))
+#define GIBBER_IS_FD_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_FD_TRANSPORT))
+#define GIBBER_IS_FD_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_FD_TRANSPORT))
+#define GIBBER_FD_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_FD_TRANSPORT, \
+ GibberFdTransportClass))
+
+void
+gibber_fd_transport_set_fd (GibberFdTransport *fd_transport, int fd,
+ gboolean is_socket);
+
+GibberFdIOResult gibber_fd_transport_read (GibberFdTransport *transport,
+ GIOChannel *channel,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_FD_TRANSPORT_H__*/
diff --git a/salut/lib/gibber/gibber-file-transfer.c b/salut/lib/gibber/gibber-file-transfer.c
new file mode 100644
index 000000000..b6647c237
--- /dev/null
+++ b/salut/lib/gibber/gibber-file-transfer.c
@@ -0,0 +1,509 @@
+/*
+ * gibber-file-transfer.c - Source for GibberFileTransfer
+ * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gibber-file-transfer.h"
+#include "gibber-oob-file-transfer.h"
+
+#define DEBUG_FLAG DEBUG_FILE_TRANSFER
+#include "gibber-debug.h"
+
+#include "gibber-signals-marshal.h"
+#include "gibber-file-transfer-enumtypes.h"
+
+
+G_DEFINE_TYPE(GibberFileTransfer, gibber_file_transfer, G_TYPE_OBJECT)
+
+/* signal enum */
+enum
+{
+ REMOTE_ACCEPTED,
+ FINISHED,
+ ERROR,
+ TRANSFERRED_CHUNK,
+ CANCELLED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_ID = 1,
+ PROP_SELF_ID,
+ PROP_PEER_ID,
+ PROP_FILENAME,
+ PROP_DIRECTION,
+ PROP_PORTER,
+ PROP_CONTACT,
+ PROP_DESCRIPTION,
+ PROP_CONTENT_TYPE,
+ LAST_PROPERTY
+};
+
+/* private structure */
+struct _GibberFileTransferPrivate
+{
+ WockyPorter *porter;
+ WockyContact *contact;
+
+ guint stanza_id;
+
+ guint64 size;
+};
+
+
+GQuark
+gibber_file_transfer_error_quark (void)
+{
+ static GQuark error_quark = 0;
+
+ if (error_quark == 0)
+ error_quark =
+ g_quark_from_static_string ("gibber-file-transfer-error-quark");
+
+ return error_quark;
+}
+
+static void
+gibber_file_transfer_init (GibberFileTransfer *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GIBBER_TYPE_FILE_TRANSFER,
+ GibberFileTransferPrivate);
+}
+
+static void
+gibber_file_transfer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GibberFileTransfer *self = GIBBER_FILE_TRANSFER (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ g_value_set_string (value, self->id);
+ break;
+ case PROP_SELF_ID:
+ g_value_set_string (value, self->self_id);
+ break;
+ case PROP_PEER_ID:
+ g_value_set_string (value, self->peer_id);
+ break;
+ case PROP_FILENAME:
+ g_value_set_string (value, self->filename);
+ break;
+ case PROP_DIRECTION:
+ g_value_set_enum (value, self->direction);
+ break;
+ case PROP_PORTER:
+ g_value_set_object (value, self->priv->porter);
+ break;
+ case PROP_CONTACT:
+ g_value_set_object (value, self->priv->contact);
+ break;
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, self->description);
+ break;
+ case PROP_CONTENT_TYPE:
+ g_value_set_string (value, self->content_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gchar *
+generate_id (void)
+{
+ static guint id_num = 0;
+
+ return g_strdup_printf ("gibber-file-transfer-%d", id_num++);
+}
+
+static gboolean received_stanza_cb (WockyPorter *porter,
+ WockyStanza *stanza, gpointer user_data);
+
+static void
+gibber_file_transfer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GibberFileTransfer *self = GIBBER_FILE_TRANSFER (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ self->id = g_value_dup_string (value);
+ if (self->id == NULL)
+ self->id = generate_id ();
+ break;
+ case PROP_SELF_ID:
+ self->self_id = g_value_dup_string (value);
+ if (self->self_id == NULL)
+ g_critical ("'self-id' cannot be NULL");
+ break;
+ case PROP_PEER_ID:
+ self->peer_id = g_value_dup_string (value);
+ if (self->peer_id == NULL)
+ g_critical ("'peer-id' cannot be NULL");
+ break;
+ case PROP_FILENAME:
+ self->filename = g_value_dup_string (value);
+ break;
+ case PROP_DIRECTION:
+ self->direction = g_value_get_enum (value);
+ break;
+ case PROP_PORTER:
+ {
+ self->priv->porter = g_value_dup_object (value);
+
+ self->priv->stanza_id =
+ wocky_porter_register_handler_from_anyone (self->priv->porter,
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_NONE,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, received_stanza_cb,
+ self, NULL);
+ }
+ break;
+ case PROP_CONTACT:
+ self->priv->contact = g_value_dup_object (value);
+ break;
+ case PROP_DESCRIPTION:
+ self->description = g_value_dup_string (value);
+ break;
+ case PROP_CONTENT_TYPE:
+ self->content_type = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void gibber_file_transfer_finalize (GObject *object);
+static void gibber_file_transfer_dispose (GObject *object);
+
+static void
+gibber_file_transfer_class_init (GibberFileTransferClass *gibber_file_transfer_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_file_transfer_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (gibber_file_transfer_class, sizeof (GibberFileTransferPrivate));
+
+ object_class->dispose = gibber_file_transfer_dispose;
+ object_class->finalize = gibber_file_transfer_finalize;
+
+ object_class->get_property = gibber_file_transfer_get_property;
+ object_class->set_property = gibber_file_transfer_set_property;
+
+ param_spec = g_param_spec_string ("id", "ID for the file transfer",
+ "The ID used tpo indentify the file transfer", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ID, param_spec);
+
+ param_spec = g_param_spec_string ("self-id",
+ "Self ID",
+ "The ID that identifies the local user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_SELF_ID, param_spec);
+
+ param_spec = g_param_spec_string ("peer-id",
+ "Peer ID",
+ "The ID that identifies the remote user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_PEER_ID, param_spec);
+
+ param_spec = g_param_spec_string ("filename",
+ "File name",
+ "The name of the transferred file", "new-file",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_FILENAME, param_spec);
+
+ param_spec = g_param_spec_enum ("direction",
+ "Direction", "File transfer direction",
+ GIBBER_TYPE_FILE_TRANSFER_DIRECTION,
+ GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_DIRECTION, param_spec);
+
+ param_spec = g_param_spec_object ("porter",
+ "WockyPorter object", "Wocky porter used to send stanzas",
+ WOCKY_TYPE_PORTER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_PORTER, param_spec);
+
+ param_spec = g_param_spec_object ("contact",
+ "WockyContact object", "Wocky Contact",
+ WOCKY_TYPE_CONTACT,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
+
+ param_spec = g_param_spec_string ("description",
+ "Description",
+ "The description of the transferred file", "",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_DESCRIPTION, param_spec);
+
+ param_spec = g_param_spec_string ("content-type",
+ "Content type",
+ "The content type of the transferred file", "application/octet-stream",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTENT_TYPE, param_spec);
+
+ signals[REMOTE_ACCEPTED] = g_signal_new ("remote-accepted",
+ G_OBJECT_CLASS_TYPE (gibber_file_transfer_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[FINISHED] = g_signal_new ("finished",
+ G_OBJECT_CLASS_TYPE (gibber_file_transfer_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[ERROR] = g_signal_new ("error",
+ G_OBJECT_CLASS_TYPE (gibber_file_transfer_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0, NULL, NULL,
+ _gibber_signals_marshal_VOID__UINT_INT_STRING,
+ G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING);
+
+ signals[TRANSFERRED_CHUNK] = g_signal_new ("transferred-chunk",
+ G_OBJECT_CLASS_TYPE (gibber_file_transfer_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0, NULL, NULL,
+ _gibber_signals_marshal_VOID__UINT64,
+ G_TYPE_NONE, 1, G_TYPE_UINT64);
+
+ signals[CANCELLED] = g_signal_new ("cancelled",
+ G_OBJECT_CLASS_TYPE (gibber_file_transfer_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+gibber_file_transfer_dispose (GObject *object)
+{
+ GibberFileTransfer *self = GIBBER_FILE_TRANSFER (object);
+
+ if (self->priv->porter != NULL)
+ {
+ wocky_porter_unregister_handler (self->priv->porter,
+ self->priv->stanza_id);
+ g_object_unref (self->priv->porter);
+ self->priv->porter = NULL;
+ }
+
+ if (self->priv->contact != NULL)
+ {
+ g_object_unref (self->priv->contact);
+ self->priv->contact = NULL;
+ }
+
+ if (self->dataforms)
+ {
+ g_list_foreach (self->dataforms, (GFunc) g_object_unref, NULL);
+ g_list_free (self->dataforms);
+ self->dataforms = NULL;
+ }
+
+ G_OBJECT_CLASS (gibber_file_transfer_parent_class)->dispose (object);
+}
+
+static void
+gibber_file_transfer_finalize (GObject *object)
+{
+ GibberFileTransfer *self = GIBBER_FILE_TRANSFER (object);
+
+ g_free (self->id);
+ g_free (self->self_id);
+ g_free (self->peer_id);
+ g_free (self->filename);
+ g_free (self->description);
+ g_free (self->content_type);
+
+ G_OBJECT_CLASS (gibber_file_transfer_parent_class)->finalize (object);
+}
+
+static gboolean
+received_stanza_cb (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ GibberFileTransfer *self = user_data;
+ const gchar *id;
+
+ id = wocky_node_get_attribute (wocky_stanza_get_top_node (stanza),
+ "id");
+ if (id != NULL && strcmp (id, self->id) == 0)
+ {
+ GIBBER_FILE_TRANSFER_GET_CLASS (self)->received_stanza (self, stanza);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gibber_file_transfer_is_file_offer (WockyStanza *stanza)
+{
+ /* FIXME put the known backends in a list and stop when the first one
+ * can handle the stanza */
+ return gibber_oob_file_transfer_is_file_offer (stanza);
+}
+
+GibberFileTransfer *
+gibber_file_transfer_new_from_stanza_with_from (
+ WockyStanza *stanza,
+ WockyPorter *porter,
+ WockyContact *contact,
+ const gchar *from,
+ GError **error)
+{
+ /* FIXME put the known backends in a list and stop when the first one
+ * can handle the stanza */
+ GibberFileTransfer *ft;
+
+ ft = gibber_oob_file_transfer_new_from_stanza_with_from (stanza, porter,
+ contact, from, error);
+ /* it's not possible to have an outgoing transfer created from
+ * a stanza */
+ g_assert (ft == NULL ||
+ ft->direction == GIBBER_FILE_TRANSFER_DIRECTION_INCOMING);
+
+ return ft;
+}
+
+GibberFileTransfer *
+gibber_file_transfer_new_from_stanza (WockyStanza *stanza,
+ WockyPorter *porter,
+ WockyContact *contact,
+ GError **error)
+{
+ const gchar *from;
+
+ from = wocky_node_get_attribute (wocky_stanza_get_top_node (stanza), "from");
+
+ return gibber_file_transfer_new_from_stanza_with_from (stanza, porter,
+ contact, from, error);
+}
+
+void
+gibber_file_transfer_offer (GibberFileTransfer *self)
+{
+ GibberFileTransferClass *cls = GIBBER_FILE_TRANSFER_GET_CLASS (self);
+
+ g_return_if_fail (self->direction ==
+ GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING);
+
+ cls->offer (self);
+}
+
+void
+gibber_file_transfer_send (GibberFileTransfer *self,
+ GIOChannel *src)
+{
+ GibberFileTransferClass *cls = GIBBER_FILE_TRANSFER_GET_CLASS (self);
+
+ g_return_if_fail (self->direction ==
+ GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING);
+
+ g_io_channel_set_buffered (src, FALSE);
+ cls->send (self, src);
+}
+
+void
+gibber_file_transfer_receive (GibberFileTransfer *self,
+ GIOChannel *dest)
+{
+ GibberFileTransferClass *cls = GIBBER_FILE_TRANSFER_GET_CLASS (self);
+
+ g_return_if_fail (self->direction ==
+ GIBBER_FILE_TRANSFER_DIRECTION_INCOMING);
+
+ g_io_channel_set_buffered (dest, FALSE);
+ cls->receive (self, dest);
+}
+
+void
+gibber_file_transfer_cancel (GibberFileTransfer *self,
+ guint error_code)
+{
+ GibberFileTransferClass *cls = GIBBER_FILE_TRANSFER_GET_CLASS (self);
+
+ cls->cancel (self, error_code);
+}
+
+void
+gibber_file_transfer_emit_error (GibberFileTransfer *self,
+ GError *error)
+{
+ DEBUG("File transfer error: %s", error->message);
+ g_signal_emit (self, signals[ERROR], 0, error->domain, error->code,
+ error->message);
+}
+
+void
+gibber_file_transfer_set_size (GibberFileTransfer *self,
+ guint64 size)
+{
+ self->priv->size = size;
+}
+
+guint64
+gibber_file_transfer_get_size (GibberFileTransfer *self)
+{
+ return self->priv->size;
+}
+
+gboolean
+gibber_file_transfer_send_stanza (GibberFileTransfer *self,
+ WockyStanza *stanza,
+ GError **error)
+{
+ if (wocky_stanza_get_to_contact (stanza) == NULL)
+ wocky_stanza_set_to_contact (stanza, self->priv->contact);
+
+ wocky_porter_send (self->priv->porter, stanza);
+
+ return TRUE;
+}
diff --git a/salut/lib/gibber/gibber-file-transfer.h b/salut/lib/gibber/gibber-file-transfer.h
new file mode 100644
index 000000000..1e479e6e0
--- /dev/null
+++ b/salut/lib/gibber/gibber-file-transfer.h
@@ -0,0 +1,135 @@
+/*
+ * gibber-file-transfer.h - Header for GibberFileTransfer
+ * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_FILE_TRANSFER_H__
+#define __GIBBER_FILE_TRANSFER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-porter.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ GIBBER_FILE_TRANSFER_DIRECTION_INCOMING,
+ GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING
+} GibberFileTransferDirection;
+
+typedef enum
+{
+ GIBBER_FILE_TRANSFER_ERROR_NOT_CONNECTED,
+ GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND,
+ GIBBER_FILE_TRANSFER_ERROR_NOT_ACCEPTABLE
+} GibberFileTransferError;
+
+#define GIBBER_FILE_TRANSFER_ERROR gibber_file_transfer_error_quark ()
+
+GQuark gibber_file_transfer_error_quark (void);
+
+typedef struct _GibberFileTransfer GibberFileTransfer;
+typedef struct _GibberFileTransferClass GibberFileTransferClass;
+
+struct _GibberFileTransferClass
+{
+ GObjectClass parent_class;
+
+ void (*offer) (GibberFileTransfer *ft);
+ void (*send) (GibberFileTransfer *ft,
+ GIOChannel *src);
+ void (*receive) (GibberFileTransfer *ft,
+ GIOChannel *dest);
+ void (*cancel) (GibberFileTransfer *ft,
+ guint error_code);
+ void (*received_stanza) (GibberFileTransfer *ft,
+ WockyStanza *stanza);
+};
+
+typedef struct _GibberFileTransferPrivate GibberFileTransferPrivate;
+
+struct _GibberFileTransfer
+{
+ GObject parent;
+
+ GibberFileTransferPrivate *priv;
+
+ /*< public >*/
+ gchar *id;
+
+ gchar *self_id;
+ gchar *peer_id;
+
+ gchar *filename;
+ gchar *description;
+ gchar *content_type;
+
+ GList *dataforms;
+
+ GibberFileTransferDirection direction;
+};
+
+GType gibber_file_transfer_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_FILE_TRANSFER \
+ (gibber_file_transfer_get_type ())
+#define GIBBER_FILE_TRANSFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIBBER_TYPE_FILE_TRANSFER, \
+ GibberFileTransfer))
+#define GIBBER_FILE_TRANSFER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GIBBER_TYPE_FILE_TRANSFER, \
+ GibberFileTransferClass))
+#define GIBBER_IS_FILE_TRANSFER (obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIBBER_TYPE_FILE_TRANSFER))
+#define GIBBER_IS_FILE_TRANSFER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GIBBER_TYPE_FILE_TRANSFER))
+#define GIBBER_FILE_TRANSFER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_FILE_TRANSFER, \
+ GibberFileTransferClass))
+
+
+gboolean gibber_file_transfer_is_file_offer (WockyStanza *stanza);
+
+GibberFileTransfer *gibber_file_transfer_new_from_stanza (
+ WockyStanza *stanza, WockyPorter *porter, WockyContact *contact,
+ GError **error);
+GibberFileTransfer *gibber_file_transfer_new_from_stanza_with_from (
+ WockyStanza *stanza, WockyPorter *porter, WockyContact *contact,
+ const gchar *from, GError **error);
+
+void gibber_file_transfer_offer (GibberFileTransfer *self);
+void gibber_file_transfer_send (GibberFileTransfer *self, GIOChannel *src);
+void gibber_file_transfer_receive (GibberFileTransfer *self, GIOChannel *dest);
+void gibber_file_transfer_cancel (GibberFileTransfer *self, guint error_code);
+
+/* these functions should only be used by backends */
+/* FIXME move to a private header if gibber becomes a public library */
+
+gboolean gibber_file_transfer_send_stanza (GibberFileTransfer *self,
+ WockyStanza *stanza, GError **error);
+
+void gibber_file_transfer_emit_error (GibberFileTransfer *self, GError *error);
+
+void gibber_file_transfer_set_size (GibberFileTransfer *self, guint64 size);
+guint64 gibber_file_transfer_get_size (GibberFileTransfer *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_FILE_TRANSFER_H__*/
diff --git a/salut/lib/gibber/gibber-linklocal-transport.c b/salut/lib/gibber/gibber-linklocal-transport.c
new file mode 100644
index 000000000..164af89c3
--- /dev/null
+++ b/salut/lib/gibber/gibber-linklocal-transport.c
@@ -0,0 +1,199 @@
+/*
+ * gibber-linklocal-transport.c - Source for GibberLLTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "gibber-sockets.h"
+#include "gibber-linklocal-transport.h"
+#include "gibber-util.h"
+
+#define DEBUG_FLAG DEBUG_NET
+#include "gibber-debug.h"
+
+/* Buffer size used for reading input */
+#define BUFSIZE 1024
+
+G_DEFINE_TYPE(GibberLLTransport, gibber_ll_transport, GIBBER_TYPE_FD_TRANSPORT)
+
+GQuark
+gibber_ll_transport_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string ("gibber_linklocal_transport_error");
+
+ return quark;
+}
+
+/* private structure */
+typedef struct _GibberLLTransportPrivate GibberLLTransportPrivate;
+
+struct _GibberLLTransportPrivate
+{
+ gboolean incoming;
+ gboolean dispose_has_run;
+};
+
+#define GIBBER_LL_TRANSPORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_LL_TRANSPORT, GibberLLTransportPrivate))
+
+static void gibber_ll_transport_finalize (GObject *object);
+
+static void
+gibber_ll_transport_init (GibberLLTransport *self)
+{
+ GibberLLTransportPrivate *priv = GIBBER_LL_TRANSPORT_GET_PRIVATE (self);
+ priv->incoming = FALSE;
+}
+
+static void gibber_ll_transport_dispose (GObject *object);
+static void
+gibber_ll_transport_class_init (GibberLLTransportClass *gibber_ll_transport_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_ll_transport_class);
+
+ g_type_class_add_private (gibber_ll_transport_class,
+ sizeof (GibberLLTransportPrivate));
+
+ object_class->dispose = gibber_ll_transport_dispose;
+ object_class->finalize = gibber_ll_transport_finalize;
+}
+
+void
+gibber_ll_transport_dispose (GObject *object)
+{
+ GibberLLTransport *self = GIBBER_LL_TRANSPORT (object);
+ GibberLLTransportPrivate *priv = GIBBER_LL_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (G_OBJECT_CLASS (gibber_ll_transport_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_ll_transport_parent_class)->dispose (object);
+}
+
+void
+gibber_ll_transport_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (gibber_ll_transport_parent_class)->finalize (object);
+}
+
+GibberLLTransport *
+gibber_ll_transport_new (void)
+{
+ return g_object_new (GIBBER_TYPE_LL_TRANSPORT, NULL);
+}
+
+void
+gibber_ll_transport_open_fd (GibberLLTransport *transport, int fd)
+{
+ GibberLLTransportPrivate *priv = GIBBER_LL_TRANSPORT_GET_PRIVATE (transport);
+
+ priv->incoming = TRUE;
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (transport),
+ GIBBER_TRANSPORT_CONNECTING);
+ gibber_fd_transport_set_fd (GIBBER_FD_TRANSPORT (transport), fd, TRUE);
+}
+
+gboolean
+gibber_ll_transport_open_sockaddr (GibberLLTransport *transport,
+ struct sockaddr_storage *addr, GError **error)
+{
+ GibberLLTransportPrivate *priv = GIBBER_LL_TRANSPORT_GET_PRIVATE (transport);
+ char host[NI_MAXHOST];
+ char port[NI_MAXSERV];
+ int fd;
+ int ret;
+
+ g_assert (!priv->incoming);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT(transport),
+ GIBBER_TRANSPORT_CONNECTING);
+
+ if (getnameinfo ((struct sockaddr *)addr, sizeof (struct sockaddr_storage),
+ host, NI_MAXHOST, port, NI_MAXSERV,
+ NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
+ DEBUG("Trying to connect to %s port %s", host, port);
+ } else {
+ DEBUG("Connecting..");
+ }
+
+ fd = socket (addr->ss_family, SOCK_STREAM, 0);
+ if (fd < 0)
+ {
+ g_set_error (error, GIBBER_LL_TRANSPORT_ERROR,
+ GIBBER_LL_TRANSPORT_ERROR_FAILED,
+ "Getting socket failed: %s", g_strerror (errno));
+ DEBUG("Getting socket failed: %s", strerror(errno));
+ goto failed;
+ }
+
+ ret = connect (fd, (struct sockaddr *)addr,
+ sizeof (struct sockaddr_storage));
+ if (ret < 0)
+ {
+ g_set_error (error, GIBBER_LL_TRANSPORT_ERROR,
+ GIBBER_LL_TRANSPORT_ERROR_CONNECT_FAILED,
+ "Connect failed: %s", g_strerror (errno));
+ DEBUG("Connecting failed: %s", strerror (errno));
+ goto failed;
+ }
+
+ gibber_fd_transport_set_fd (GIBBER_FD_TRANSPORT (transport), fd, TRUE);
+ return TRUE;
+
+failed:
+ gibber_transport_set_state (GIBBER_TRANSPORT (transport),
+ GIBBER_TRANSPORT_DISCONNECTED);
+ if (fd >= 0)
+ {
+ close (fd);
+ }
+ return FALSE;
+}
+
+gboolean
+gibber_ll_transport_is_incoming (GibberLLTransport *transport)
+{
+ GibberLLTransportPrivate *priv = GIBBER_LL_TRANSPORT_GET_PRIVATE (transport);
+ return priv->incoming;
+}
+
+void
+gibber_ll_transport_set_incoming (GibberLLTransport *transport,
+ gboolean incoming)
+{
+ GibberLLTransportPrivate *priv = GIBBER_LL_TRANSPORT_GET_PRIVATE (transport);
+ g_assert (
+ GIBBER_TRANSPORT (transport)->state == GIBBER_TRANSPORT_DISCONNECTED);
+ priv->incoming = incoming;
+}
diff --git a/salut/lib/gibber/gibber-linklocal-transport.h b/salut/lib/gibber/gibber-linklocal-transport.h
new file mode 100644
index 000000000..96ce20f8c
--- /dev/null
+++ b/salut/lib/gibber/gibber-linklocal-transport.h
@@ -0,0 +1,84 @@
+/*
+ * gibber-linklocal-transport.h - Header for GibberLLTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_LL_TRANSPORT_H__
+#define __GIBBER_LL_TRANSPORT_H__
+
+#include <glib-object.h>
+
+#include "gibber-fd-transport.h"
+
+G_BEGIN_DECLS
+
+GQuark gibber_ll_transport_error_quark (void);
+#define GIBBER_LL_TRANSPORT_ERROR gibber_ll_transport_error_quark()
+
+typedef enum
+{
+ GIBBER_LL_TRANSPORT_ERROR_CONNECT_FAILED,
+ GIBBER_LL_TRANSPORT_ERROR_FAILED,
+} GibberLLTransportError;
+
+typedef struct _GibberLLTransport GibberLLTransport;
+typedef struct _GibberLLTransportClass GibberLLTransportClass;
+
+
+struct _GibberLLTransportClass {
+ GibberFdTransportClass parent_class;
+};
+
+struct _GibberLLTransport {
+ GibberFdTransport parent;
+};
+
+GType gibber_ll_transport_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_LL_TRANSPORT \
+ (gibber_ll_transport_get_type ())
+#define GIBBER_LL_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_LL_TRANSPORT, \
+ GibberLLTransport))
+#define GIBBER_LL_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_LL_TRANSPORT, \
+ GibberLLTransportClass))
+#define GIBBER_IS_LL_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_LL_TRANSPORT))
+#define GIBBER_IS_LL_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_LL_TRANSPORT))
+#define GIBBER_LL_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_LL_TRANSPORT, \
+ GibberLLTransportClass))
+
+GibberLLTransport * gibber_ll_transport_new (void);
+
+void gibber_ll_transport_open_fd (GibberLLTransport *connection, int fd);
+
+gboolean gibber_ll_transport_open_sockaddr (GibberLLTransport *connection,
+ struct sockaddr_storage *addr, GError **error);
+
+gboolean gibber_ll_transport_is_incoming (GibberLLTransport *connection);
+
+void gibber_ll_transport_set_incoming (GibberLLTransport *connetion,
+ gboolean incoming);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_LL_TRANSPORT_H__*/
diff --git a/salut/lib/gibber/gibber-listener.c b/salut/lib/gibber/gibber-listener.c
new file mode 100644
index 000000000..40d4610d7
--- /dev/null
+++ b/salut/lib/gibber/gibber-listener.c
@@ -0,0 +1,540 @@
+/*
+ * gibber-listener.c - Source for GibberListener
+ * Copyright (C) 2007,2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "gibber-sockets.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <glib.h>
+
+#include "gibber-listener.h"
+#include "gibber-fd-transport.h"
+#include "gibber-unix-transport.h"
+#include "gibber-util.h"
+
+#define DEBUG_FLAG DEBUG_NET
+#include "gibber-debug.h"
+
+#include "gibber-signals-marshal.h"
+
+G_DEFINE_TYPE (GibberListener, gibber_listener, \
+ G_TYPE_OBJECT);
+
+/* signals */
+enum
+{
+ NEW_CONNECTION,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct {
+ GIOChannel *listener;
+ guint io_watch_in;
+} Listener;
+
+typedef struct _GibberListenerPrivate GibberListenerPrivate;
+
+struct _GibberListenerPrivate
+{
+ GSList *listeners;
+
+ /* Don't allow to listen again if it is already listening */
+ gboolean listening;
+ int port;
+
+ gboolean dispose_has_run;
+};
+
+#define GIBBER_LISTENER_GET_PRIVATE(obj) \
+ ((GibberListenerPrivate *) obj->priv)
+
+GQuark
+gibber_listener_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string (
+ "gibber_listener_error");
+
+ return quark;
+}
+
+static gboolean
+unimplemented (GError **error)
+{
+ g_set_error (error, GIBBER_LISTENER_ERROR, GIBBER_LISTENER_ERROR_FAILED,
+ "Unimplemented");
+
+ return FALSE;
+}
+
+static void
+gibber_listener_init (GibberListener *self)
+{
+ GibberListenerPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (self, GIBBER_TYPE_LISTENER,
+ GibberListenerPrivate);
+
+ self->priv = priv;
+
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+gibber_listeners_clean_listeners (GibberListener *self)
+{
+ GibberListenerPrivate *priv = GIBBER_LISTENER_GET_PRIVATE (self);
+ GSList *t;
+
+ for (t = priv->listeners ; t != NULL ; t = g_slist_delete_link (t, t))
+ {
+ Listener *l = (Listener *) t->data;
+
+ g_io_channel_unref (l->listener);
+ g_source_remove (l->io_watch_in);
+ g_slice_free (Listener, l);
+ }
+
+ priv->listeners = NULL;
+ priv->listening = FALSE;
+ priv->port = 0;
+}
+
+static void
+gibber_listener_dispose (GObject *object)
+{
+ GibberListener *self =
+ GIBBER_LISTENER (object);
+
+ gibber_listeners_clean_listeners (self);
+
+ G_OBJECT_CLASS (gibber_listener_parent_class)->dispose (
+ object);
+}
+
+static void
+gibber_listener_class_init (
+ GibberListenerClass *gibber_listener_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (
+ gibber_listener_class);
+
+ g_type_class_add_private (gibber_listener_class,
+ sizeof (GibberListenerPrivate));
+
+ object_class->dispose = gibber_listener_dispose;
+
+ signals[NEW_CONNECTION] =
+ g_signal_new (
+ "new-connection",
+ G_OBJECT_CLASS_TYPE (gibber_listener_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _gibber_signals_marshal_VOID__OBJECT_POINTER_UINT,
+ G_TYPE_NONE, 3, GIBBER_TYPE_TRANSPORT, G_TYPE_POINTER, G_TYPE_UINT);
+}
+
+GibberListener *
+gibber_listener_new (void)
+{
+ return g_object_new (GIBBER_TYPE_LISTENER,
+ NULL);
+}
+
+static gboolean
+listener_io_in_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ GibberListener *self = GIBBER_LISTENER (user_data);
+ GibberFdTransport *transport;
+ int fd, nfd;
+ int ret;
+ char host[NI_MAXHOST];
+ char port[NI_MAXSERV];
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof (struct sockaddr_storage);
+
+ fd = g_io_channel_unix_get_fd (source);
+ nfd = accept (fd, (struct sockaddr *) &addr, &addrlen);
+ gibber_normalize_address (&addr);
+
+#ifdef GIBBER_TYPE_UNIX_TRANSPORT
+ if (addr.ss_family == AF_UNIX)
+ {
+ transport = GIBBER_FD_TRANSPORT (gibber_unix_transport_new_from_fd (nfd));
+
+ /* UNIX sockets doesn't have port */
+ ret = getnameinfo ((struct sockaddr *) &addr, addrlen,
+ host, NI_MAXHOST, NULL, 0,
+ NI_NUMERICHOST);
+
+ port[0] = '\0';
+ }
+ else
+#endif
+ {
+ transport = g_object_new (GIBBER_TYPE_FD_TRANSPORT, NULL);
+ gibber_fd_transport_set_fd (transport, nfd, TRUE);
+
+ ret = getnameinfo ((struct sockaddr *) &addr, addrlen,
+ host, NI_MAXHOST, port, NI_MAXSERV,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ }
+
+ if (ret == 0)
+ {
+ if (port[0] != '\0')
+ DEBUG ("New connection from %s port %s", host, port);
+ else
+ DEBUG ("New connection from %s", host);
+ }
+ else
+ {
+ DEBUG("New connection...");
+ }
+
+ g_signal_emit (self, signals[NEW_CONNECTION], 0, transport, &addr,
+ (guint) addrlen);
+
+ g_object_unref (transport);
+ return TRUE;
+}
+
+static gboolean
+add_listener (GibberListener *self, int family, int type, int protocol,
+ struct sockaddr *address, socklen_t addrlen, GError **error)
+{
+ #define BACKLOG 5
+ int fd = -1, ret, yes = 1;
+ Listener *l;
+ GibberListenerPrivate *priv = GIBBER_LISTENER_GET_PRIVATE (self);
+ char name [NI_MAXHOST], portname[NI_MAXSERV];
+ union {
+ struct sockaddr addr;
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ struct sockaddr_storage storage;
+ } baddress;
+ socklen_t baddrlen = sizeof (baddress);
+
+ fd = socket (family, type, protocol);
+ if (fd == -1)
+ {
+ gibber_socket_set_error (error, "socket failed", GIBBER_LISTENER_ERROR,
+ gibber_socket_errno_is_eafnosupport () ?
+ GIBBER_LISTENER_ERROR_FAMILY_NOT_SUPPORTED :
+ GIBBER_LISTENER_ERROR_FAILED);
+ goto error;
+ }
+
+ ret = setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (int));
+ if (ret == -1)
+ {
+ gibber_socket_set_error (error, "setsockopt failed",
+ GIBBER_LISTENER_ERROR, GIBBER_LISTENER_ERROR_FAILED);
+ goto error;
+ }
+
+#ifdef IPV6_V6ONLY
+ if (family == AF_INET6)
+ {
+ ret = setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof (int));
+ if (ret == -1)
+ {
+ gibber_socket_set_error (error, "setsockopt failed",
+ GIBBER_LISTENER_ERROR, GIBBER_LISTENER_ERROR_FAILED);
+ goto error;
+ }
+ }
+#endif
+
+ ret = bind (fd, address, addrlen);
+ if (ret < 0)
+ {
+ gibber_socket_set_error (error, "bind failed",
+ GIBBER_LISTENER_ERROR,
+ gibber_socket_errno_is_eaddrinuse () ?
+ GIBBER_LISTENER_ERROR_ADDRESS_IN_USE :
+ GIBBER_LISTENER_ERROR_FAILED);
+ goto error;
+ }
+
+ ret = listen (fd, BACKLOG);
+ if (ret == -1)
+ {
+ gibber_socket_set_error (error, "listen failed",
+ GIBBER_LISTENER_ERROR,
+ gibber_socket_errno_is_eaddrinuse () ?
+ GIBBER_LISTENER_ERROR_ADDRESS_IN_USE :
+ GIBBER_LISTENER_ERROR_FAILED);
+ goto error;
+ }
+
+ ret = getsockname (fd, &baddress.addr, &baddrlen);
+ if (ret == -1)
+ {
+ gibber_socket_set_error (error, "getsockname failed",
+ GIBBER_LISTENER_ERROR, GIBBER_LISTENER_ERROR_FAILED);
+ goto error;
+ }
+
+ getnameinfo (&baddress.addr, baddrlen, name, sizeof (name),
+ portname, sizeof (portname), NI_NUMERICHOST | NI_NUMERICSERV);
+
+ DEBUG ( "Listening on %s port %s...", name, portname);
+
+ switch (family)
+ {
+ case AF_INET:
+ priv->port = g_ntohs (baddress.in.sin_port);
+ break;
+ case AF_INET6:
+ priv->port = g_ntohs (baddress.in6.sin6_port);
+ break;
+ default:
+ priv->port = 0;
+ break;
+ }
+
+ l = g_slice_new (Listener);
+
+ l->listener = gibber_io_channel_new_from_socket (fd);
+ g_io_channel_set_close_on_unref (l->listener, TRUE);
+ l->io_watch_in = g_io_add_watch (l->listener, G_IO_IN,
+ listener_io_in_cb, self);
+
+ priv->listeners = g_slist_append (priv->listeners, l);
+
+ return TRUE;
+
+error:
+ if (fd > 0)
+ close (fd);
+ return FALSE;
+}
+
+/* port: if 0, choose a random port
+ */
+static gboolean
+listen_tcp_af (GibberListener *listener, int port, GibberAddressFamily family,
+ gboolean loopback, GError **error)
+{
+ GibberListenerPrivate *priv = GIBBER_LISTENER_GET_PRIVATE (listener);
+ struct addrinfo req, *ans = NULL, *a;
+ int ret;
+ gchar sport[6];
+
+ if (priv->listening)
+ {
+ g_set_error (error, GIBBER_LISTENER_ERROR,
+ GIBBER_LISTENER_ERROR_ALREADY_LISTENING,
+ "GibberListener is already listening");
+ return FALSE;
+ }
+
+ memset (&req, 0, sizeof (req));
+ if (!loopback)
+ req.ai_flags = AI_PASSIVE;
+
+ switch (family)
+ {
+ case GIBBER_AF_IPV4:
+ req.ai_family = AF_INET;
+ break;
+ case GIBBER_AF_IPV6:
+ req.ai_family = AF_INET6;
+ break;
+ case GIBBER_AF_ANY:
+ req.ai_family = AF_UNSPEC;
+ break;
+ }
+ req.ai_socktype = SOCK_STREAM;
+ req.ai_protocol = IPPROTO_TCP;
+
+ g_snprintf (sport, 6, "%d", port);
+
+ ret = getaddrinfo (NULL, sport, &req, &ans);
+ if (ret != 0)
+ {
+ DEBUG ("getaddrinfo failed: %s", gai_strerror (ret));
+ g_set_error (error, GIBBER_LISTENER_ERROR,
+ GIBBER_LISTENER_ERROR_FAILED,
+ "%s", gai_strerror (ret));
+ goto error;
+ }
+
+ priv->port = 0;
+ for (a = ans ; a != NULL ; a = a->ai_next)
+ {
+ union {
+ struct sockaddr *addr;
+ struct sockaddr_storage *storage;
+ struct sockaddr_in *in;
+ struct sockaddr_in6 *in6;
+ } addr;
+ gboolean ret_;
+ GError *terror = NULL;
+
+ addr.addr = a->ai_addr;
+
+ /* the caller let us choose a port and we are not in the first round */
+ if (port == 0 && priv->port != 0)
+ {
+ if (a->ai_family == AF_INET)
+ addr.in->sin_port = g_htons (priv->port);
+ else if (a->ai_family == AF_INET6)
+ addr.in6->sin6_port = g_htons (priv->port);
+ else
+ g_assert_not_reached ();
+ }
+
+ ret_ = add_listener (listener, a->ai_family, a->ai_socktype,
+ a->ai_protocol, a->ai_addr, a->ai_addrlen, &terror);
+
+ if (ret_ == FALSE)
+ {
+ gboolean fatal = !g_error_matches (terror, GIBBER_LISTENER_ERROR,
+ GIBBER_LISTENER_ERROR_FAMILY_NOT_SUPPORTED);
+
+ /* let error always point to the last error */
+ g_clear_error (error);
+ g_propagate_error (error, terror);
+
+ if (fatal)
+ goto error;
+ }
+ else
+ {
+ /* add_listener succeeded: don't allow to listen again */
+ priv->listening = TRUE;
+ }
+ }
+
+ /* If all listeners failed, report the last error */
+ if (priv->listeners == NULL)
+ goto error;
+
+ /* There was an error at some point, but it was not fatal. ignore it */
+ g_clear_error (error);
+
+ freeaddrinfo (ans);
+
+ return TRUE;
+
+error:
+ gibber_listeners_clean_listeners (listener);
+ if (ans != NULL)
+ freeaddrinfo (ans);
+
+ return FALSE;
+}
+
+gboolean
+gibber_listener_listen_tcp (GibberListener *listener, int port, GError **error)
+{
+ return gibber_listener_listen_tcp_af (listener, port, GIBBER_AF_ANY, error);
+}
+
+gboolean
+gibber_listener_listen_tcp_af (GibberListener *listener, int port,
+ GibberAddressFamily family, GError **error)
+{
+ return listen_tcp_af (listener, port, family, FALSE, error);
+}
+
+gboolean
+gibber_listener_listen_tcp_loopback (GibberListener *listener,
+ int port, GError **error)
+{
+ return gibber_listener_listen_tcp_loopback_af (listener, port,
+ GIBBER_AF_ANY, error);
+}
+
+gboolean
+gibber_listener_listen_tcp_loopback_af (GibberListener *listener,
+ int port, GibberAddressFamily family, GError **error)
+{
+ return listen_tcp_af (listener, port, family, TRUE, error);
+}
+
+gboolean
+gibber_listener_listen_socket (GibberListener *listener,
+ gchar *path, gboolean abstract, GError **error)
+{
+ GibberListenerPrivate *priv = GIBBER_LISTENER_GET_PRIVATE (listener);
+#ifdef GIBBER_TYPE_UNIX_TRANSPORT
+ struct sockaddr_un addr;
+ int ret;
+#endif
+
+ if (priv->listening)
+ {
+ g_set_error (error, GIBBER_LISTENER_ERROR,
+ GIBBER_LISTENER_ERROR_ALREADY_LISTENING,
+ "GibberListener is already listening");
+ return FALSE;
+ }
+
+#ifdef GIBBER_TYPE_UNIX_TRANSPORT
+
+ if (abstract)
+ return unimplemented (error);
+
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = PF_UNIX;
+ snprintf (addr.sun_path, sizeof (addr.sun_path) - 1, "%s", path);
+
+ ret = add_listener (listener, AF_UNIX, SOCK_STREAM, 0,
+ (struct sockaddr *) &addr, sizeof (addr), error);
+
+ if (ret == TRUE)
+ {
+ /* add_listener succeeded: don't allow to listen again */
+ priv->listening = TRUE;
+ }
+
+ return ret;
+
+#else /* Unix transport not supported */
+ return unimplemented (error);
+#endif /* Unix transport not supported */
+}
+
+int
+gibber_listener_get_port (GibberListener *listener)
+{
+ GibberListenerPrivate *priv = GIBBER_LISTENER_GET_PRIVATE (listener);
+ return priv->port;
+}
diff --git a/salut/lib/gibber/gibber-listener.h b/salut/lib/gibber/gibber-listener.h
new file mode 100644
index 000000000..7ca150135
--- /dev/null
+++ b/salut/lib/gibber/gibber-listener.h
@@ -0,0 +1,99 @@
+/*
+ * gibber-listener.h - Header for GibberListener
+ * Copyright (C) 2007, 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _GIBBER_LISTENER_H_
+#define _GIBBER_LISTENER_H_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+GQuark gibber_listener_error_quark (void);
+#define GIBBER_LISTENER_ERROR \
+ gibber_listener_error_quark ()
+
+typedef enum
+{
+ GIBBER_LISTENER_ERROR_ALREADY_LISTENING,
+ GIBBER_LISTENER_ERROR_ADDRESS_IN_USE,
+ GIBBER_LISTENER_ERROR_FAMILY_NOT_SUPPORTED,
+ GIBBER_LISTENER_ERROR_FAILED,
+} GibberListenerError;
+
+typedef enum
+{
+ GIBBER_AF_IPV4,
+ GIBBER_AF_IPV6,
+ GIBBER_AF_ANY
+} GibberAddressFamily;
+
+typedef struct _GibberListener GibberListener;
+typedef struct _GibberListenerClass GibberListenerClass;
+
+struct _GibberListenerClass {
+ GObjectClass parent_class;
+};
+
+struct _GibberListener {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType gibber_listener_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_LISTENER \
+ (gibber_listener_get_type ())
+#define GIBBER_LISTENER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_LISTENER,\
+ GibberListener))
+#define GIBBER_LISTENER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_LISTENER,\
+ GibberListenerClass))
+#define GIBBER_IS_LISTENER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_LISTENER))
+#define GIBBER_IS_LISTENER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_LISTENER))
+#define GIBBER_LISTENER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_LISTENER,\
+ GibberListenerClass))
+
+GibberListener *gibber_listener_new (void);
+
+gboolean gibber_listener_listen_tcp (GibberListener *listener,
+ int port, GError **error);
+
+gboolean gibber_listener_listen_tcp_af (GibberListener *listener,
+ int port, GibberAddressFamily family, GError **error);
+
+gboolean gibber_listener_listen_tcp_loopback (GibberListener *listener,
+ int port, GError **error);
+
+gboolean gibber_listener_listen_tcp_loopback_af (GibberListener *listener,
+ int port, GibberAddressFamily family, GError **error);
+
+gboolean gibber_listener_listen_socket (GibberListener *listener,
+ gchar *path, gboolean abstract, GError **error);
+
+int gibber_listener_get_port (GibberListener *listener);
+
+G_END_DECLS
+
+#endif /* #ifndef _GIBBER_LISTENER_H_ */
diff --git a/salut/lib/gibber/gibber-muc-connection.c b/salut/lib/gibber/gibber-muc-connection.c
new file mode 100644
index 000000000..39b8a27f2
--- /dev/null
+++ b/salut/lib/gibber/gibber-muc-connection.c
@@ -0,0 +1,772 @@
+/*
+ * gibber-muc-connection.c - Source for GibberMucConnection
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "gibber-muc-connection.h"
+#include "gibber-signals-marshal.h"
+
+#include "gibber-sockets.h"
+#include "gibber-multicast-transport.h"
+#include "gibber-r-multicast-transport.h"
+#include "gibber-r-multicast-causal-transport.h"
+
+#include <wocky/wocky-xmpp-reader.h>
+#include <wocky/wocky-xmpp-writer.h>
+#include <wocky/wocky-namespaces.h>
+
+#define ADDRESS_KEY "address"
+#define PORT_KEY "port"
+
+#define DEBUG_FLAG DEBUG_MUC_CONNECTION
+#include "gibber-debug.h"
+
+static void _connection_received_data (GibberTransport *transport,
+ GibberBuffer *buffer, gpointer user_data);
+
+
+G_DEFINE_TYPE (GibberMucConnection, gibber_muc_connection, G_TYPE_OBJECT)
+
+/* signal enum */
+enum
+{
+ RECEIVED_STANZA,
+ RECEIVED_DATA,
+ PARSE_ERROR,
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED,
+ DISCONNECTING,
+ NEW_SENDERS,
+ LOST_SENDERS,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+typedef struct _GibberMucConnectionPrivate GibberMucConnectionPrivate;
+
+struct _GibberMucConnectionPrivate
+{
+ gboolean dispose_has_run;
+ gchar *name;
+ gchar *protocol;
+ gchar *address;
+ gchar *port;
+
+ WockyXmppReader *reader;
+ WockyXmppWriter *writer;
+
+ GHashTable *parameters;
+
+ GibberMulticastTransport *mtransport;
+ GibberRMulticastCausalTransport *rmctransport;
+ GibberRMulticastTransport *rmtransport;
+
+ GArray *streams_used;
+ guint16 last_stream_allocated;
+ gulong rmc_connected_handler;
+};
+
+#define GIBBER_MUC_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_MUC_CONNECTION, GibberMucConnectionPrivate))
+
+GQuark
+gibber_muc_connection_error_quark (void) {
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string ("gibber_muc_connection_error");
+
+ return quark;
+}
+
+
+static void
+gibber_muc_connection_init (GibberMucConnection *obj)
+{
+ GibberMucConnectionPrivate *priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (obj);
+ guint16 stream_id;
+
+ /* allocate any data required by the object here */
+ priv->reader = wocky_xmpp_reader_new_no_stream ();
+ priv->writer = wocky_xmpp_writer_new_no_stream ();
+
+ priv->streams_used = g_array_sized_new (FALSE, TRUE, sizeof (guint16), 1);
+ /* 0 is the "default" stream */
+ stream_id = 0;
+ g_array_append_val (priv->streams_used, stream_id);
+ priv->last_stream_allocated = 0;
+}
+
+static void gibber_muc_connection_dispose (GObject *object);
+static void gibber_muc_connection_finalize (GObject *object);
+
+static void
+gibber_muc_connection_class_init (
+ GibberMucConnectionClass *gibber_muc_connection_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_muc_connection_class);
+
+ g_type_class_add_private (gibber_muc_connection_class,
+ sizeof (GibberMucConnectionPrivate));
+
+ object_class->dispose = gibber_muc_connection_dispose;
+ object_class->finalize = gibber_muc_connection_finalize;
+
+ signals[RECEIVED_STANZA] = g_signal_new ("received-stanza",
+ G_OBJECT_CLASS_TYPE(gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _gibber_signals_marshal_VOID__STRING_OBJECT,
+ G_TYPE_NONE, 2, G_TYPE_STRING, WOCKY_TYPE_STANZA);
+ /* UINT: 16 bit stream id
+ * POINTER: guint8 * data buffer
+ * ULONG: data buffer size
+ */
+ signals[RECEIVED_DATA] = g_signal_new ("received-data",
+ G_OBJECT_CLASS_TYPE(gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _gibber_signals_marshal_VOID__STRING_UINT_POINTER_ULONG,
+ G_TYPE_NONE, 4, G_TYPE_STRING,
+ G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_ULONG);
+
+ signals[PARSE_ERROR] = g_signal_new ("parse-error",
+ G_OBJECT_CLASS_TYPE (gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ signals[DISCONNECTED] = g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CONNECTING] = g_signal_new ("connecting",
+ G_OBJECT_CLASS_TYPE (gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CONNECTED] = g_signal_new ("connected",
+ G_OBJECT_CLASS_TYPE (gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[DISCONNECTING] = g_signal_new ("disconnecting",
+ G_OBJECT_CLASS_TYPE (gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /* POINTER: GArray of gchar* containing the names of the new senders */
+ signals[NEW_SENDERS] = g_signal_new ("new-senders",
+ G_OBJECT_CLASS_TYPE(gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ /* POINTER: GArray of gchar* containing the names of the lost senders */
+ signals[LOST_SENDERS] = g_signal_new ("lost-senders",
+ G_OBJECT_CLASS_TYPE(gibber_muc_connection_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+void
+gibber_muc_connection_dispose (GObject *object)
+{
+ GibberMucConnection *self = GIBBER_MUC_CONNECTION (object);
+ GibberMucConnectionPrivate *priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ /* release any references held by the object here */
+ g_object_unref (priv->reader);
+ g_object_unref (priv->writer);
+ g_object_unref (priv->mtransport);
+ g_object_unref (priv->rmctransport);
+ g_object_unref (priv->rmtransport);
+
+ if (G_OBJECT_CLASS (gibber_muc_connection_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_muc_connection_parent_class)->dispose (object);
+}
+
+void
+gibber_muc_connection_finalize (GObject *object)
+{
+ GibberMucConnection *self = GIBBER_MUC_CONNECTION (object);
+ GibberMucConnectionPrivate *priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+ g_free (priv->name);
+ priv->name = NULL;
+
+ g_free (priv->protocol);
+ priv->protocol = NULL;
+
+ g_free (priv->address);
+ priv->address = NULL;
+
+ g_free (priv->port);
+ priv->port = NULL;
+
+ if (priv->parameters != NULL) {
+ g_hash_table_unref (priv->parameters);
+ priv->parameters = NULL;
+ }
+
+ g_array_unref (priv->streams_used);
+
+ G_OBJECT_CLASS (gibber_muc_connection_parent_class)->finalize (object);
+}
+
+const gchar **
+gibber_muc_connection_get_protocols (void)
+{
+ static const gchar *protocols[] = { WOCKY_TELEPATHY_NS_CLIQUE, NULL };
+ return protocols;
+}
+
+/* FIXME: we can probably get major simplification in this class
+ * if we declare that Clique is the only protocol it supports */
+const gchar **
+gibber_muc_connection_get_required_parameters (const gchar *protocol)
+{
+ int i;
+ static const gchar *parameters[] = { ADDRESS_KEY, PORT_KEY, NULL };
+ struct {
+ const gchar *protocol;
+ const gchar **parameters;
+ } protocols[] = { { WOCKY_TELEPATHY_NS_CLIQUE, parameters },
+ { NULL, NULL }
+ };
+
+ for (i = 0; protocols[i].protocol != NULL; i++) {
+ if (!strcmp (protocols[i].protocol, protocol)) {
+ return protocols[i].parameters;
+ }
+ }
+ return NULL;
+}
+
+static gboolean
+gibber_muc_connection_validate_address (const gchar *address,
+ const gchar *port, GError **error)
+{
+ int ret;
+
+ struct addrinfo hints;
+ struct addrinfo *ans;
+
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+
+ ret = getaddrinfo (address, port, &hints, &ans);
+ if (ret < 0)
+ {
+ DEBUG ("Getaddrinfo failed: %s", gai_strerror(ret));
+ g_set_error (error, GIBBER_MUC_CONNECTION_ERROR,
+ GIBBER_MUC_CONNECTION_ERROR_INVALID_ADDRESS,
+ "Getaddrinfo failed: %s", gai_strerror (ret));
+
+ goto err;
+ }
+
+ if (ans == NULL)
+ {
+ DEBUG ("Couldn't find address");
+ g_set_error (error, GIBBER_MUC_CONNECTION_ERROR,
+ GIBBER_MUC_CONNECTION_ERROR_INVALID_ADDRESS,
+ "Couldn't find address");
+ goto err;
+ }
+
+ if (ans->ai_next != NULL)
+ {
+ g_set_error (error, GIBBER_MUC_CONNECTION_ERROR,
+ GIBBER_MUC_CONNECTION_ERROR_INVALID_ADDRESS,
+ "Address isn't unique");
+ goto err;
+ }
+
+ freeaddrinfo (ans);
+
+ return TRUE;
+
+err:
+ if (ans != NULL)
+ {
+ freeaddrinfo (ans);
+ }
+
+ g_assert (error == NULL || *error != NULL);
+ return FALSE;
+}
+
+static void
+gibber_muc_connection_create_random_address (GibberMucConnection *self)
+{
+ GibberMucConnectionPrivate *priv =
+ GIBBER_MUC_CONNECTION_GET_PRIVATE (self);
+ gboolean ret;
+ int p;
+
+ g_free (priv->address);
+ g_free (priv->port);
+
+ /* Just pick any port above 1024 */
+ p = g_random_int_range (1024, G_MAXUINT16);
+ priv->port = g_strdup_printf ("%d", p);
+ /* RFC 2365 defines 239.255.0.0/16 as the IPv4 local scope (for multicast
+ * addresses). One /24 net was randomly picked out of this and is used for
+ * Clique muc groups */
+ priv->address =
+ g_strdup_printf ("239.255.71.%d", g_random_int_range (1, 254));
+
+ /* Just to be sure */
+ ret = gibber_muc_connection_validate_address (priv->address, priv->port,
+ NULL);
+
+ DEBUG ("Generated random address: %s:%s", priv->address, priv->port);
+
+ g_assert (ret);
+}
+
+GibberMucConnection *
+gibber_muc_connection_new (const gchar *name, const gchar *protocol,
+ GHashTable *parameters, GError **error)
+{
+ const gchar *address = NULL;
+ const gchar *port = NULL;
+ GibberMucConnection *result;
+ GibberMucConnectionPrivate *priv;
+
+ if (protocol != NULL && strcmp (protocol, WOCKY_TELEPATHY_NS_CLIQUE) != 0)
+ {
+ g_set_error (error, GIBBER_MUC_CONNECTION_ERROR,
+ GIBBER_MUC_CONNECTION_ERROR_INVALID_PROTOCOL,
+ "Invalid protocol: %s", protocol);
+ }
+
+ if (parameters != NULL)
+ {
+ address = g_hash_table_lookup (parameters, ADDRESS_KEY);
+ port = g_hash_table_lookup (parameters, PORT_KEY);
+ if (address == NULL || port == NULL)
+ {
+ g_set_error (error, GIBBER_MUC_CONNECTION_ERROR,
+ GIBBER_MUC_CONNECTION_ERROR_INVALID_PARAMETERS,
+ "Missing address or port parameter");
+ goto err;
+ }
+
+ if (!gibber_muc_connection_validate_address (address, port, error))
+ {
+ goto err;
+ }
+ }
+
+ /* Got an address, so we can init the transport */
+ result = g_object_new (GIBBER_TYPE_MUC_CONNECTION, NULL);
+ priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (result);
+
+ priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (result);
+ priv->name = g_strdup (name);
+ if (protocol != NULL)
+ {
+ priv->protocol = g_strdup (protocol);
+ }
+ else
+ {
+ priv->protocol = g_strdup (WOCKY_TELEPATHY_NS_CLIQUE);
+ }
+
+ priv->address = g_strdup (address);
+ priv->port = g_strdup (port);
+
+ priv->mtransport = gibber_multicast_transport_new ();
+ priv->rmctransport = gibber_r_multicast_causal_transport_new (
+ GIBBER_TRANSPORT (priv->mtransport), priv->name);
+ priv->rmtransport = gibber_r_multicast_transport_new (priv->rmctransport);
+
+ gibber_transport_set_handler (GIBBER_TRANSPORT (priv->rmtransport),
+ _connection_received_data, result);
+
+ return result;
+
+err:
+ g_assert (error == NULL || *error != NULL);
+ return NULL;
+}
+
+static void
+_rmtransport_new_senders_cb (GibberRMulticastTransport *transport,
+ gpointer new, gpointer user_data)
+{
+ g_signal_emit (GIBBER_MUC_CONNECTION (user_data), signals[NEW_SENDERS],
+ 0, new);
+}
+
+static void
+_rmtransport_lost_senders_cb (GibberRMulticastTransport *transport,
+ gpointer lost, gpointer user_data)
+{
+ g_signal_emit (GIBBER_MUC_CONNECTION (user_data), signals[LOST_SENDERS],
+ 0, lost);
+}
+
+static void
+_rmtransport_connected_cb (GibberRMulticastTransport *transport,
+ gpointer user_data)
+{
+ GibberMucConnection *connection = GIBBER_MUC_CONNECTION (user_data);
+
+ connection->state = GIBBER_MUC_CONNECTION_CONNECTED;
+ g_signal_emit (connection, signals[CONNECTED], 0);
+}
+
+static void
+_rmctransport_connected_cb (GibberRMulticastTransport *transport,
+ gpointer user_data)
+{
+ GibberMucConnection *connection = GIBBER_MUC_CONNECTION (user_data);
+ GibberMucConnectionPrivate *priv =
+ GIBBER_MUC_CONNECTION_GET_PRIVATE (connection);
+
+ if (!gibber_r_multicast_transport_connect (priv->rmtransport, NULL))
+ {
+ gibber_transport_disconnect (GIBBER_TRANSPORT (priv->rmctransport));
+ }
+
+ g_signal_handler_disconnect (transport, priv->rmc_connected_handler);
+}
+
+static void
+_transport_disconnected_cb (GibberRMulticastTransport *transport,
+ gpointer user_data)
+{
+ GibberMucConnection *connection = GIBBER_MUC_CONNECTION (user_data);
+
+ if (connection->state == GIBBER_MUC_CONNECTION_DISCONNECTED)
+ {
+ return;
+ }
+
+ connection->state = GIBBER_MUC_CONNECTION_DISCONNECTED;
+ g_signal_emit (connection, signals[DISCONNECTED], 0);
+}
+
+gboolean
+gibber_muc_connection_connect (GibberMucConnection *connection, GError **error)
+{
+ GibberMucConnectionPrivate *priv =
+ GIBBER_MUC_CONNECTION_GET_PRIVATE(connection);
+ int ret = FALSE;
+
+ if (connection->state > GIBBER_MUC_CONNECTION_DISCONNECTED)
+ {
+ return TRUE;
+ }
+
+ connection->state = GIBBER_MUC_CONNECTION_CONNECTING;
+ g_signal_emit (connection, signals[CONNECTING], 0);
+
+ g_signal_connect (priv->rmtransport, "connected",
+ G_CALLBACK (_rmtransport_connected_cb), connection);
+
+ priv->rmc_connected_handler = g_signal_connect (priv->rmctransport,
+ "connected", G_CALLBACK (_rmctransport_connected_cb), connection);
+
+ if (priv->address == NULL)
+ {
+ int attempts = 10;
+ do
+ {
+ gibber_muc_connection_create_random_address (connection);
+ if (gibber_multicast_transport_connect (priv->mtransport,
+ priv->address, priv->port))
+ {
+ if (gibber_r_multicast_causal_transport_connect (
+ priv->rmctransport, TRUE, NULL))
+ {
+ ret = TRUE;
+ }
+ break;
+ }
+ } while (--attempts);
+ }
+ else
+ {
+ if (gibber_multicast_transport_connect (priv->mtransport,
+ priv->address, priv->port))
+ {
+ if (gibber_r_multicast_causal_transport_connect (priv->rmctransport,
+ TRUE, NULL))
+ {
+ ret = TRUE;
+ }
+ }
+ }
+
+ if (!ret)
+ {
+ connection->state = GIBBER_MUC_CONNECTION_DISCONNECTED;
+ g_signal_emit (connection, signals[DISCONNECTED], 0);
+
+ if (gibber_transport_get_state (GIBBER_TRANSPORT (priv->mtransport)) !=
+ GIBBER_TRANSPORT_DISCONNECTED)
+ {
+ gibber_transport_disconnect (GIBBER_TRANSPORT (priv->mtransport));
+ }
+ g_set_error (error, GIBBER_MUC_CONNECTION_ERROR,
+ GIBBER_MUC_CONNECTION_ERROR_CONNECTION_FAILED,
+ "Failed to connect to multicast group");
+ }
+ else
+ {
+ g_signal_connect (priv->rmtransport, "disconnected",
+ G_CALLBACK (_transport_disconnected_cb), connection);
+ g_signal_connect (priv->rmtransport, "new-senders",
+ G_CALLBACK (_rmtransport_new_senders_cb), connection);
+ g_signal_connect (priv->rmtransport, "lost-senders",
+ G_CALLBACK (_rmtransport_lost_senders_cb), connection);
+ }
+
+ return ret;
+}
+
+void
+gibber_muc_connection_disconnect (GibberMucConnection *connection)
+{
+ GibberMucConnectionPrivate *priv =
+ GIBBER_MUC_CONNECTION_GET_PRIVATE (connection);
+
+ connection->state = GIBBER_MUC_CONNECTION_DISCONNECTING;
+ g_signal_emit (connection, signals[DISCONNECTING], 0);
+
+ gibber_transport_disconnect (GIBBER_TRANSPORT (priv->rmtransport));
+}
+
+const gchar *
+gibber_muc_connection_get_protocol (GibberMucConnection *connection)
+{
+ GibberMucConnectionPrivate *priv =
+ GIBBER_MUC_CONNECTION_GET_PRIVATE (connection);
+ return priv->protocol;
+}
+
+/* Current parameters of the transport. str -> str */
+const GHashTable *
+gibber_muc_connection_get_parameters (GibberMucConnection *connection)
+{
+ GibberMucConnectionPrivate *priv =
+ GIBBER_MUC_CONNECTION_GET_PRIVATE (connection);
+
+ g_assert (priv->mtransport != NULL &&
+ gibber_transport_get_state (
+ GIBBER_TRANSPORT (priv->mtransport)) ==
+ GIBBER_TRANSPORT_CONNECTED);
+
+ if (priv->parameters == NULL)
+ {
+ priv->parameters = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (priv->parameters, ADDRESS_KEY, priv->address);
+ g_hash_table_insert (priv->parameters, PORT_KEY, priv->port);
+ }
+
+ return priv->parameters;
+}
+
+static void
+_connection_received_data (GibberTransport *transport, GibberBuffer *buffer,
+ gpointer user_data)
+{
+ GibberMucConnection *self = GIBBER_MUC_CONNECTION (user_data);
+ GibberMucConnectionPrivate *priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (self);
+ GibberRMulticastBuffer *rmbuffer = (GibberRMulticastBuffer *) buffer;
+ WockyStanza *stanza;
+ GError *error = NULL;
+
+ g_assert (buffer->length > 0);
+
+ if (rmbuffer->stream_id != GIBBER_R_MULTICAST_CAUSAL_DEFAULT_STREAM)
+ {
+ g_signal_emit (self, signals[RECEIVED_DATA], 0,
+ rmbuffer->sender, (guint) rmbuffer->stream_id,
+ buffer->data, buffer->length);
+ return;
+ }
+
+ /* push the data into the reader */
+ wocky_xmpp_reader_push (priv->reader, buffer->data, buffer->length);
+
+ error = wocky_xmpp_reader_get_error (priv->reader);
+
+ if (error != NULL)
+ {
+ DEBUG ("reader error: %s", error->message);
+ g_signal_emit (self, signals[PARSE_ERROR], 0);
+ g_clear_error (&error);
+
+ wocky_xmpp_reader_reset (priv->reader);
+ }
+
+ /* now check if we got a stanza out of it */
+ stanza = wocky_xmpp_reader_pop_stanza (priv->reader);
+
+ if (stanza != NULL)
+ {
+ g_signal_emit (self, signals[RECEIVED_STANZA], 0,
+ rmbuffer->sender, stanza);
+ g_object_unref (stanza);
+
+ wocky_xmpp_reader_reset (priv->reader);
+ }
+}
+
+gboolean
+gibber_muc_connection_send (GibberMucConnection *connection,
+ WockyStanza *stanza, GError **error)
+{
+ GibberMucConnectionPrivate *priv =
+ GIBBER_MUC_CONNECTION_GET_PRIVATE (connection);
+ const guint8 *data;
+ gsize length;
+
+ wocky_xmpp_writer_write_stanza (priv->writer, stanza,
+ &data, &length);
+
+ return gibber_transport_send (GIBBER_TRANSPORT (priv->rmtransport),
+ data, length, error);
+}
+
+static gboolean
+stream_is_used (GibberMucConnection *self,
+ guint16 stream_id)
+{
+ GibberMucConnectionPrivate *priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (self);
+ guint i;
+
+ for (i = 0; i < priv->streams_used->len; i++)
+ {
+ guint16 tmp;
+
+ tmp = g_array_index (priv->streams_used, guint16, i);
+ if (tmp == stream_id)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+gibber_muc_connection_send_raw (GibberMucConnection *connection,
+ guint16 stream_id, const guint8 *data, gsize size, GError **error)
+{
+ GibberMucConnectionPrivate *priv =
+ GIBBER_MUC_CONNECTION_GET_PRIVATE (connection);
+
+ g_assert (stream_is_used (connection, stream_id));
+
+ return gibber_r_multicast_transport_send (priv->rmtransport,
+ stream_id, data, size, error);
+}
+
+guint16
+gibber_muc_connection_new_stream (GibberMucConnection *self)
+{
+ GibberMucConnectionPrivate *priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (self);
+ guint16 stream_id;
+
+ if (priv->streams_used->len >= G_MAXUINT16)
+ /* All streams are allocated */
+ return 0;
+
+ /* in pathological cases (nearly running out of streams) that function
+ * will be O(n**2) */
+ stream_id = priv->last_stream_allocated + 1;
+ while (stream_is_used (self, stream_id))
+ {
+ if (stream_id == G_MAXUINT16)
+ stream_id = 1;
+ else
+ stream_id++;
+ }
+
+ priv->last_stream_allocated = stream_id;
+ g_array_append_val (priv->streams_used, stream_id);
+
+ return stream_id;
+}
+
+void
+gibber_muc_connection_free_stream (GibberMucConnection *self,
+ guint16 stream_id)
+{
+ GibberMucConnectionPrivate *priv = GIBBER_MUC_CONNECTION_GET_PRIVATE (self);
+ guint i;
+
+ g_assert (stream_id != 0);
+
+ for (i = 0; i < priv->streams_used->len; i++)
+ {
+ guint16 tmp;
+
+ tmp = g_array_index (priv->streams_used, guint16, i);
+ if (tmp == stream_id)
+ {
+ g_array_remove_index_fast (priv->streams_used, i);
+ return;
+ }
+ }
+}
diff --git a/salut/lib/gibber/gibber-muc-connection.h b/salut/lib/gibber/gibber-muc-connection.h
new file mode 100644
index 000000000..0f724c84b
--- /dev/null
+++ b/salut/lib/gibber/gibber-muc-connection.h
@@ -0,0 +1,117 @@
+/*
+ * gibber-muc-connection.h - Header for GibberMucConnection
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_MUC_CONNECTION_H__
+#define __GIBBER_MUC_CONNECTION_H__
+
+#include <glib-object.h>
+
+#include "gibber-r-multicast-transport.h"
+#include <wocky/wocky-stanza.h>
+
+G_BEGIN_DECLS
+
+GQuark gibber_muc_connection_error_quark (void);
+#define GIBBER_MUC_CONNECTION_ERROR \
+ gibber_muc_connection_error_quark ()
+
+typedef enum
+{
+ GIBBER_MUC_CONNECTION_ERROR_INVALID_ADDRESS,
+ GIBBER_MUC_CONNECTION_ERROR_INVALID_PARAMETERS,
+ GIBBER_MUC_CONNECTION_ERROR_INVALID_PROTOCOL,
+ GIBBER_MUC_CONNECTION_ERROR_CONNECTION_FAILED,
+} GibberMucConnectionError;
+
+typedef enum
+{
+ GIBBER_MUC_CONNECTION_DISCONNECTED = 0,
+ GIBBER_MUC_CONNECTION_CONNECTING,
+ GIBBER_MUC_CONNECTION_CONNECTED,
+ GIBBER_MUC_CONNECTION_DISCONNECTING,
+} GibberMucConnectionState;
+
+
+typedef struct _GibberMucConnection GibberMucConnection;
+typedef struct _GibberMucConnectionClass GibberMucConnectionClass;
+
+struct _GibberMucConnectionClass {
+ GObjectClass parent_class;
+};
+
+struct _GibberMucConnection {
+ GObject parent;
+ GibberMucConnectionState state;
+};
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_MUC_CONNECTION \
+ (gibber_muc_connection_get_type ())
+#define GIBBER_MUC_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_MUC_CONNECTION, \
+ GibberMucConnection))
+#define GIBBER_MUC_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_MUC_CONNECTION, \
+ GibberMucConnectionClass))
+#define GIBBER_IS_MUC_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_MUC_CONNECTION))
+#define GIBBER_IS_MUC_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_MUC_CONNECTION))
+#define GIBBER_MUC_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_MUC_CONNECTION, \
+ GibberMucConnectionClass))
+
+const gchar ** gibber_muc_connection_get_protocols (void);
+
+const gchar ** gibber_muc_connection_get_required_parameters (
+ const gchar *protocol);
+
+GibberMucConnection * gibber_muc_connection_new (const gchar *name,
+ const gchar *protocol, GHashTable *parameters, GError **error);
+
+gboolean gibber_muc_connection_connect (GibberMucConnection *connection,
+ GError **error);
+
+void gibber_muc_connection_disconnect (GibberMucConnection *connection);
+
+const gchar * gibber_muc_connection_get_protocol (
+ GibberMucConnection *connection);
+
+/* Current parameters of the transport. str -> str */
+const GHashTable * gibber_muc_connection_get_parameters (
+ GibberMucConnection *connection);
+
+GType gibber_muc_connection_get_type (void);
+
+gboolean gibber_muc_connection_send (GibberMucConnection *connection,
+ WockyStanza *stanza, GError **error);
+
+gboolean
+gibber_muc_connection_send_raw (GibberMucConnection *connection,
+ guint16 stream_id, const guint8 *data, gsize size, GError **error);
+
+guint16 gibber_muc_connection_new_stream (GibberMucConnection *connection);
+
+void gibber_muc_connection_free_stream (GibberMucConnection *connection,
+ guint16 stream_id);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_MUC_CONNECTION_H__*/
diff --git a/salut/lib/gibber/gibber-multicast-transport.c b/salut/lib/gibber/gibber-multicast-transport.c
new file mode 100644
index 000000000..d593c44ce
--- /dev/null
+++ b/salut/lib/gibber/gibber-multicast-transport.c
@@ -0,0 +1,494 @@
+/*
+ * gibber-multicast-muc-transport.c - Source for GibberMulticastTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "gibber-sockets.h"
+
+#include <gibber-multicast-transport.h>
+
+#define DEBUG_FLAG DEBUG_NET
+#include <gibber-debug.h>
+
+#define BUFSIZE 1500
+#define MAX_PACKET_SIZE 1440
+
+static gboolean gibber_multicast_transport_send (GibberTransport *transport,
+ const guint8 *data, gsize size, GError **error);
+
+static void gibber_multicast_transport_disconnect (GibberTransport *transport);
+
+G_DEFINE_TYPE(GibberMulticastTransport, gibber_multicast_transport,
+ GIBBER_TYPE_TRANSPORT);
+
+/* Privates */
+typedef struct _GibberMulticastTransportPrivate
+ GibberMulticastTransportPrivate;
+
+struct _GibberMulticastTransportPrivate
+{
+ gboolean dispose_has_run;
+ GIOChannel *channel;
+ int fd;
+ guint watch_in;
+ guint watch_err;
+ struct sockaddr_storage address;
+ socklen_t addrlen;
+};
+
+#define GIBBER_MULTICAST_TRANSPORT_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_MULTICAST_TRANSPORT, \
+ GibberMulticastTransportPrivate))
+
+GQuark
+gibber_multicast_transport_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string ("gibber_multicast_transport_error");
+
+ return quark;
+}
+
+static void
+gibber_multicast_transport_init (GibberMulticastTransport *obj)
+{
+ GibberMulticastTransportPrivate *priv = GIBBER_MULTICAST_TRANSPORT_GET_PRIVATE (obj);
+
+ /* allocate any data required by the object here */
+ priv->fd = -1;
+ priv->watch_in = 0;
+ priv->watch_err = 0;
+ priv->channel = NULL;
+ GIBBER_TRANSPORT (obj)->max_packet_size = MAX_PACKET_SIZE;
+}
+
+static void gibber_multicast_transport_dispose (GObject *object);
+static void gibber_multicast_transport_finalize (GObject *object);
+
+static void
+gibber_multicast_transport_class_init (
+ GibberMulticastTransportClass *gibber_multicast_transport_class)
+{
+ GObjectClass *object_class =
+ G_OBJECT_CLASS (gibber_multicast_transport_class);
+ GibberTransportClass *transport_class =
+ GIBBER_TRANSPORT_CLASS(gibber_multicast_transport_class);
+
+
+ g_type_class_add_private (gibber_multicast_transport_class,
+ sizeof (GibberMulticastTransportPrivate));
+
+ object_class->dispose = gibber_multicast_transport_dispose;
+ object_class->finalize = gibber_multicast_transport_finalize;
+
+ transport_class->send = gibber_multicast_transport_send;
+ transport_class->disconnect = gibber_multicast_transport_disconnect;
+}
+
+void
+gibber_multicast_transport_dispose (GObject *object)
+{
+ GibberMulticastTransport *self = GIBBER_MULTICAST_TRANSPORT (object);
+ GibberMulticastTransportPrivate *priv =
+ GIBBER_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+ if (priv->channel)
+ {
+ gibber_multicast_transport_disconnect (GIBBER_TRANSPORT(self));
+ }
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (gibber_multicast_transport_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_multicast_transport_parent_class)->dispose (object);
+}
+
+void
+gibber_multicast_transport_finalize (GObject *object)
+{
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (gibber_multicast_transport_parent_class)->finalize (object);
+}
+
+static gboolean
+_channel_io_in (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ GibberMulticastTransport *self =
+ GIBBER_MULTICAST_TRANSPORT (data);
+ GibberMulticastTransportPrivate *priv =
+ GIBBER_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ guint8 buf[BUFSIZE + 1];
+
+ struct sockaddr_storage from;
+ int ret;
+ socklen_t len = sizeof (struct sockaddr_storage);
+
+ ret = recvfrom (priv->fd, buf, BUFSIZE, 0, (struct sockaddr *)&from, &len);
+
+ if (ret < 0)
+ {
+ DEBUG("recv failed: %s", strerror(errno));
+ /* FIXME should throw error */
+ return TRUE;
+ }
+
+ buf[ret] = '\0';
+ DEBUG ("Received %d bytes", ret);
+
+ gibber_transport_received_data (GIBBER_TRANSPORT (self), buf, ret);
+
+ return TRUE;
+}
+
+static gboolean
+_channel_io_err (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ /* Either _HUP or _ERR */
+ /* Should disconnect */
+ DEBUG ("ERR");
+ return TRUE;
+}
+
+static int
+_open_multicast (GibberMulticastTransport *self, GError **error)
+{
+ GibberMulticastTransportPrivate *priv =
+ GIBBER_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ int yes = 1;
+ unsigned char one = 1;
+ int fd = -1;
+
+ g_assert (self != NULL);
+#define SETSOCKOPT(s, level, optname, optval, len) G_STMT_START { \
+ if (setsockopt (s, level, optname, optval, len) != 0) \
+ { \
+ g_set_error (error, GIBBER_MULTICAST_TRANSPORT_ERROR, \
+ GIBBER_MULTICAST_TRANSPORT_ERROR_JOIN_FAILED, \
+ #optname " failed: %s", strerror(errno)); \
+ goto err; \
+ } \
+} G_STMT_END
+
+ /* Only try the first! */
+ switch (priv->address.ss_family)
+ {
+ case AF_INET:
+ {
+ struct ip_mreq mreq;
+ struct sockaddr_in baddr;
+ fd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ if (fd < 0)
+ {
+ g_set_error (error, GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_JOIN_FAILED,
+ "Failed to open the socket: %s", strerror (errno));
+ DEBUG("Failed to open socket: %s", strerror (errno));
+ goto err;
+ }
+
+ SETSOCKOPT (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (yes));
+#ifdef SO_REUSEPORT
+ SETSOCKOPT (fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof (yes));
+#endif
+ SETSOCKOPT (fd, IPPROTO_IP, IP_MULTICAST_LOOP, &yes, sizeof (yes));
+ SETSOCKOPT (fd, IPPROTO_IP, IP_MULTICAST_TTL, &one, sizeof (one));
+
+ mreq.imr_multiaddr =
+ ((struct sockaddr_in *) &(priv->address))->sin_addr;
+ mreq.imr_interface.s_addr = htonl (INADDR_ANY);
+
+ SETSOCKOPT (fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof (mreq));
+
+ memset (&baddr, 0, sizeof (baddr));
+ baddr.sin_family = AF_INET;
+ baddr.sin_addr.s_addr = htonl (INADDR_ANY);
+ baddr.sin_port = ((struct sockaddr_in *)&(priv->address))->sin_port;
+
+ if (bind (fd, (struct sockaddr *)&baddr, sizeof (baddr)) != 0)
+ {
+ DEBUG("Failed to bind to socket: %s", strerror (errno));
+ g_set_error (error, GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_JOIN_FAILED,
+ "Failed to bind to socket: %s", strerror (errno));
+ goto err;
+ }
+ break;
+ }
+ case AF_INET6:
+ {
+ struct ipv6_mreq mreq6;
+ fd = socket (AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+
+ if (fd < 0)
+ {
+ g_set_error (error, GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_JOIN_FAILED,
+ "Failed to open the socket: %s", strerror (errno));
+ goto err;
+ }
+
+ mreq6.ipv6mr_multiaddr =
+ ((struct sockaddr_in6 *)&priv->address)->sin6_addr;
+ mreq6.ipv6mr_interface = 0;
+
+ SETSOCKOPT (fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6,
+ sizeof (mreq6));
+
+ if (bind (fd, (struct sockaddr *)&(priv->address), priv->addrlen)
+ != 0)
+ {
+ DEBUG("Failed to bind to socket: %s", strerror(errno));
+ g_set_error (error, GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_JOIN_FAILED,
+ "Failed to bind to socket: %s", strerror (errno));
+ goto err;
+ }
+ break;
+ }
+ default:
+ DEBUG ("Address from an unsupported address family: %d",
+ priv->address.ss_family);
+ g_set_error (error, GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_JOIN_FAILED,
+ "Unknown address family");
+
+ }
+
+ return fd;
+err:
+ if (fd > 0)
+ {
+ close (fd);
+ }
+ return -1;
+#undef SETSOCKOPT
+}
+
+static gboolean
+gibber_multicast_transport_validate_address (const gchar *address,
+ const gchar *port, struct sockaddr_storage *sock_addr, socklen_t *socklen,
+ GError **error)
+{
+ int ret;
+
+ struct addrinfo hints;
+ struct addrinfo *ans;
+
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+
+ ret = getaddrinfo (address, port, &hints, &ans);
+ if (ret < 0)
+ {
+ DEBUG("Getaddrinfo failed: %s", gai_strerror(ret));
+ g_set_error (error,
+ GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_INVALID_ADDRESS,
+ "Getaddrinfo failed: %s", gai_strerror(ret));
+
+ goto err;
+ }
+
+ if (ans == NULL)
+ {
+ DEBUG("Couldn't find address");
+ g_set_error (error,
+ GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_INVALID_ADDRESS,
+ "Couldn't find address");
+ goto err;
+ }
+
+ if (ans->ai_next != NULL)
+ {
+ g_set_error (error,
+ GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_INVALID_ADDRESS,
+ "Address isn't unique");
+ goto err;
+ }
+
+ memcpy (sock_addr, ans->ai_addr, ans->ai_addrlen);
+ *socklen = ans->ai_addrlen;
+
+ freeaddrinfo (ans);
+
+ return TRUE;
+
+err:
+ if (ans != NULL)
+ {
+ freeaddrinfo (ans);
+ }
+
+ g_assert (error == NULL || *error != NULL);
+ return FALSE;
+}
+
+gboolean
+gibber_multicast_transport_connect (GibberMulticastTransport *mtransport,
+ const gchar *address, const gchar *port)
+{
+ GibberMulticastTransportPrivate *priv =
+ GIBBER_MULTICAST_TRANSPORT_GET_PRIVATE (mtransport);
+ GError *error = NULL;
+ int fd = -1;
+
+ gibber_transport_set_state (GIBBER_TRANSPORT(mtransport),
+ GIBBER_TRANSPORT_CONNECTING);
+ if (!gibber_multicast_transport_validate_address (address, port,
+ &(priv->address), &(priv->addrlen), &error))
+ {
+ goto failed;
+ }
+
+ /* Address already set, must use this one */
+ fd = _open_multicast (mtransport, &error);
+
+ if (fd < 0 )
+ {
+ goto failed;
+ }
+
+ g_assert (priv->channel == NULL);
+
+ priv->fd = fd;
+
+ priv->channel = g_io_channel_unix_new (fd);
+
+ g_io_channel_set_close_on_unref (priv->channel, TRUE);
+ g_io_channel_set_encoding (priv->channel, NULL, NULL);
+ g_io_channel_set_buffered (priv->channel, FALSE);
+
+ priv->watch_in =
+ g_io_add_watch (priv->channel, G_IO_IN, _channel_io_in, mtransport);
+ priv->watch_err =
+ g_io_add_watch (priv->channel, G_IO_ERR|G_IO_HUP, _channel_io_err,
+ mtransport);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT(mtransport),
+ GIBBER_TRANSPORT_CONNECTED);
+
+ return TRUE;
+
+failed:
+ g_assert (error != NULL);
+ gibber_transport_emit_error (GIBBER_TRANSPORT(mtransport), error);
+
+ g_error_free (error);
+ gibber_transport_set_state (GIBBER_TRANSPORT (mtransport),
+ GIBBER_TRANSPORT_DISCONNECTED);
+
+ return FALSE;
+}
+
+void
+gibber_multicast_transport_disconnect (GibberTransport *transport)
+{
+ GibberMulticastTransport *self =
+ GIBBER_MULTICAST_TRANSPORT (transport);
+ GibberMulticastTransportPrivate *priv =
+ GIBBER_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ /* Ensure we're connected */
+ g_assert (priv->fd >= 0);
+
+ if (priv->watch_in)
+ {
+ g_source_remove (priv->watch_in);
+ priv->watch_in = 0;
+ }
+
+ if (priv->watch_err)
+ {
+ g_source_remove (priv->watch_err);
+ priv->watch_err = 0;
+ }
+
+ if (priv->channel)
+ {
+ g_io_channel_shutdown (priv->channel, TRUE, NULL);
+ g_io_channel_unref (priv->channel);
+ priv->channel = NULL;
+ }
+
+ priv->fd = -1;
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (self),
+ GIBBER_TRANSPORT_DISCONNECTED);
+}
+
+static gboolean
+gibber_multicast_transport_send (GibberTransport *transport,
+ const guint8 *data, gsize size, GError **error)
+{
+ GibberMulticastTransport *self =
+ GIBBER_MULTICAST_TRANSPORT (transport);
+ GibberMulticastTransportPrivate *priv =
+ GIBBER_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ if (size > MAX_PACKET_SIZE)
+ {
+ DEBUG ("Message too big");
+ *error = g_error_new (GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_MESSAGE_TOO_BIG,
+ "Message too big");
+ return FALSE;
+ }
+
+ if (sendto (priv->fd, data, size, 0,
+ (struct sockaddr *)&(priv->address),
+ sizeof (struct sockaddr_storage)) < 0)
+ {
+ DEBUG("send failed: %s", strerror (errno));
+ if (error != NULL)
+ {
+ *error = g_error_new (GIBBER_MULTICAST_TRANSPORT_ERROR,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_NETWORK,
+ "Network error: %s", strerror (errno));
+ }
+ }
+
+ return TRUE;
+}
+
+GibberMulticastTransport *
+gibber_multicast_transport_new (void)
+{
+ GibberMulticastTransport *transport;
+
+ transport = g_object_new (GIBBER_TYPE_MULTICAST_TRANSPORT, NULL);
+
+ return transport;
+}
+
diff --git a/salut/lib/gibber/gibber-multicast-transport.h b/salut/lib/gibber/gibber-multicast-transport.h
new file mode 100644
index 000000000..6877b281b
--- /dev/null
+++ b/salut/lib/gibber/gibber-multicast-transport.h
@@ -0,0 +1,84 @@
+/*
+ * gibber-multicast-muc-transport.h - Header for GibberMulticastTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_MULTICAST_TRANSPORT_H__
+#define __GIBBER_MULTICAST_TRANSPORT_H__
+
+#include <glib-object.h>
+#include "gibber-transport.h"
+
+G_BEGIN_DECLS
+
+GQuark gibber_multicast_transport_error_quark (void);
+#define GIBBER_MULTICAST_TRANSPORT_ERROR \
+ gibber_multicast_transport_error_quark ()
+
+typedef enum
+{
+ GIBBER_MULTICAST_TRANSPORT_ERROR_JOIN_FAILED,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_INVALID_ADDRESS,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_MESSAGE_TOO_BIG,
+ GIBBER_MULTICAST_TRANSPORT_ERROR_NETWORK,
+} GibberMulticastTransportError;
+
+
+typedef struct _GibberMulticastTransport GibberMulticastTransport;
+typedef struct _GibberMulticastTransportClass GibberMulticastTransportClass;
+
+struct _GibberMulticastTransportClass {
+ GibberTransportClass parent_class;
+};
+
+struct _GibberMulticastTransport {
+ GibberTransport parent;
+};
+
+GibberMulticastTransport * gibber_multicast_transport_new (void);
+
+gboolean gibber_multicast_transport_connect (
+ GibberMulticastTransport *mtransport, const gchar *address,
+ const gchar *port);
+
+gsize
+gibber_multicast_transport_get_max_packet_size (
+ GibberMulticastTransport *mtransport);
+
+GType gibber_multicast_transport_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_MULTICAST_TRANSPORT \
+ (gibber_multicast_transport_get_type ())
+#define GIBBER_MULTICAST_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_MULTICAST_TRANSPORT, \
+ GibberMulticastTransport))
+#define GIBBER_MULTICAST_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_MULTICAST_TRANSPORT, \
+ GibberMulticastTransportClass))
+#define GIBBER_IS_MULTICAST_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_MULTICAST_TRANSPORT))
+#define GIBBER_IS_MULTICAST_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_MULTICAST_TRANSPORT))
+#define GIBBER_MULTICAST_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_MULTICAST_TRANSPORT, \
+ GibberMulticastTransportClass))
+
+
+G_END_DECLS
+#endif /* #ifndef __GIBBER_MULTICAST_TRANSPORT_H__*/
diff --git a/salut/lib/gibber/gibber-oob-file-transfer.c b/salut/lib/gibber/gibber-oob-file-transfer.c
new file mode 100644
index 000000000..0f723bde9
--- /dev/null
+++ b/salut/lib/gibber/gibber-oob-file-transfer.c
@@ -0,0 +1,1056 @@
+/*
+ * gibber-oob-file-transfer.c - Source for GibberOobFileTransfer
+ * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libsoup/soup.h>
+#include <libsoup/soup-server.h>
+#include <libsoup/soup-message.h>
+
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-meta-porter.h>
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-data-form.h>
+
+#include "gibber-oob-file-transfer.h"
+#include "gibber-fd-transport.h"
+#include "gibber-util.h"
+
+#define DEBUG_FLAG DEBUG_FILE_TRANSFER
+#include "gibber-debug.h"
+
+enum {
+ HTTP_STATUS_CODE_OK = 200,
+ HTTP_STATUS_CODE_NOT_FOUND = 404,
+ HTTP_STATUS_CODE_NOT_ACCEPTABLE = 406
+};
+
+G_DEFINE_TYPE(GibberOobFileTransfer, gibber_oob_file_transfer,
+ GIBBER_TYPE_FILE_TRANSFER)
+
+/* private structure */
+struct _GibberOobFileTransferPrivate
+{
+ /* HTTP server used to send files (only when sending files) */
+ SoupServer *server;
+ /* object used to send file chunks (when sending files) or to
+ * get the file (when receiving file) */
+ SoupMessage *msg;
+ /* The unescaped served path passed to libsoup, i.e.
+ * "/salut-ft-12/hello world" (only when sending files) */
+ gchar *served_name;
+ /* The full escaped URL, such as
+ * "http://192.168.1.2/salut-ft-12/hello%20world" */
+ gchar *url;
+ /* Input/output channel */
+ GIOChannel *channel;
+ /* Current number of transferred bytes */
+ guint64 transferred_bytes;
+ /* whether the transfer has been cancelled */
+ gboolean cancelled;
+ /* the watch id on the channel */
+ guint watch_id;
+ /* session used to receive the file */
+ SoupSession *session;
+};
+
+static void
+gibber_oob_file_transfer_init (GibberOobFileTransfer *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ GIBBER_TYPE_OOB_FILE_TRANSFER, GibberOobFileTransferPrivate);
+}
+
+static void gibber_oob_file_transfer_finalize (GObject *object);
+static void gibber_oob_file_transfer_offer (GibberFileTransfer *ft);
+static void gibber_oob_file_transfer_send (GibberFileTransfer *ft,
+ GIOChannel *src);
+static void gibber_oob_file_transfer_receive (GibberFileTransfer *ft,
+ GIOChannel *dest);
+static void gibber_oob_file_transfer_cancel (GibberFileTransfer *ft,
+ guint error_code);
+static void gibber_oob_file_transfer_received_stanza (GibberFileTransfer *ft,
+ WockyStanza *stanza);
+
+static void
+gibber_oob_file_transfer_class_init (
+ GibberOobFileTransferClass *gibber_oob_file_transfer_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_oob_file_transfer_class);
+ GibberFileTransferClass *ft_class =
+ GIBBER_FILE_TRANSFER_CLASS(gibber_oob_file_transfer_class);
+
+ g_type_class_add_private (gibber_oob_file_transfer_class,
+ sizeof (GibberOobFileTransferPrivate));
+
+ object_class->finalize = gibber_oob_file_transfer_finalize;
+
+ ft_class->offer = gibber_oob_file_transfer_offer;
+ ft_class->send = gibber_oob_file_transfer_send;
+ ft_class->receive = gibber_oob_file_transfer_receive;
+ ft_class->cancel = gibber_oob_file_transfer_cancel;
+ ft_class->received_stanza = gibber_oob_file_transfer_received_stanza;
+}
+
+static void
+gibber_oob_file_transfer_finalize (GObject *object)
+{
+ GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (object);
+
+ if (self->priv->watch_id != 0)
+ g_source_remove (self->priv->watch_id);
+
+ if (self->priv->server != NULL)
+ {
+ soup_server_quit (self->priv->server);
+ g_object_unref (G_OBJECT (self->priv->server));
+ }
+
+ if (self->priv->session != NULL)
+ g_object_unref (self->priv->session);
+
+ if (self->priv->channel != NULL)
+ g_io_channel_unref (self->priv->channel);
+
+ g_free (self->priv->served_name);
+ g_free (self->priv->url);
+
+ G_OBJECT_CLASS (gibber_oob_file_transfer_parent_class)->finalize (object);
+}
+
+gboolean
+gibber_oob_file_transfer_is_file_offer (WockyStanza *stanza)
+{
+ WockyStanzaType type;
+ WockyStanzaSubType sub_type;
+ WockyNode *query;
+ WockyNode *url;
+ const gchar *url_content;
+
+ wocky_stanza_get_type_info (stanza, &type, &sub_type);
+ if (type != WOCKY_STANZA_TYPE_IQ ||
+ sub_type != WOCKY_STANZA_SUB_TYPE_SET)
+ {
+ return FALSE;
+ }
+
+ query = wocky_node_get_child (wocky_stanza_get_top_node (stanza),
+ "query");
+ if (query == NULL)
+ return FALSE;
+
+ url = wocky_node_get_child (query, "url");
+ if (url == NULL)
+ return FALSE;
+
+ url_content = url->content;
+ if (url_content == NULL || strcmp (url_content, "") == 0)
+ return FALSE;
+
+ if (url_content[0] == '\n')
+ /* iChat prefixes url with '\n' */
+ url_content++;
+
+ /* We only support file transfer over HTTP */
+ if (!g_str_has_prefix (url_content, "http://"))
+ return FALSE;
+
+ return TRUE;
+}
+
+static GList *
+extract_dataforms (WockyNode *file)
+{
+ GList *forms = NULL;
+ WockyNodeIter iter;
+ WockyNode *x;
+
+ wocky_node_iter_init (&iter, file, "x", WOCKY_XMPP_NS_DATA);
+ while (wocky_node_iter_next (&iter, &x))
+ {
+ GError *error = NULL;
+ WockyDataForm *form = wocky_data_form_new_from_node (x, &error);
+
+ if (form == NULL)
+ {
+ DEBUG ("Failed to parse data form: %s", error->message);
+ g_clear_error (&error);
+ continue;
+ }
+
+ forms = g_list_append (forms, form);
+ }
+
+ return forms;
+}
+
+GibberFileTransfer *
+gibber_oob_file_transfer_new_from_stanza_with_from (
+ WockyStanza *stanza,
+ WockyPorter *porter,
+ WockyContact *contact,
+ const gchar *peer_id,
+ GError **error)
+{
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+ GibberOobFileTransfer *self;
+ WockyNode *query;
+ WockyNode *url_node;
+ WockyNode *desc_node;
+ const gchar *self_id;
+ const gchar *type;
+ const gchar *id;
+ const gchar *size;
+ const gchar *description = NULL;
+ const gchar *content_type;
+ const gchar *ft_type;
+ gchar *url;
+ gchar *filename;
+
+ g_return_val_if_fail (WOCKY_IS_PORTER (porter), NULL);
+
+ if (strcmp (node->name, "iq") != 0)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "Not an IQ: %s", node->name);
+ return NULL;
+ }
+
+ self_id = wocky_node_get_attribute (node, "to");
+
+ if (peer_id == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "No 'from' attribute");
+ return NULL;
+ }
+
+ if (self_id == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "No 'to' attribute");
+ return NULL;
+ }
+
+ type = wocky_node_get_attribute (node, "type");
+
+ if (type == NULL || strcmp (type, "set") != 0)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "type != 'set': '%s'", (type == NULL ? "(null)" : type));
+ return NULL;
+ }
+
+ id = wocky_node_get_attribute (node, "id");
+
+ if (id == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "no 'id' attribute");
+ return NULL;
+ }
+
+ query = wocky_node_get_child (node, "query");
+
+ if (query == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "no <query> node");
+ return NULL;
+ }
+
+ url_node = wocky_node_get_child (query, "url");
+
+ if (url_node == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "no <query><url> node");
+ return NULL;
+ }
+
+ if (url_node->content == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "<query><url> node has no content");
+ return NULL;
+ }
+
+ ft_type = wocky_node_get_attribute (url_node, "type");
+
+ if (ft_type != NULL && gibber_strdiff (ft_type, "file"))
+ {
+ /* We don't support directory transfer */
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "<url> has a 'type' attribute other than 'file': '%s'", ft_type);
+ return NULL;
+ }
+
+ /* The file name is extracted from the address */
+ url = g_strdup (url_node->content);
+ g_strstrip (url);
+ filename = g_strrstr (url, "/");
+ if (filename == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "<url> has no '/': '%s'", url);
+ g_free (url);
+ return NULL;
+ }
+ filename++; /* move after the last "/" */
+ filename = g_uri_unescape_string (filename, NULL);
+
+ desc_node = wocky_node_get_child (query, "desc");
+ if (desc_node != NULL)
+ {
+ description = desc_node->content;
+ }
+
+ content_type = wocky_node_get_attribute (url_node, "mimeType");
+ if (content_type == NULL)
+ {
+ content_type = "application/octet-stream";
+ }
+
+ self = g_object_new (GIBBER_TYPE_OOB_FILE_TRANSFER,
+ "id", id,
+ "self-id", self_id,
+ "peer-id", peer_id,
+ "filename", filename,
+ "porter", porter,
+ "contact", contact,
+ "direction", GIBBER_FILE_TRANSFER_DIRECTION_INCOMING,
+ "description", description,
+ "content-type", content_type,
+ NULL);
+
+ size = wocky_node_get_attribute (url_node, "size");
+ if (size != NULL)
+ gibber_file_transfer_set_size (GIBBER_FILE_TRANSFER (self),
+ g_ascii_strtoull (size, NULL, 0));
+
+ self->priv->url = url;
+
+ self->priv->transferred_bytes = 0;
+
+ GIBBER_FILE_TRANSFER (self)->dataforms = extract_dataforms (query);
+
+ g_free (filename);
+
+ return GIBBER_FILE_TRANSFER (self);
+}
+
+static void
+transferred_chunk (GibberOobFileTransfer *self,
+ guint64 bytes_read)
+{
+ g_signal_emit_by_name (self, "transferred-chunk", bytes_read);
+ self->priv->transferred_bytes += bytes_read;
+}
+
+/*
+ * Data received from the HTTP server.
+ */
+static void
+http_client_chunk_cb (SoupMessage *msg,
+ SoupBuffer *chunk,
+ gpointer user_data)
+{
+ GibberOobFileTransfer *self = user_data;
+
+ /* Don't write anything if it's been cancelled */
+ if (self->priv->cancelled)
+ return;
+
+ /* FIXME make async */
+ g_io_channel_write_chars (self->priv->channel, chunk->data,
+ chunk->length, NULL, NULL);
+
+ if (msg->status_code != HTTP_STATUS_CODE_OK)
+ {
+ /* Something did wrong, so it's not file data. Don't fire the
+ * transferred-chunk signal. */
+ self->priv->transferred_bytes += chunk->length;
+ return;
+ }
+
+ transferred_chunk (self, (guint64) chunk->length);
+}
+
+/*
+ * Received all the file from the HTTP server.
+ */
+static void
+http_client_finished_chunks_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
+{
+ GibberOobFileTransfer *self = user_data;
+ WockyStanza *stanza;
+ GError *error = NULL;
+ guint64 size;
+
+ /* disconnect from the "got-chunk" signal */
+ g_signal_handlers_disconnect_by_func (msg, http_client_chunk_cb, user_data);
+
+ /* message has been unreffed by libsoup */
+ self->priv->msg = NULL;
+
+ g_io_channel_unref (self->priv->channel);
+ self->priv->channel = NULL;
+
+ size = gibber_file_transfer_get_size (GIBBER_FILE_TRANSFER (self));
+
+ /* Is the transfer actually incomplete? */
+ if (size > self->priv->transferred_bytes)
+ {
+ DEBUG ("File transfer incomplete (size is %"G_GUINT64_FORMAT
+ " and only got %"G_GUINT64_FORMAT")",
+ size, self->priv->transferred_bytes);
+ g_signal_emit_by_name (self, "cancelled");
+ return;
+ }
+
+ DEBUG ("Finished HTTP chunked file transfer");
+
+ if (msg->status_code != HTTP_STATUS_CODE_OK)
+ {
+ const gchar *reason_phrase;
+
+ if (msg->reason_phrase != NULL)
+ reason_phrase = msg->reason_phrase;
+ else
+ reason_phrase = "Unknown HTTP error";
+
+ DEBUG ("HTTP error %d: %s", msg->status_code, reason_phrase);
+ error = g_error_new_literal (GIBBER_FILE_TRANSFER_ERROR,
+ GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND, reason_phrase);
+ gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+ g_error_free (error);
+ return;
+ }
+
+ stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_RESULT,
+ GIBBER_FILE_TRANSFER (self)->self_id,
+ GIBBER_FILE_TRANSFER (self)->peer_id,
+ WOCKY_NODE_ATTRIBUTE, "id", GIBBER_FILE_TRANSFER (self)->id,
+ NULL);
+
+ if (!gibber_file_transfer_send_stanza (GIBBER_FILE_TRANSFER (self), stanza,
+ &error))
+ {
+ DEBUG ("Wasn't able to send IQ result; ignoring: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* Send one last TransferredBytes signal. This will definitely get
+ * through, even if it has been < 1s since the last emission, so that
+ * clients will show 100% for sure.
+ */
+ transferred_chunk (self, 0);
+ g_signal_emit_by_name (self, "finished");
+
+ g_object_unref (stanza);
+}
+
+static void
+gibber_oob_file_transfer_receive (GibberFileTransfer *ft,
+ GIOChannel *dest)
+{
+ GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+
+ self->priv->session = soup_session_async_new ();
+ self->priv->msg = soup_message_new (SOUP_METHOD_GET, self->priv->url);
+ if (self->priv->msg == NULL)
+ {
+ GError *error = NULL;
+
+ gibber_file_transfer_cancel (ft, HTTP_STATUS_CODE_NOT_FOUND);
+ g_set_error (&error, GIBBER_FILE_TRANSFER_ERROR,
+ GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND, "Couldn't get the file");
+ gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+
+ return;
+ }
+
+ self->priv->channel = g_io_channel_ref (dest);
+
+ soup_message_body_set_accumulate (self->priv->msg->response_body, FALSE);
+ g_signal_connect (self->priv->msg, "got-chunk",
+ G_CALLBACK (http_client_chunk_cb), self);
+ soup_session_queue_message (self->priv->session, self->priv->msg,
+ http_client_finished_chunks_cb, self);
+}
+
+static WockyStanza *
+create_transfer_offer (GibberOobFileTransfer *self,
+ GError **error)
+{
+ GibberFileTransfer *ft = (GibberFileTransfer *) self;
+ WockyMetaPorter *porter;
+ WockyContact *contact;
+ GSocketConnection *conn;
+ GSocketAddress *address;
+ GInetAddress *addr;
+ GSocketFamily family;
+ GList *l;
+
+ /* local host name */
+ gchar *host_name;
+ gchar *host_escaped;
+
+ WockyStanza *stanza;
+ WockyNode *node;
+ WockyNode *query_node;
+ WockyNode *url_node;
+
+ gchar *filename_escaped;
+ gchar *url;
+ gchar *served_name;
+
+ guint64 size;
+
+ g_object_get (GIBBER_FILE_TRANSFER (self),
+ "porter", &porter,
+ "contact", &contact,
+ NULL);
+
+ conn = wocky_meta_porter_borrow_connection (porter, WOCKY_LL_CONTACT (contact));
+
+ g_object_unref (porter);
+ g_object_unref (contact);
+
+ if (conn == NULL)
+ {
+ g_set_error (error, GIBBER_FILE_TRANSFER_ERROR,
+ GIBBER_FILE_TRANSFER_ERROR_NOT_CONNECTED, "Null transport");
+ return NULL;
+ }
+
+ address = g_socket_connection_get_local_address (conn, NULL);
+ address = gibber_normalize_socket_address (address);
+ addr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address));
+ family = g_socket_address_get_family (address);
+
+ host_name = g_inet_address_to_string (addr);
+
+ g_object_unref (address);
+
+ if (family == G_SOCKET_FAMILY_IPV6)
+ {
+ /* put brackets around the IP6 */
+ host_escaped = g_strdup_printf ("[%s]", host_name);
+ }
+ else
+ {
+ /* IPv4: No need to modify the host_name */
+ host_escaped = g_strdup (host_name);
+ }
+
+ g_free (host_name);
+
+ filename_escaped = g_uri_escape_string (GIBBER_FILE_TRANSFER (self)->filename,
+ NULL, FALSE);
+ url = g_strdup_printf ("http://%s:%d/%s/%s", host_escaped,
+ soup_server_get_port (self->priv->server),
+ GIBBER_FILE_TRANSFER (self)->id, filename_escaped);
+ g_free (host_escaped);
+ g_free (filename_escaped);
+ served_name = g_strdup_printf ("/%s/%s", GIBBER_FILE_TRANSFER (self)->id,
+ GIBBER_FILE_TRANSFER (self)->filename);
+
+ stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_SET,
+ GIBBER_FILE_TRANSFER (self)->self_id,
+ GIBBER_FILE_TRANSFER (self)->peer_id,
+ WOCKY_NODE_ATTRIBUTE, "id", GIBBER_FILE_TRANSFER (self)->id,
+ NULL);
+ node = wocky_stanza_get_top_node (stanza);
+
+ query_node = wocky_node_add_child_ns (node, "query",
+ WOCKY_XMPP_NS_IQ_OOB);
+
+ url_node = wocky_node_add_child_with_content (query_node, "url", url);
+ wocky_node_set_attribute (url_node, "type", "file");
+ wocky_node_set_attribute (url_node, "mimeType",
+ GIBBER_FILE_TRANSFER (self)->content_type);
+
+ wocky_node_add_child_with_content (query_node, "desc",
+ GIBBER_FILE_TRANSFER (self)->description);
+
+ size = gibber_file_transfer_get_size (GIBBER_FILE_TRANSFER (self));
+
+ /* FIXME 0 could be a valid size */
+ if (size > 0)
+ {
+ gchar *size_str = g_strdup_printf ("%" G_GUINT64_FORMAT,
+ size);
+ wocky_node_set_attribute (url_node, "size", size_str);
+ g_free (size_str);
+ }
+
+ self->priv->url = url;
+ self->priv->served_name = served_name;
+
+ /* dataforms */
+ for (l = ft->dataforms; l != NULL; l = l->next)
+ {
+ WockyDataForm *form = l->data;
+ wocky_data_form_add_to_node (form, query_node);
+ }
+
+ return stanza;
+}
+
+/*
+ * Data is available from the channel so we can send it.
+ */
+static gboolean
+input_channel_readable_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ GibberOobFileTransfer *self = user_data;
+ GIOStatus status;
+ gchar *buff;
+ gsize bytes_read;
+
+#define BUFF_SIZE 4096
+
+ if (condition & G_IO_IN)
+ {
+ buff = g_malloc (BUFF_SIZE);
+ status = g_io_channel_read_chars (source, buff, BUFF_SIZE,
+ &bytes_read, NULL);
+ switch (status)
+ {
+ case G_IO_STATUS_NORMAL:
+ soup_message_body_append (self->priv->msg->response_body,
+ SOUP_MEMORY_TAKE, buff, bytes_read);
+ soup_server_unpause_message (self->priv->server, self->priv->msg);
+ DEBUG("Data available, writing a %"G_GSIZE_FORMAT" bytes chunk",
+ bytes_read);
+ transferred_chunk (self, (guint64) bytes_read);
+ return FALSE;
+ case G_IO_STATUS_AGAIN:
+ DEBUG("Data available, try again");
+ g_free (buff);
+ return TRUE;
+ case G_IO_STATUS_EOF:
+ DEBUG("EOF received on input");
+ break;
+ default:
+ DEBUG ("Read from the channel failed");
+ }
+ g_free (buff);
+ }
+
+#undef BUFF_SIZE
+
+ DEBUG("Closing HTTP chunked transfer");
+ soup_message_body_complete (self->priv->msg->response_body);
+ soup_server_unpause_message (self->priv->server, self->priv->msg);
+
+ g_io_channel_unref (self->priv->channel);
+ self->priv->channel = NULL;
+
+ soup_server_remove_handler (self->priv->server, self->priv->served_name);
+
+ return FALSE;
+}
+
+static void
+http_server_cb (SoupServer *server,
+ SoupMessage *msg,
+ const char *path,
+ GHashTable *query,
+ SoupClientContext *context,
+ gpointer user_data)
+{
+ const SoupURI *uri = soup_message_get_uri (msg);
+ GibberOobFileTransfer *self = user_data;
+ const gchar *accept_encoding;
+ guint64 size;
+ gchar *size_str;
+
+ if (msg->method != SOUP_METHOD_GET)
+ {
+ DEBUG ("A HTTP client tried to use an unsupported method");
+ soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+ return;
+ }
+
+ if (strcmp (uri->path, self->priv->served_name) != 0)
+ {
+ soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+ return;
+ }
+
+ DEBUG ("Serving '%s'", uri->path);
+
+ soup_message_set_status (msg, SOUP_STATUS_OK);
+ soup_message_headers_set_encoding (msg->response_headers,
+ SOUP_ENCODING_CHUNKED);
+
+ soup_message_headers_append (msg->response_headers, "Content-Type",
+ GIBBER_FILE_TRANSFER (self)->content_type);
+
+ size = gibber_file_transfer_get_size (GIBBER_FILE_TRANSFER (self));
+ size_str = g_strdup_printf ("%" G_GUINT64_FORMAT, size);
+ soup_message_headers_append (msg->response_headers, "Content-Length",
+ size_str);
+ g_free (size_str);
+
+ self->priv->msg = msg;
+
+ /* iChat accepts only AppleSingle encoding, i.e. file's contents and
+ * attributes are stored in the same stream */
+ accept_encoding = soup_message_headers_get (msg->request_headers,
+ "Accept-Encoding");
+ if (accept_encoding != NULL && strcmp (accept_encoding, "AppleSingle") == 0)
+ {
+ guint32 uint32;
+ guint16 uint16;
+ GByteArray *array;
+ gchar *buff;
+ guint len;
+
+ DEBUG ("Using AppleSingle encoding");
+
+ array = g_byte_array_sized_new (38);
+ /* magic number */
+ uint32 = htonl (0x51600);
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+ /* version */
+ uint32 = htonl (0x20000);
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+ /* filler */
+ uint32 = 0;
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+ /* nb entry */
+ uint16 = htons (1);
+ g_byte_array_append (array, (guint8 *) &uint16, 2);
+ /* data fork */
+ uint32 = htonl (1);
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+ /* data fork offset is the length of this header */
+ uint32 = htonl (38);
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+ /* data fork size is the size of the file */
+ uint32 = htonl (size);
+ g_byte_array_append (array, (guint8 *) &uint32, 4);
+
+ soup_message_headers_append (msg->response_headers, "Content-encoding",
+ "AppleSingle");
+
+ /* libsoup will free the date once they are written */
+ len = array->len;
+ buff = (gchar *) g_byte_array_free (array, FALSE);
+ soup_message_body_append (self->priv->msg->response_body,
+ SOUP_MEMORY_TAKE, buff, len);
+
+ soup_server_unpause_message (self->priv->server, self->priv->msg);
+ }
+
+ g_signal_emit_by_name (self, "remote-accepted");
+}
+
+static void
+create_and_send_transfer_offer (GibberOobFileTransfer *self)
+{
+ GError *error = NULL;
+ WockyStanza *stanza;
+
+ stanza = create_transfer_offer (self, &error);
+ if (stanza == NULL)
+ {
+ gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+ return;
+ }
+
+ soup_server_add_handler (self->priv->server, self->priv->served_name,
+ http_server_cb, self, NULL);
+
+ if (!gibber_file_transfer_send_stanza (GIBBER_FILE_TRANSFER (self),
+ stanza, &error))
+ {
+ gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+ }
+
+ g_object_unref (stanza);
+}
+
+static void
+porter_open_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyMetaPorter *porter = WOCKY_META_PORTER (source_object);
+ GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (user_data);
+ GError *error = NULL;
+
+ WockyContact *contact;
+
+ GSocketConnection *conn;
+ GSocketAddress *address;
+ GSocketFamily family;
+
+ if (!wocky_meta_porter_open_finish (porter, result, &error))
+ {
+ DEBUG ("Failed to open connection: %s", error->message);
+ g_clear_error (&error);
+
+ g_set_error (&error, GIBBER_FILE_TRANSFER_ERROR,
+ GIBBER_FILE_TRANSFER_ERROR_NOT_CONNECTED, "Couldn't open connection");
+ gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self),
+ error);
+ g_error_free (error);
+ return;
+ }
+
+ g_object_get (GIBBER_FILE_TRANSFER (self),
+ "contact", &contact,
+ NULL);
+
+ /* FIXME we should have only a single server */
+
+ /* FIXME: libsoup can't listen on IPv4 and IPv6 interfaces at the same
+ * time. http://bugzilla.gnome.org/show_bug.cgi?id=522519
+ * We have to check which IP will be send when creating the stanza. */
+
+ conn = wocky_meta_porter_borrow_connection (porter,
+ WOCKY_LL_CONTACT (contact));
+
+ if (conn == NULL)
+ {
+ g_set_error (&error, GIBBER_FILE_TRANSFER_ERROR,
+ GIBBER_FILE_TRANSFER_ERROR_NOT_CONNECTED, "Null transport");
+ gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self),
+ error);
+ g_error_free (error);
+ goto out;
+ }
+
+ address = g_socket_connection_get_remote_address (conn, NULL);
+ family = g_socket_address_get_family (address);
+ g_object_unref (address);
+
+ if (family == G_SOCKET_FAMILY_IPV6)
+ {
+ /* IPv6 server */
+ SoupAddress *addr;
+
+ addr = soup_address_new_any (SOUP_ADDRESS_FAMILY_IPV6, 0);
+ self->priv->server = soup_server_new (SOUP_SERVER_INTERFACE,
+ addr, NULL);
+
+ g_object_unref (addr);
+ }
+ else
+ {
+ /* IPv4 server */
+ self->priv->server = soup_server_new (NULL, NULL);
+ }
+
+ soup_server_run_async (self->priv->server);
+
+ create_and_send_transfer_offer (self);
+
+out:
+ /* this was reffed when calling open_async */
+ wocky_meta_porter_unhold (porter, contact);
+ g_object_unref (contact);
+}
+
+static void
+gibber_oob_file_transfer_offer (GibberFileTransfer *ft)
+{
+ GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+ WockyMetaPorter *porter;
+ WockyContact *contact;
+
+ if (self->priv->server != NULL)
+ {
+ create_and_send_transfer_offer (self);
+ return;
+ }
+
+ /* we need to create the soup server */
+
+ g_object_get (ft,
+ "porter", &porter,
+ "contact", &contact,
+ NULL);
+
+ wocky_meta_porter_open_async (porter, WOCKY_LL_CONTACT (contact),
+ NULL, porter_open_cb, ft);
+
+ g_object_unref (contact);
+ g_object_unref (porter);
+}
+
+static void
+http_server_wrote_chunk_cb (SoupMessage *msg,
+ gpointer user_data)
+{
+ GibberOobFileTransfer *self = user_data;
+
+ DEBUG("Chunk written, adding a watch to get more input (%s)",
+ self->priv->cancelled ? "cancelled" : "not cancelled");
+ if (self->priv->channel && !self->priv->cancelled)
+ {
+ self->priv->watch_id = g_io_add_watch (self->priv->channel,
+ G_IO_IN | G_IO_HUP, input_channel_readable_cb, self);
+ }
+}
+
+static void
+gibber_oob_file_transfer_send (GibberFileTransfer *ft,
+ GIOChannel *src)
+{
+ GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+
+ DEBUG("Starting HTTP chunked file transfer");
+ self->priv->channel = src;
+ g_io_channel_ref (src);
+ g_signal_connect (self->priv->msg, "wrote-chunk",
+ G_CALLBACK (http_server_wrote_chunk_cb), self);
+
+ /* The transfer only starts because an initial chunk has been sent, so call
+ * the callback.*/
+ http_server_wrote_chunk_cb (self->priv->msg, self);
+}
+
+static void
+gibber_oob_file_transfer_cancel (GibberFileTransfer *ft,
+ guint error_code)
+{
+ GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+ WockyStanza *stanza;
+ WockyNode *node;
+ WockyNode *query;
+ WockyNode *error_node;
+ gchar *code_string;
+
+ if (self->priv->cancelled)
+ return;
+ self->priv->cancelled = TRUE;
+
+ if (ft->direction == GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING)
+ /* The OOB XEP doesn't have protocol to inform the receiver that the
+ * sender cancelled the transfer. */
+ return;
+
+ stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_ERROR,
+ GIBBER_FILE_TRANSFER (self)->self_id,
+ GIBBER_FILE_TRANSFER (self)->peer_id,
+ WOCKY_NODE_ATTRIBUTE, "id", GIBBER_FILE_TRANSFER (self)->id,
+ NULL);
+ node = wocky_stanza_get_top_node (stanza);
+
+ query = wocky_node_add_child_ns (node, "query",
+ WOCKY_XMPP_NS_IQ_OOB);
+ wocky_node_add_child_with_content (query, "url", self->priv->url);
+
+ error_node = wocky_node_add_child (node, "error");
+ code_string = g_strdup_printf ("%d", error_code);
+
+ switch (error_code)
+ {
+ case HTTP_STATUS_CODE_NOT_FOUND:
+ wocky_node_set_attribute (error_node, "code", code_string);
+ wocky_node_set_attribute (error_node, "type", "cancel");
+ wocky_node_add_child_ns (error_node,
+ "item-not-found", WOCKY_XMPP_NS_STANZAS);
+ break;
+ case HTTP_STATUS_CODE_NOT_ACCEPTABLE:
+ wocky_node_set_attribute (error_node, "code", code_string);
+ wocky_node_set_attribute (error_node, "type", "modify");
+ wocky_node_add_child_ns (error_node,
+ "not-acceptable", WOCKY_XMPP_NS_STANZAS);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_free (code_string);
+
+ gibber_file_transfer_send_stanza (ft, stanza, NULL);
+
+ g_object_unref (stanza);
+}
+
+static void
+gibber_oob_file_transfer_received_stanza (GibberFileTransfer *ft,
+ WockyStanza *stanza)
+{
+ GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+ const gchar *type;
+ WockyNode *error_node;
+
+ if (strcmp (node->name, "iq") != 0)
+ return;
+
+ type = wocky_node_get_attribute (node, "type");
+ if (type == NULL)
+ return;
+
+ if (strcmp (type, "result") == 0)
+ {
+ g_signal_emit_by_name (self, "finished");
+ return;
+ }
+
+ error_node = wocky_node_get_child (node, "error");
+ if (error_node != NULL)
+ {
+ GError *error = NULL;
+ const gchar *error_code_str;
+
+ self->priv->cancelled = TRUE;
+
+ /* FIXME copy the error handling code from gabble */
+ error_code_str = wocky_node_get_attribute (error_node, "code");
+ if (error_code_str == NULL)
+ /* iChat uses the 'type' attribute to transmit the error code */
+ error_code_str = wocky_node_get_attribute (error_node, "type");
+
+ if (error_code_str != NULL && g_ascii_strtoll (error_code_str, NULL,
+ 10) == HTTP_STATUS_CODE_NOT_ACCEPTABLE)
+ {
+ g_signal_emit_by_name (self, "cancelled");
+ return;
+ }
+
+ g_set_error (&error, GIBBER_FILE_TRANSFER_ERROR,
+ GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND,
+ "Remote user is not able to retrieve the file");
+ gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+ g_error_free (error);
+ return;
+ }
+}
diff --git a/salut/lib/gibber/gibber-oob-file-transfer.h b/salut/lib/gibber/gibber-oob-file-transfer.h
new file mode 100644
index 000000000..ea29b9bfa
--- /dev/null
+++ b/salut/lib/gibber/gibber-oob-file-transfer.h
@@ -0,0 +1,70 @@
+/*
+ * gibber-oob-file-transfer.h - Header for GibberOobFileTransfer
+ * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_OOB_FILE_TRANSFER_H__
+#define __GIBBER_OOB_FILE_TRANSFER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include "gibber-file-transfer.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberOobFileTransfer GibberOobFileTransfer;
+typedef struct _GibberOobFileTransferClass GibberOobFileTransferClass;
+
+struct _GibberOobFileTransferClass
+{
+ GibberFileTransferClass parent_class;
+};
+
+typedef struct _GibberOobFileTransferPrivate GibberOobFileTransferPrivate;
+
+struct _GibberOobFileTransfer {
+ GibberFileTransfer parent;
+
+ GibberOobFileTransferPrivate *priv;
+};
+
+GType gibber_oob_file_transfer_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_OOB_FILE_TRANSFER \
+ (gibber_oob_file_transfer_get_type ())
+#define GIBBER_OOB_FILE_TRANSFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIBBER_TYPE_OOB_FILE_TRANSFER, GibberOobFileTransfer))
+#define GIBBER_OOB_FILE_TRANSFER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GIBBER_TYPE_OOB_FILE_TRANSFER, GibberOobFileTransferClass))
+#define GIBBER_IS_OOB_FILE_TRANSFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIBBER_TYPE_OOB_FILE_TRANSFER))
+#define GIBBER_IS_OOB_FILE_TRANSFER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GIBBER_TYPE_OOB_FILE_TRANSFER))
+#define GIBBER_OOB_FILE_TRANSFER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_OOB_FILE_TRANSFER, GibberOobFileTransferClass))
+
+
+gboolean gibber_oob_file_transfer_is_file_offer (WockyStanza *stanza);
+
+GibberFileTransfer *gibber_oob_file_transfer_new_from_stanza_with_from (
+ WockyStanza *stanza, WockyPorter *porter, WockyContact *contact,
+ const gchar *from, GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_OOB_FILE_TRANSFER_H__*/
diff --git a/salut/lib/gibber/gibber-r-multicast-causal-transport.c b/salut/lib/gibber/gibber-r-multicast-causal-transport.c
new file mode 100644
index 000000000..3cdb2a8d3
--- /dev/null
+++ b/salut/lib/gibber/gibber-r-multicast-causal-transport.c
@@ -0,0 +1,1402 @@
+/*
+ * gibber-r-multicast-causal-transport.c -
+ * Source for GibberRMulticastCausalTransport
+ *
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DEBUG_FLAG DEBUG_RMULTICAST
+#include "gibber-debug.h"
+
+#include "gibber-r-multicast-causal-transport.h"
+#include "gibber-r-multicast-packet.h"
+#include "gibber-r-multicast-sender.h"
+
+#include "gibber-signals-marshal.h"
+
+#define SESSION_TIMEOUT_MIN 1500
+#define SESSION_TIMEOUT_MAX 3000
+
+#define NR_JOIN_REQUESTS_TO_SEND 3
+#define PASSIVE_JOIN_TIME 500
+#define ACTIVE_JOIN_INTERVAL 250
+
+/* At least send a reliable packet _with_ depends every 3 minutes as keepalive
+ * and to ack pending data */
+#define KEEPALIVE_TIMEOUT 180000
+
+/* Send three bye's on disconnect at 500 ms intervals */
+#define NR_BYE_TO_SEND 3
+#define BYE_INTERVAL 500
+
+#define DEBUG_TRANSPORT(transport, format,...) \
+ DEBUG("%s (%x): " format, \
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE(transport)->name, \
+ transport->sender_id, ##__VA_ARGS__)
+
+struct hash_data {
+ GibberRMulticastSender *sender;
+ GibberRMulticastPacket *packet;
+};
+
+static void repair_message_cb (GibberRMulticastSender *sender,
+ GibberRMulticastPacket *packet, gpointer user_data);
+static void whois_reply_cb (GibberRMulticastSender *sender,
+ gpointer user_data);
+
+static void schedule_session_message (
+ GibberRMulticastCausalTransport *transport);
+static void schedule_keepalive_message (
+ GibberRMulticastCausalTransport *transport);
+
+G_DEFINE_TYPE(GibberRMulticastCausalTransport,
+ gibber_r_multicast_causal_transport,
+ GIBBER_TYPE_TRANSPORT)
+
+/* signal enum */
+enum
+{
+ SENDER_FAILED,
+ RECEIVED_CONTROL_PACKET,
+ RECEIVED_FOREIGN_PACKET,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum {
+ PROP_NAME = 1,
+ PROP_TRANSPORT,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _GibberRMulticastCausalTransportPrivate
+ GibberRMulticastCausalTransportPrivate;
+
+struct _GibberRMulticastCausalTransportPrivate
+{
+ gboolean dispose_has_run;
+ GibberTransport *transport;
+ guint32 packet_id;
+ GibberRMulticastSenderGroup *sender_group;
+ GibberRMulticastSender *self;
+ guint timer;
+ guint keepalive_timer;
+ gchar *name;
+
+ gint nr_join_requests;
+ gint nr_join_requests_seen;
+
+ gint nr_bye;
+
+ gboolean resetting;
+};
+
+#define GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_R_MULTICAST_CAUSAL_TRANSPORT,\
+ GibberRMulticastCausalTransportPrivate))
+
+static guint32
+_random_nonzero_uint (void)
+{
+ guint32 result;
+
+ do {
+ result = g_random_int ();
+ } while (result == 0);
+
+ return result;
+}
+
+static void
+gibber_r_multicast_causal_transport_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GibberRMulticastCausalTransport *transport =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (object);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ switch (property_id) {
+ case PROP_NAME:
+ /* Only set on initialisation */
+ priv->name = g_value_dup_string (value);
+ break;
+ case PROP_TRANSPORT:
+ priv->transport = GIBBER_TRANSPORT (g_value_dup_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gibber_r_multicast_causal_transport_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GibberRMulticastCausalTransport *transport =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (object);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ switch (property_id) {
+ case PROP_NAME:
+ g_value_set_string (value, priv->self->name);
+ break;
+ case PROP_TRANSPORT:
+ g_value_set_object (value, priv->transport);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gibber_r_multicast_causal_transport_init (GibberRMulticastCausalTransport *obj)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (obj);
+
+ /* allocate any data required by the object here */
+ priv->sender_group = gibber_r_multicast_sender_group_new ();
+ priv->packet_id = g_random_int ();
+}
+
+static void gibber_r_multicast_causal_transport_dispose (GObject *object);
+static void gibber_r_multicast_causal_transport_finalize (GObject *object);
+
+static gboolean gibber_r_multicast_causal_transport_do_send (
+ GibberTransport *transport, const guint8 *data, gsize size,
+ GError **error);
+
+static void gibber_r_multicast_causal_transport_disconnect (
+ GibberTransport *transport);
+
+static void
+gibber_r_multicast_causal_transport_class_init (
+ GibberRMulticastCausalTransportClass *
+ gibber_r_multicast_causal_transport_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (
+ gibber_r_multicast_causal_transport_class);
+ GibberTransportClass *transport_class =
+ GIBBER_TRANSPORT_CLASS (gibber_r_multicast_causal_transport_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (gibber_r_multicast_causal_transport_class,
+ sizeof (GibberRMulticastCausalTransportPrivate));
+
+ object_class->dispose = gibber_r_multicast_causal_transport_dispose;
+ object_class->finalize = gibber_r_multicast_causal_transport_finalize;
+
+ signals[SENDER_FAILED] = g_signal_new ("sender-failed",
+ G_OBJECT_CLASS_TYPE (gibber_r_multicast_causal_transport_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GIBBER_TYPE_R_MULTICAST_SENDER);
+
+ signals[RECEIVED_CONTROL_PACKET] = g_signal_new ("received-control-packet",
+ G_OBJECT_CLASS_TYPE (gibber_r_multicast_causal_transport_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _gibber_signals_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2, GIBBER_TYPE_R_MULTICAST_SENDER,
+ GIBBER_TYPE_R_MULTICAST_PACKET);
+
+ signals[RECEIVED_FOREIGN_PACKET] = g_signal_new ("received-foreign-packet",
+ G_OBJECT_CLASS_TYPE (gibber_r_multicast_causal_transport_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GIBBER_TYPE_R_MULTICAST_PACKET);
+
+ object_class->set_property =
+ gibber_r_multicast_causal_transport_set_property;
+ object_class->get_property =
+ gibber_r_multicast_causal_transport_get_property;
+
+ param_spec = g_param_spec_object ("transport", "transport",
+ "The underlying Transport", GIBBER_TYPE_TRANSPORT,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TRANSPORT, param_spec);
+
+ param_spec = g_param_spec_string ("name", "name",
+ "The name to use on the protocol", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NAME, param_spec);
+
+ transport_class->send = gibber_r_multicast_causal_transport_do_send;
+ transport_class->disconnect = gibber_r_multicast_causal_transport_disconnect;
+}
+
+void
+gibber_r_multicast_causal_transport_dispose (GObject *object)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (object);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+ if (priv->transport != NULL)
+ {
+ g_object_unref (priv->transport);
+ priv->transport = NULL;
+ }
+
+ if (priv->timer != 0)
+ {
+ g_source_remove (priv->timer);
+ }
+
+ if (priv->keepalive_timer != 0)
+ {
+ g_source_remove (priv->keepalive_timer);
+ }
+
+ if (priv->self != NULL)
+ {
+ g_object_unref (priv->self);
+ priv->self = NULL;
+ }
+
+ gibber_r_multicast_sender_group_free (priv->sender_group);
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (
+ gibber_r_multicast_causal_transport_parent_class)->dispose)
+ {
+ G_OBJECT_CLASS (gibber_r_multicast_causal_transport_parent_class)->dispose
+ (object);
+ }
+}
+
+void
+gibber_r_multicast_causal_transport_finalize (GObject *object)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (object);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (
+ gibber_r_multicast_causal_transport_parent_class)->finalize (object);
+}
+
+static gboolean
+sendout_packet (GibberRMulticastCausalTransport *transport,
+ GibberRMulticastPacket *packet,
+ GError **error)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+ guint8 *rawdata;
+ gsize rawsize;
+
+ if (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet)
+ && (packet->depends->len != 0
+ || g_hash_table_size (priv->sender_group->senders) == 0))
+ schedule_keepalive_message (transport);
+
+ rawdata = gibber_r_multicast_packet_get_raw_data (packet, &rawsize);
+ return gibber_transport_send (GIBBER_TRANSPORT(priv->transport),
+ rawdata, rawsize, error);
+}
+
+static gchar *
+g_array_uint32_to_str (GArray *array)
+{
+ guint i;
+ GString *str;
+
+ if (array == NULL || array->len == 0) {
+ return g_strdup (" { } ");
+ }
+
+ str= g_string_sized_new (4 + 10 * array->len);
+ str = g_string_append (str, " {");
+ for (i = 0; i < array->len; i++)
+ {
+ g_string_append_printf (str, " %x,", g_array_index (array, guint32, i));
+ }
+ g_string_truncate (str, str->len - 1);
+ g_string_append (str, " }");
+
+ return g_string_free (str, FALSE);
+}
+
+static void
+add_sender_info (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (value);
+ GibberRMulticastPacket *packet = GIBBER_R_MULTICAST_PACKET (user_data);
+ gboolean r;
+
+ if (sender->state == GIBBER_R_MULTICAST_SENDER_STATE_NEW ||
+ sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ return;
+
+ r = gibber_r_multicast_packet_add_sender_info (packet, sender->id,
+ sender->next_input_packet, NULL);
+ g_assert (r);
+}
+
+static gboolean
+sendout_session_cb (gpointer data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (data);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ GibberRMulticastPacket *packet =
+ gibber_r_multicast_packet_new (PACKET_TYPE_SESSION, priv->self->id,
+ priv->transport->max_packet_size);
+
+ DEBUG_TRANSPORT (self, "Preparing session message");
+ g_hash_table_foreach (priv->sender_group->senders, add_sender_info, packet);
+ DEBUG_TRANSPORT (self, "Sending out session message");
+ sendout_packet (self, packet, NULL);
+ g_object_unref (packet);
+
+ priv->timer = 0;
+ schedule_session_message (self);
+
+ return FALSE;
+}
+
+static void
+schedule_session_message (GibberRMulticastCausalTransport *transport)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ if (priv->timer != 0)
+ g_source_remove (priv->timer);
+
+ priv->timer =
+ g_timeout_add (
+ g_random_int_range (SESSION_TIMEOUT_MIN, SESSION_TIMEOUT_MAX),
+ sendout_session_cb, transport);
+}
+
+static void
+connected (GibberRMulticastCausalTransport *transport)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE(transport);
+ GibberRMulticastPacket *packet;
+
+ DEBUG_TRANSPORT (transport, "Connected to group");
+
+ priv->self = gibber_r_multicast_sender_new (transport->sender_id, priv->name,
+ priv->sender_group);
+ gibber_r_multicast_sender_update_start (priv->self, priv->packet_id);
+ gibber_r_multicast_sender_set_data_start (priv->self, priv->packet_id);
+
+ gibber_r_multicast_sender_group_add (priv->sender_group, priv->self);
+
+ g_object_ref (priv->self);
+
+ g_signal_connect (priv->self, "repair-message",
+ G_CALLBACK (repair_message_cb), transport);
+ g_signal_connect (priv->self, "whois-reply",
+ G_CALLBACK (whois_reply_cb), transport);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (transport),
+ GIBBER_TRANSPORT_CONNECTED);
+
+ /* Send out an unsolicited whois reply */
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_WHOIS_REPLY,
+ transport->sender_id, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_whois_reply_info (packet, priv->name);
+
+ sendout_packet (transport, packet, NULL);
+ g_object_unref (packet);
+
+ schedule_session_message (transport);
+ schedule_keepalive_message (transport);
+}
+
+static gboolean
+next_join_step (gpointer data)
+{
+ GibberRMulticastCausalTransport *transport =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (data);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ DEBUG_TRANSPORT (transport, "step: %d", priv->nr_join_requests);
+
+ if (priv->nr_join_requests < NR_JOIN_REQUESTS_TO_SEND)
+ {
+ GibberRMulticastPacket *packet;
+
+ priv->nr_join_requests++;
+
+ /* Set sender to 0 as we don't have an official id yet */
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_WHOIS_REQUEST,
+ 0, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_whois_request_info (packet,
+ transport->sender_id);
+
+ sendout_packet (transport, packet, NULL);
+ g_object_unref (packet);
+
+ priv->timer = g_timeout_add (ACTIVE_JOIN_INTERVAL,
+ next_join_step, transport);
+ }
+ else
+ {
+ connected (transport);
+ }
+ return FALSE;
+}
+
+
+
+static void
+start_joining (GibberRMulticastCausalTransport *transport)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ transport->sender_id = _random_nonzero_uint ();
+ priv->nr_join_requests = 0;
+ priv->nr_join_requests_seen = 0;
+
+ DEBUG_TRANSPORT (transport, "Started joining");
+
+ if (priv->timer != 0)
+ {
+ g_source_remove (priv->timer);
+ }
+
+ priv->timer = g_timeout_add (PASSIVE_JOIN_TIME,
+ next_join_step, transport);
+}
+
+static void
+data_received_cb (GibberRMulticastSender *sender,
+ guint16 stream_id,
+ guint8 *data,
+ gsize size,
+ gpointer user_data)
+{
+ GibberRMulticastCausalBuffer rmbuffer;
+
+ rmbuffer.buffer.data = data;
+ rmbuffer.buffer.length = size;
+ rmbuffer.sender = sender->name;
+ rmbuffer.stream_id = stream_id;
+ rmbuffer.sender_id = sender->id;
+
+ gibber_transport_received_data_custom (GIBBER_TRANSPORT (user_data),
+ (GibberBuffer *) &rmbuffer);
+}
+
+static void
+control_packet_received_cb (GibberRMulticastSender *sender,
+ GibberRMulticastPacket *packet,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (user_data);
+
+ g_signal_emit (self, signals[RECEIVED_CONTROL_PACKET], 0, sender, packet);
+}
+
+static void
+repair_request_cb (GibberRMulticastSender *sender,
+ guint id,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (user_data);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastPacket *packet =
+ gibber_r_multicast_packet_new (PACKET_TYPE_REPAIR_REQUEST,
+ priv->self->id, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_repair_request_info (packet, sender->id, id);
+
+ sendout_packet (self, packet, NULL);
+ g_object_unref (packet);
+}
+
+static void
+repair_message_cb (GibberRMulticastSender *sender,
+ GibberRMulticastPacket *packet,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (user_data);
+
+ sendout_packet (self, packet, NULL);
+}
+
+static void
+whois_reply_cb (GibberRMulticastSender *sender,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (user_data);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastPacket *packet =
+ gibber_r_multicast_packet_new (PACKET_TYPE_WHOIS_REPLY,
+ sender->id, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_whois_reply_info (packet, sender->name);
+
+ sendout_packet (self, packet, NULL);
+ g_object_unref (packet);
+}
+
+static void
+whois_request_cb (GibberRMulticastSender *sender,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (user_data);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastPacket *packet =
+ gibber_r_multicast_packet_new (PACKET_TYPE_WHOIS_REQUEST,
+ priv->self->id, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_whois_request_info (packet, sender->id);
+
+ sendout_packet (self, packet, NULL);
+ g_object_unref (packet);
+}
+
+static void
+sender_failed_cb (GibberRMulticastSender *sender,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (user_data);
+
+ g_signal_emit (self, signals[SENDER_FAILED], 0, sender);
+}
+
+GibberRMulticastSender *
+gibber_r_multicast_causal_transport_add_sender (
+ GibberRMulticastCausalTransport *transport,
+ guint32 sender_id)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+ GibberRMulticastSender *sender;
+
+ sender = gibber_r_multicast_sender_group_lookup (priv->sender_group,
+ sender_id);
+
+ if (sender != NULL)
+ return sender;
+
+ sender = gibber_r_multicast_sender_new (sender_id, NULL, priv->sender_group);
+
+ gibber_r_multicast_sender_group_add (priv->sender_group, sender);
+
+ g_signal_connect (sender, "received-data",
+ G_CALLBACK (data_received_cb), transport);
+
+ g_signal_connect (sender, "received-control-packet",
+ G_CALLBACK (control_packet_received_cb), transport);
+
+ g_signal_connect (sender, "repair-message",
+ G_CALLBACK (repair_message_cb), transport);
+ g_signal_connect (sender, "repair-request",
+ G_CALLBACK (repair_request_cb), transport);
+
+ g_signal_connect (sender, "whois-request",
+ G_CALLBACK (whois_request_cb), transport);
+ g_signal_connect (sender, "whois-reply",
+ G_CALLBACK (whois_reply_cb), transport);
+
+ g_signal_connect (sender, "failed",
+ G_CALLBACK (sender_failed_cb), transport);
+
+ return sender;
+}
+
+
+void gibber_r_multicast_causal_transport_update_sender_start (
+ GibberRMulticastCausalTransport *transport,
+ guint32 sender_id,
+ guint32 packet_id)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+ GibberRMulticastSender *sender;
+
+ sender = gibber_r_multicast_sender_group_lookup (priv->sender_group,
+ sender_id);
+ g_assert (sender != NULL);
+
+ gibber_r_multicast_sender_update_start (sender, packet_id);
+}
+
+static void
+handle_session_message (GibberRMulticastCausalTransport *self,
+ GibberRMulticastPacket *packet)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ guint i;
+ gboolean outdated = FALSE;
+
+ g_assert (packet->type == PACKET_TYPE_SESSION);
+
+ for (i = 0; i < packet->depends->len ; i++)
+ {
+ GibberRMulticastPacketSenderInfo *sender_info =
+ g_array_index (packet->depends,
+ GibberRMulticastPacketSenderInfo *, i);
+ GibberRMulticastSender *sender =
+ gibber_r_multicast_sender_group_lookup (priv->sender_group,
+ sender_info->sender_id);
+
+ if (sender == NULL)
+ {
+ /* We will hear about this guy in a reliable message we apparently
+ * didn't receive yet */
+ continue;
+ }
+
+ if (gibber_r_multicast_packet_diff (sender_info->packet_id,
+ sender->next_input_packet) > 0)
+ {
+ g_assert (sender->state > GIBBER_R_MULTICAST_SENDER_STATE_NEW);
+ outdated = TRUE;
+ }
+ gibber_r_multicast_sender_seen (sender, sender_info->packet_id);
+ }
+
+ /* Reschedule the sending out of a session message if the received session
+ * message was at least as up to date as us */
+ if (!outdated &&
+ g_hash_table_size (priv->sender_group->senders)
+ == packet->depends->len)
+ {
+ DEBUG_TRANSPORT (self, "Rescheduling session message");
+ schedule_session_message (self);
+ }
+}
+
+static void
+handle_packet_depends (GibberRMulticastCausalTransport *self,
+ GibberRMulticastPacket *packet)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ guint i;
+
+ g_assert (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet));
+
+ for (i = 0 ; i < packet->depends->len; i++)
+ {
+ GibberRMulticastPacketSenderInfo *sender_info =
+ g_array_index (packet->depends,
+ GibberRMulticastPacketSenderInfo *, i);
+ GibberRMulticastSender *sender =
+ gibber_r_multicast_sender_group_lookup (priv->sender_group,
+ sender_info->sender_id);
+
+ /* This might be a resent after which the sender was removed */
+ if (sender != NULL)
+ gibber_r_multicast_sender_seen (sender, sender_info->packet_id);
+ }
+}
+
+static void
+joining_multicast_receive (GibberRMulticastCausalTransport *self,
+ GibberRMulticastPacket *packet)
+{
+
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ DEBUG_TRANSPORT (self, "Received packet type: 0x%x from %x id %x",
+ packet->type, packet->sender, packet->packet_id);
+
+ if (packet->sender == self->sender_id)
+ {
+ DEBUG_TRANSPORT (self, "Detected collision with existing sender, "
+ "restarting join process");
+ start_joining (self);
+ return;
+ }
+
+ if (packet->type == PACKET_TYPE_WHOIS_REQUEST &&
+ packet->data.whois_request.sender_id == self->sender_id)
+ {
+ if (packet->sender != 0)
+ {
+ DEBUG_TRANSPORT (self, "Detected existing node querying "
+ "for the same id, restarting join process");
+ start_joining (self);
+ }
+ else
+ {
+ priv->nr_join_requests_seen++;
+ if (priv->nr_join_requests < priv->nr_join_requests_seen)
+ {
+ DEBUG_TRANSPORT (self, "Detected another node probing for "
+ "the same id, restarting join process");
+ start_joining (self);
+ }
+ }
+ }
+}
+
+static void
+joined_multicast_receive (GibberRMulticastCausalTransport *self,
+ GibberRMulticastPacket *packet)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ if (packet->sender == 0)
+ {
+ if (packet->type != PACKET_TYPE_WHOIS_REQUEST)
+ {
+ DEBUG_TRANSPORT (self, "Invalid packet (sender is 0, which is "
+ "not valid for type %x)",
+ packet->type);
+ return;
+ }
+
+ DEBUG_TRANSPORT (self, "New sender polling for a unique id");
+ /* Just try to push the packet once. If the id is known a whois reply
+ * will be scheduled otherwise it's dropped as should be */
+ gibber_r_multicast_sender_group_push_packet (priv->sender_group, packet);
+ return;
+ }
+
+ if (packet->sender == self->sender_id)
+ /* Discard our own packets */
+ return;
+
+ DEBUG_TRANSPORT (self, "Received packet type: 0x%x from %x",
+ packet->type, packet->sender);
+
+ if (!gibber_r_multicast_sender_group_push_packet (priv->sender_group,
+ packet))
+ {
+ /* No entries that could pick up the packet, signal a foreign message so
+ * the membership protocol can handle it if needed */
+ DEBUG_TRANSPORT (self, "Foreign packet Received type: 0x%x from %x",
+ packet->type, packet->sender);
+ g_signal_emit (self, signals[RECEIVED_FOREIGN_PACKET], 0, packet);
+
+ /* Retry the push. the signal handler might have added it */
+ if (!gibber_r_multicast_sender_group_push_packet (priv->sender_group,
+ packet))
+ return;
+ }
+
+ /* The packet was picked up by one of the nodes, time for some
+ * postprocessing */
+
+ switch (packet->type)
+ {
+ case PACKET_TYPE_WHOIS_REQUEST:
+ case PACKET_TYPE_WHOIS_REPLY:
+ case PACKET_TYPE_REPAIR_REQUEST:
+ /* No postprocessing needed */
+ break;
+ case PACKET_TYPE_SESSION:
+ handle_session_message (self, packet);
+ break;
+ default:
+ if (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet))
+ {
+ handle_packet_depends (self, packet);
+ }
+ else
+ {
+ DEBUG_TRANSPORT (self,
+ "Received unhandled packet type!!, ignoring");
+ }
+ }
+}
+
+/* Packet received while disconnecting. Only react on repair requests and
+ * incoming reliable packets (to cancel repair request sends)
+ */
+static void
+disconnecting_multicast_receive (GibberRMulticastCausalTransport *self,
+ GibberRMulticastPacket *packet)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastSender *sender;
+
+ if (packet->sender == 0)
+ return;
+
+ sender = gibber_r_multicast_sender_group_lookup (priv->sender_group,
+ packet->sender);
+
+ if (sender == NULL)
+ return;
+
+ if (packet->type == PACKET_TYPE_REPAIR_REQUEST)
+ {
+ sender = gibber_r_multicast_sender_group_lookup (priv->sender_group,
+ packet->data.repair_request.sender_id);
+ if (sender != NULL)
+ gibber_r_multicast_sender_repair_request (sender,
+ packet->data.repair_request.packet_id);
+ }
+
+ if (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet))
+ {
+ gibber_r_multicast_sender_push (sender, packet);
+ }
+}
+
+
+
+static void
+r_multicast_receive (GibberTransport *transport,
+ GibberBuffer *buffer,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (user_data);
+ GibberRMulticastPacket *packet = NULL;
+ GError *error = NULL;
+
+ packet = gibber_r_multicast_packet_parse (buffer->data,
+ buffer->length, &error);
+
+ if (packet == NULL)
+ {
+ DEBUG_TRANSPORT (self, "Failed to parse packet: %s", error->message);
+ }
+ else
+ {
+ switch (GIBBER_TRANSPORT (self)->state)
+ {
+ case GIBBER_TRANSPORT_CONNECTING:
+ joining_multicast_receive (self, packet);
+ break;
+ case GIBBER_TRANSPORT_CONNECTED:
+ joined_multicast_receive (self, packet);
+ break;
+ case GIBBER_TRANSPORT_DISCONNECTING:
+ disconnecting_multicast_receive (self, packet);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ if (error != NULL)
+ g_error_free (error);
+
+ if (packet != NULL)
+ g_object_unref (packet);
+}
+
+GibberRMulticastCausalTransport *
+gibber_r_multicast_causal_transport_new (GibberTransport *transport,
+ const gchar *name)
+{
+ GibberRMulticastCausalTransport *result;
+
+ g_assert (name != NULL && *name != '\0');
+
+ result = g_object_new (GIBBER_TYPE_R_MULTICAST_CAUSAL_TRANSPORT,
+ "name", name,
+ "transport", transport,
+ NULL);
+
+ gibber_transport_set_handler (GIBBER_TRANSPORT (transport),
+ r_multicast_receive, result);
+
+ return result;
+}
+
+gboolean
+gibber_r_multicast_causal_transport_connect (
+ GibberRMulticastCausalTransport *transport,
+ gboolean initial,
+ GError **error)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ g_assert (priv->transport->max_packet_size > 128);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (transport),
+ GIBBER_TRANSPORT_CONNECTING);
+
+ start_joining (transport);
+
+ return TRUE;
+}
+
+static void
+add_depend (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (value);
+ struct hash_data *d = (struct hash_data *) user_data;
+ gboolean r;
+
+ if (sender->state < GIBBER_R_MULTICAST_SENDER_STATE_PREPARING
+ || sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ return;
+
+ if (sender == d->sender)
+ return;
+
+ r = gibber_r_multicast_packet_add_sender_info (d->packet, sender->id,
+ sender->next_output_packet, NULL);
+ g_assert (r);
+}
+
+static void
+add_packet_depends (GibberRMulticastCausalTransport *self,
+ GibberRMulticastPacket *packet)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ struct hash_data hd;
+
+ hd.sender = priv->self;
+ hd.packet = packet;
+ g_hash_table_foreach (priv->sender_group->senders, add_depend, &hd);
+}
+
+static gboolean
+send_keepalive_cb (gpointer data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (data);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastPacket *packet;
+
+ /* Sending out a reliable packet will reschedule the keepalive */
+ priv->keepalive_timer = 0;
+
+ DEBUG ("Sending out keepalive");
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_NO_DATA,
+ priv->self->id, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_packet_id (packet, priv->packet_id++);
+ add_packet_depends (self, packet);
+
+ gibber_r_multicast_sender_push (priv->self, packet);
+ sendout_packet (self, packet, NULL);
+ g_object_unref (packet);
+
+ return FALSE;
+}
+
+
+static void
+schedule_keepalive_message (GibberRMulticastCausalTransport *transport)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ if (priv->keepalive_timer != 0)
+ g_source_remove (priv->keepalive_timer);
+
+ priv->keepalive_timer =
+ g_timeout_add (KEEPALIVE_TIMEOUT, send_keepalive_cb, transport);
+}
+
+
+gboolean
+gibber_r_multicast_causal_transport_send (
+ GibberRMulticastCausalTransport *transport,
+ guint16 stream_id,
+ const guint8 *data,
+ gsize size,
+ GError **error)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (transport);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastPacket *packet;
+ gsize payloaded;
+ gboolean ret = TRUE;
+
+ if (priv->resetting)
+ return TRUE;
+
+ g_assert (priv->self != NULL);
+
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_DATA, priv->self->id,
+ priv->transport->max_packet_size);
+
+ add_packet_depends (self, packet);
+ payloaded = gibber_r_multicast_packet_add_payload (packet, data, size);
+ gibber_r_multicast_packet_set_data_info (packet, stream_id,
+ GIBBER_R_MULTICAST_DATA_PACKET_START, size);
+
+ if (payloaded < size)
+ {
+ do
+ {
+ gibber_r_multicast_packet_set_packet_id (packet, priv->packet_id++);
+ ret = sendout_packet (self, packet, error);
+ gibber_r_multicast_sender_push (priv->self, packet);
+ g_object_unref (packet);
+
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_DATA,
+ priv->self->id, priv->transport->max_packet_size);
+ payloaded += gibber_r_multicast_packet_add_payload (packet,
+ data + payloaded, size - payloaded);
+ gibber_r_multicast_packet_set_data_info (packet, stream_id, 0, size);
+ } while (payloaded < size);
+ gibber_r_multicast_packet_set_data_info (packet, stream_id,
+ GIBBER_R_MULTICAST_DATA_PACKET_END, size);
+ }
+ else
+ {
+ gibber_r_multicast_packet_set_data_info (packet, stream_id,
+ GIBBER_R_MULTICAST_DATA_PACKET_START
+ | GIBBER_R_MULTICAST_DATA_PACKET_END, size);
+
+ }
+ gibber_r_multicast_packet_set_packet_id (packet, priv->packet_id++);
+ gibber_r_multicast_sender_push (priv->self, packet);
+ ret = sendout_packet (self, packet, error);
+ g_object_unref (packet);
+
+ return ret;
+}
+
+static gboolean
+gibber_r_multicast_causal_transport_do_send (GibberTransport *transport,
+ const guint8 *data, gsize size, GError **error)
+{
+ return gibber_r_multicast_causal_transport_send (
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (transport),
+ GIBBER_R_MULTICAST_CAUSAL_DEFAULT_STREAM,
+ data, size, error);
+}
+
+static void
+reconnect (GibberRMulticastCausalTransport *self)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ /* Remove all data and start connection phase */
+ gibber_r_multicast_sender_group_free (priv->sender_group);
+ priv->sender_group = gibber_r_multicast_sender_group_new ();
+ priv->packet_id = g_random_int ();
+ priv->resetting = FALSE;
+
+ g_assert (gibber_r_multicast_causal_transport_connect (self, FALSE, NULL));
+}
+
+static void
+disconnect_done (GibberRMulticastCausalTransport *self)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->timer != 0)
+ {
+ g_source_remove (priv->timer);
+ }
+
+ gibber_transport_disconnect (GIBBER_TRANSPORT (priv->transport));
+
+ if (priv->self != NULL)
+ {
+ g_object_unref (priv->self);
+ priv->self = NULL;
+ }
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (self),
+ GIBBER_TRANSPORT_DISCONNECTED);
+}
+
+static gboolean
+send_next_bye (gpointer data)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (data);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastPacket *packet;
+
+ DEBUG ("Sending bye nr %d", priv->nr_bye);
+
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_BYE,
+ priv->self->id, priv->transport->max_packet_size);
+ gibber_r_multicast_packet_set_packet_id (packet, priv->packet_id);
+
+ sendout_packet (self, packet, NULL);
+ g_object_unref (packet);
+
+ priv->nr_bye++;
+
+ if (priv->nr_bye < NR_BYE_TO_SEND)
+ {
+ priv->timer = g_timeout_add (BYE_INTERVAL,
+ send_next_bye, self);
+ }
+ else if (priv->resetting)
+ {
+ reconnect (self);
+ }
+ else
+ {
+ disconnect_done (self);
+ }
+
+ return FALSE;
+}
+
+static void
+do_disconnect (GibberRMulticastCausalTransport *transport)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (transport);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ if (gibber_transport_get_state (GIBBER_TRANSPORT (self))
+ == GIBBER_TRANSPORT_DISCONNECTING)
+ return;
+
+ if (priv->timer != 0)
+ {
+ g_source_remove (priv->timer);
+ }
+
+ if (priv->keepalive_timer != 0)
+ {
+ g_source_remove (priv->keepalive_timer);
+ }
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (self),
+ GIBBER_TRANSPORT_DISCONNECTING);
+
+ gibber_r_multicast_sender_group_stop (priv->sender_group);
+
+ priv->nr_bye = 0;
+ send_next_bye (self);
+}
+
+static void
+gibber_r_multicast_causal_transport_disconnect (GibberTransport *transport)
+{
+ GibberRMulticastCausalTransport *self =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (transport);
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (self);
+
+ priv->resetting = FALSE;
+ if (gibber_transport_get_state (GIBBER_TRANSPORT (self))
+ < GIBBER_TRANSPORT_CONNECTED)
+ {
+ disconnect_done (self);
+ }
+ else
+ {
+ do_disconnect (self);
+ }
+}
+
+void
+gibber_r_multicast_causal_transport_reset (
+ GibberRMulticastCausalTransport *transport)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ priv->resetting = TRUE;
+ do_disconnect (transport);
+}
+
+guint32
+gibber_r_multicast_causal_transport_send_attempt_join (
+ GibberRMulticastCausalTransport *transport,
+ GArray *new_senders,
+ gboolean repeat)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+ GibberRMulticastPacket *packet;
+ gchar *str;
+ guint32 packet_id;
+
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_ATTEMPT_JOIN,
+ priv->self->id, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_packet_id (packet, priv->packet_id++);
+ gibber_r_multicast_packet_attempt_join_add_senders (packet, new_senders,
+ NULL);
+ add_packet_depends (transport, packet);
+
+ str = g_array_uint32_to_str (new_senders);
+ DEBUG_TRANSPORT (transport, "Sending out %sAJ: %s",
+ repeat ? "repeating " : "", str);
+ g_free (str);
+
+ gibber_r_multicast_sender_push (priv->self, packet);
+ gibber_r_multicast_sender_set_packet_repeat (priv->self,
+ packet->packet_id, repeat);
+
+ sendout_packet (transport, packet, NULL);
+
+ packet_id = packet->packet_id;
+ g_object_unref (packet);
+
+ return packet_id;
+}
+
+void gibber_r_multicast_causal_transport_stop_attempt_join (
+ GibberRMulticastCausalTransport *transport,
+ guint32 attempt_join_id)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ gibber_r_multicast_sender_set_packet_repeat (priv->self,
+ attempt_join_id, FALSE);
+}
+
+void
+gibber_r_multicast_causal_transport_send_failure (
+ GibberRMulticastCausalTransport *transport,
+ GArray *failures)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+ GibberRMulticastPacket *packet;
+ gchar *str;
+
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_FAILURE,
+ priv->self->id, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_packet_id (packet, priv->packet_id++);
+ gibber_r_multicast_packet_failure_add_senders (packet, failures,
+ NULL);
+ add_packet_depends (transport, packet);
+
+ str = g_array_uint32_to_str (failures);
+ DEBUG_TRANSPORT (transport, "Sending out failure: %s", str);
+ g_free (str);
+
+ gibber_r_multicast_sender_push (priv->self, packet);
+
+ sendout_packet (transport, packet, NULL);
+ g_object_unref (packet);
+}
+
+void
+gibber_r_multicast_causal_transport_send_join (
+ GibberRMulticastCausalTransport *transport, GArray *failures) {
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+ GibberRMulticastPacket *packet;
+
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_JOIN,
+ priv->self->id, priv->transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_packet_id (packet, priv->packet_id++);
+ add_packet_depends (transport, packet);
+ gibber_r_multicast_packet_join_add_failures (packet, failures, NULL);
+
+ gibber_r_multicast_sender_push (priv->self, packet);
+ sendout_packet (transport, packet, NULL);
+ g_object_unref (packet);
+}
+
+GibberRMulticastSender *
+gibber_r_multicast_causal_transport_get_sender (
+ GibberRMulticastCausalTransport *transport,
+ guint32 sender_id)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+ GibberRMulticastSender *sender;
+
+ sender = gibber_r_multicast_sender_group_lookup (priv->sender_group,
+ sender_id);
+ g_assert (sender != NULL);
+
+ return sender;
+}
+
+GibberRMulticastSender *
+gibber_r_multicast_causal_transport_get_sender_by_name (
+ GibberRMulticastCausalTransport *transport,
+ const gchar *name)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ return gibber_r_multicast_sender_group_lookup_by_name (priv->sender_group,
+ name);
+}
+
+void
+gibber_r_multicast_causal_transport_remove_sender (
+ GibberRMulticastCausalTransport *transport, guint32 sender_id)
+{
+ GibberRMulticastCausalTransportPrivate *priv =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_PRIVATE (transport);
+
+ gibber_r_multicast_sender_group_remove (priv->sender_group, sender_id);
+}
+
diff --git a/salut/lib/gibber/gibber-r-multicast-causal-transport.h b/salut/lib/gibber/gibber-r-multicast-causal-transport.h
new file mode 100644
index 000000000..95292aba0
--- /dev/null
+++ b/salut/lib/gibber/gibber-r-multicast-causal-transport.h
@@ -0,0 +1,121 @@
+/*
+ * gibber-r-multicast-transport.h - Header for GibberRMulticastCausalTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_H__
+#define __GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_H__
+
+#include <glib-object.h>
+#include "gibber-transport.h"
+#include "gibber-r-multicast-sender.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberRMulticastCausalTransport GibberRMulticastCausalTransport;
+typedef struct _GibberRMulticastCausalTransportClass
+ GibberRMulticastCausalTransportClass;
+
+struct _GibberRMulticastCausalTransportClass {
+ GibberTransportClass parent_class;
+};
+
+struct _GibberRMulticastCausalTransport {
+ GibberTransport parent;
+ guint32 sender_id;
+};
+
+GType gibber_r_multicast_causal_transport_get_type (void);
+
+typedef struct {
+ GibberBuffer buffer;
+ const gchar *sender;
+ guint16 stream_id;
+ guint32 sender_id;
+} GibberRMulticastCausalBuffer;
+
+#define GIBBER_R_MULTICAST_CAUSAL_DEFAULT_STREAM 0
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_R_MULTICAST_CAUSAL_TRANSPORT \
+ (gibber_r_multicast_causal_transport_get_type ())
+#define GIBBER_R_MULTICAST_CAUSAL_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GIBBER_TYPE_R_MULTICAST_CAUSAL_TRANSPORT, \
+ GibberRMulticastCausalTransport))
+#define GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GIBBER_TYPE_R_MULTICAST_CAUSAL_TRANSPORT, \
+ GibberRMulticastCausalTransportClass))
+#define GIBBER_IS_R_MULTICAST_CAUSAL_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GIBBER_TYPE_R_MULTICAST_CAUSAL_TRANSPORT))
+#define GIBBER_IS_R_MULTICAST_CAUSAL_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GIBBER_TYPE_R_MULTICAST_CAUSAL_TRANSPORT))
+#define GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GIBBER_TYPE_R_MULTICAST_CAUSAL_TRANSPORT, \
+ GibberRMulticastCausalTransportClass))
+
+GibberRMulticastCausalTransport *gibber_r_multicast_causal_transport_new (
+ GibberTransport *transport, const gchar *name);
+
+gboolean gibber_r_multicast_causal_transport_connect (
+ GibberRMulticastCausalTransport *transport, gboolean initial,
+ GError **error);
+
+gboolean gibber_r_multicast_causal_transport_send (
+ GibberRMulticastCausalTransport *transport, guint16 stream_id,
+ const guint8 *data, gsize size, GError **error);
+
+GibberRMulticastSender *gibber_r_multicast_causal_transport_add_sender (
+ GibberRMulticastCausalTransport *transport, guint32 sender_id);
+
+void gibber_r_multicast_causal_transport_update_sender_start (
+ GibberRMulticastCausalTransport *transport, guint32 sender_id,
+ guint32 packet_id);
+
+guint32 gibber_r_multicast_causal_transport_send_attempt_join (
+ GibberRMulticastCausalTransport *transport, GArray *new_senders,
+ gboolean repeat);
+
+void gibber_r_multicast_causal_transport_stop_attempt_join (
+ GibberRMulticastCausalTransport *transport, guint32 attempt_join_id);
+
+void gibber_r_multicast_causal_transport_send_join (
+ GibberRMulticastCausalTransport *transport, GArray *failures);
+
+void gibber_r_multicast_causal_transport_send_failure (
+ GibberRMulticastCausalTransport *transport, GArray *failures);
+
+GibberRMulticastSender *gibber_r_multicast_causal_transport_get_sender (
+ GibberRMulticastCausalTransport *transport, guint32 sender_id);
+
+GibberRMulticastSender *gibber_r_multicast_causal_transport_get_sender_by_name (
+ GibberRMulticastCausalTransport *transport, const gchar *name);
+
+void gibber_r_multicast_causal_transport_remove_sender (
+ GibberRMulticastCausalTransport *transport, guint32 sender_id);
+
+void gibber_r_multicast_causal_transport_reset (
+ GibberRMulticastCausalTransport *transport);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_R_MULTICAST_CAUSAL_TRANSPORT_H__*/
diff --git a/salut/lib/gibber/gibber-r-multicast-packet.c b/salut/lib/gibber/gibber-r-multicast-packet.c
new file mode 100644
index 000000000..5710b6dd3
--- /dev/null
+++ b/salut/lib/gibber/gibber-r-multicast-packet.c
@@ -0,0 +1,917 @@
+/*
+ * gibber-r-multicast-packet.c - Source for GibberRMulticastPacket
+ * Copyright (C) 2007 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gibber-sockets.h"
+
+#include "gibber-r-multicast-packet.h"
+
+#define PACKET_VERSION 1
+
+#define PACKET_PREFIX { 'C', 'l', 'i', 'q', 'u', 'e' }
+#define PACKET_PREFIX_LENGTH 6
+
+/* Our packet header is the prefix, version (1 byte), type (1 byte)
+ * and sender id (4 bytes) */
+#define PACKET_HEADER_SIZE (6 + PACKET_PREFIX_LENGTH)
+
+
+static void gibber_r_multicast_packet_sender_info_free (
+ GibberRMulticastPacketSenderInfo *sender_info);
+
+G_DEFINE_TYPE(GibberRMulticastPacket, gibber_r_multicast_packet, G_TYPE_OBJECT)
+
+GQuark gibber_r_multicast_packet_error_quark (void);
+
+/* private structure */
+typedef struct _GibberRMulticastPacketPrivate GibberRMulticastPacketPrivate;
+
+struct _GibberRMulticastPacketPrivate
+{
+ gboolean dispose_has_run;
+ /* Actually needed data size untill this point */
+ gsize size;
+
+ guint8 *data;
+ /* Maximum data size */
+ gsize max_data;
+};
+
+GQuark
+gibber_r_multicast_packet_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string (
+ "gibber_r_multicast_packet_error");
+
+ return quark;
+}
+
+
+#define GIBBER_R_MULTICAST_PACKET_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_R_MULTICAST_PACKET, \
+ GibberRMulticastPacketPrivate))
+
+static void
+gibber_r_multicast_packet_init (GibberRMulticastPacket *obj)
+{
+ GibberRMulticastPacket *self = GIBBER_R_MULTICAST_PACKET (obj);
+ self->version = PACKET_VERSION;
+ self->depends = g_array_new (FALSE, FALSE,
+ sizeof (GibberRMulticastPacketSenderInfo *));
+}
+
+static void gibber_r_multicast_packet_dispose (GObject *object);
+static void gibber_r_multicast_packet_finalize (GObject *object);
+
+static void
+gibber_r_multicast_packet_class_init (GibberRMulticastPacketClass *gibber_r_multicast_packet_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_r_multicast_packet_class);
+
+ g_type_class_add_private (gibber_r_multicast_packet_class, sizeof (GibberRMulticastPacketPrivate));
+
+ object_class->dispose = gibber_r_multicast_packet_dispose;
+ object_class->finalize = gibber_r_multicast_packet_finalize;
+
+}
+
+void
+gibber_r_multicast_packet_dispose (GObject *object)
+{
+ GibberRMulticastPacket *self = GIBBER_R_MULTICAST_PACKET (object);
+ GibberRMulticastPacketPrivate *priv =
+ GIBBER_R_MULTICAST_PACKET_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (gibber_r_multicast_packet_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_r_multicast_packet_parent_class)->dispose (object);
+}
+
+void
+gibber_r_multicast_packet_finalize (GObject *object)
+{
+ GibberRMulticastPacket *self = GIBBER_R_MULTICAST_PACKET (object);
+ GibberRMulticastPacketPrivate *priv =
+ GIBBER_R_MULTICAST_PACKET_GET_PRIVATE (self);
+ guint i;
+
+ for (i = 0; i < self->depends->len ; i++) {
+ gibber_r_multicast_packet_sender_info_free (
+ g_array_index (self->depends, GibberRMulticastPacketSenderInfo *, i));
+ }
+ g_array_unref (self->depends);
+
+ /* free any data held directly by the object here */
+ switch (self->type) {
+ case PACKET_TYPE_WHOIS_REPLY:
+ g_free (self->data.whois_reply.sender_name);
+ break;
+ case PACKET_TYPE_DATA:
+ g_free (self->data.data.payload);
+ break;
+ case PACKET_TYPE_ATTEMPT_JOIN:
+ g_array_unref (self->data.attempt_join.senders);
+ break;
+ case PACKET_TYPE_JOIN:
+ g_array_unref (self->data.join.failures);
+ break;
+ case PACKET_TYPE_FAILURE:
+ g_array_unref (self->data.failure.failures);
+ break;
+ default:
+ /* Nothing specific to free */;
+ }
+ g_free (priv->data);
+
+ G_OBJECT_CLASS (gibber_r_multicast_packet_parent_class)->finalize (object);
+}
+
+static GibberRMulticastPacketSenderInfo *
+gibber_r_multicast_packet_sender_info_new (guint32 sender_id,
+ guint32 expected_packet)
+{
+ GibberRMulticastPacketSenderInfo *result
+ = g_slice_new (GibberRMulticastPacketSenderInfo);
+ result->sender_id = sender_id;
+ result->packet_id = expected_packet;
+
+ return result;
+}
+
+static void
+gibber_r_multicast_packet_sender_info_free (
+ GibberRMulticastPacketSenderInfo *sender_info)
+{
+ g_slice_free (GibberRMulticastPacketSenderInfo, sender_info);
+}
+
+/* Start a new packet */
+GibberRMulticastPacket *
+gibber_r_multicast_packet_new (GibberRMulticastPacketType type,
+ guint32 sender, gsize max_size)
+{
+ GibberRMulticastPacket *result =
+ g_object_new (GIBBER_TYPE_R_MULTICAST_PACKET, NULL);
+ GibberRMulticastPacketPrivate *priv =
+ GIBBER_R_MULTICAST_PACKET_GET_PRIVATE(result);
+
+ /* Fixme do this using properties */
+ result->type = type;
+ result->sender = sender;
+
+ priv->max_data = max_size;
+
+ switch (result->type) {
+ case PACKET_TYPE_ATTEMPT_JOIN:
+ result->data.attempt_join.senders = g_array_new (FALSE, FALSE,
+ sizeof (guint32));
+ break;
+ case PACKET_TYPE_JOIN:
+ result->data.join.failures = g_array_new (FALSE, FALSE,
+ sizeof (guint32));
+ break;
+ case PACKET_TYPE_FAILURE:
+ result->data.failure.failures = g_array_new (FALSE, FALSE,
+ sizeof (guint32));
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+gboolean
+gibber_r_multicast_packet_add_sender_info (GibberRMulticastPacket *packet,
+ guint32 sender_id, guint32 packet_id, GError **error)
+{
+ GibberRMulticastPacketSenderInfo *s =
+ gibber_r_multicast_packet_sender_info_new (sender_id, packet_id);
+ GibberRMulticastPacketPrivate *priv =
+ GIBBER_R_MULTICAST_PACKET_GET_PRIVATE (packet);
+
+ g_assert (priv->data == NULL);
+ g_assert (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet)
+ || packet->type == PACKET_TYPE_SESSION);
+
+ g_array_append_val (packet->depends, s);
+
+ return TRUE;
+}
+
+void
+
+gibber_r_multicast_packet_set_packet_id (GibberRMulticastPacket *packet,
+ guint32 packet_id)
+{
+ g_assert (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET(packet));
+ packet->packet_id = packet_id;
+}
+
+void
+gibber_r_multicast_packet_set_data_info (GibberRMulticastPacket *packet,
+ guint16 stream_id, guint8 flags, guint32 size)
+{
+ g_assert (packet->type == PACKET_TYPE_DATA);
+
+ packet->data.data.flags = flags;
+ packet->data.data.total_size = size;
+ packet->data.data.stream_id = stream_id;
+}
+
+void
+gibber_r_multicast_packet_set_repair_request_info (
+ GibberRMulticastPacket *packet, guint32 sender_id, guint32 packet_id)
+{
+ g_assert (packet->type == PACKET_TYPE_REPAIR_REQUEST);
+
+ packet->data.repair_request.packet_id = packet_id;
+ packet->data.repair_request.sender_id = sender_id;
+}
+
+void
+gibber_r_multicast_packet_set_whois_request_info (
+ GibberRMulticastPacket *packet,
+ guint32 sender_id)
+{
+ g_assert (packet->type == PACKET_TYPE_WHOIS_REQUEST);
+
+ packet->data.whois_request.sender_id = sender_id;
+}
+
+void
+gibber_r_multicast_packet_set_whois_reply_info (GibberRMulticastPacket *packet,
+ const gchar *name)
+{
+ g_assert (packet->type == PACKET_TYPE_WHOIS_REPLY);
+
+ packet->data.whois_reply.sender_name = g_strdup (name);
+}
+
+
+static gsize
+gibber_r_multicast_packet_calculate_size (GibberRMulticastPacket *packet)
+{
+
+ /* 8 bit type, 8 bit version, 32 bit sender */
+ gsize result = PACKET_HEADER_SIZE;
+
+ if (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet)) {
+ /* 32 bit packet id, 8 bit nr sender info */
+ result += 5;
+ /* 32 bit sender id, 32 bit packet id */
+ result += 8 * packet->depends->len;
+ result += packet->data.data.payload_size;
+ }
+
+ switch (packet->type) {
+ case PACKET_TYPE_WHOIS_REQUEST:
+ /* 32 bit sender id */
+ result += 4;
+ break;
+ case PACKET_TYPE_WHOIS_REPLY:
+ g_assert (packet->data.whois_reply.sender_name != NULL);
+ result += 1 + strlen (packet->data.whois_reply.sender_name);
+ break;
+ case PACKET_TYPE_DATA:
+ /* 8 bit flags, 32 bit data size, 16 bit stream id */
+ result += 7;
+ break;
+ case PACKET_TYPE_REPAIR_REQUEST:
+ /* 32 bit packet id and 32 sender id*/
+ result += 8;
+ break;
+ case PACKET_TYPE_ATTEMPT_JOIN:
+ /* 8 bit nr of senders, 32 bit per sender */
+ result += 1 + 4 * packet->data.attempt_join.senders->len;
+ break;
+ case PACKET_TYPE_JOIN:
+ /* 8 bit nr of senders, 32 bit per failure */
+ result += 1 + 4 * packet->data.join.failures->len;
+ break;
+ case PACKET_TYPE_FAILURE:
+ /* 8 bit nr of senders, 32 bit per failure */
+ result += 1 + 4 * packet->data.failure.failures->len;
+ break;
+ case PACKET_TYPE_SESSION:
+ /* 8 bit nr sender info + N times 32 bit sender id, 32 bit packet id
+ */
+ result += 1 + 8 * packet->depends->len;
+ break;
+ default:
+ /* Nothing to add */;
+ }
+
+ return result;
+}
+
+static void
+add_guint8 (guint8 *data, gsize length, gsize *offset, guint8 i)
+{
+ g_assert (*offset + 1 <= length);
+ *(data + *offset) = i;
+ (*offset)++;
+}
+
+static guint8
+get_guint8 (const guint8 *data, gsize length, gsize *offset)
+{
+ guint8 i;
+ g_assert (*offset + 1 <= length);
+ i = *(data + *offset);
+ (*offset)++;
+ return i;
+}
+
+static void
+add_guint16 (guint8 *data, gsize length, gsize *offset, guint16 i)
+{
+ guint16 ni = htons (i);
+
+ g_assert (*offset + 2 <= length);
+
+ memcpy (data + *offset, &ni, 2);
+ (*offset) += 2;
+}
+
+static guint16
+get_guint16 (const guint8 *data, gsize length, gsize *offset)
+{
+ guint16 ni;
+ g_assert (*offset + 2 <= length);
+
+ memcpy (&ni, data + *offset, 2);
+ (*offset) += 2;
+
+ return ntohs (ni);
+}
+
+static void
+add_guint32 (guint8 *data, gsize length, gsize *offset, guint32 i)
+{
+ guint32 ni = htonl (i);
+
+ g_assert (*offset + 4 <= length);
+
+ memcpy (data + *offset, &ni, 4);
+ (*offset) += 4;
+}
+
+static guint32
+get_guint32 (const guint8 *data, gsize length, gsize *offset)
+{
+ guint32 ni;
+
+ g_assert (*offset + 4 <= length);
+
+ memcpy (&ni, data + *offset, 4);
+ (*offset) += 4;
+ return ntohl (ni);
+}
+
+static void
+add_string (guint8 *data, gsize length, gsize *offset, const gchar *str)
+{
+ gsize len = strlen (str);
+
+ g_assert (len < G_MAXUINT8);
+ add_guint8 (data, length, offset, len);
+
+ g_assert (*offset + len <= length);
+ memcpy (data + *offset, str, len);
+ (*offset) += len;
+}
+
+static gchar *
+get_string (const guint8 *data, gsize length, gsize *offset)
+{
+ gsize len;
+ gchar *str;
+
+ if (*offset + 1 > length)
+ return NULL;
+
+ len = get_guint8 (data, length, offset);
+
+ if (*offset + len > length)
+ return NULL;
+
+ str = g_strndup ((gchar *) data + *offset, len);
+ (*offset) += len;
+ return str;
+}
+
+static void
+add_sender_info (guint8 *data, gsize length, gsize *offset, GArray *senders)
+{
+ guint i;
+
+ add_guint8 (data, length, offset, senders->len);
+
+ for (i = 0; i < senders->len; i++)
+ {
+ GibberRMulticastPacketSenderInfo *info =
+ g_array_index (senders, GibberRMulticastPacketSenderInfo *, i);
+ add_guint32 (data, length, offset, info->sender_id);
+ add_guint32 (data, length, offset, info->packet_id);
+ }
+}
+
+static gboolean
+get_sender_info (guint8 *data, gsize length, gsize *offset, GArray *depends)
+{
+ guint8 nr_items;
+
+ if (*offset + 1 > length)
+ return FALSE;
+
+ nr_items = get_guint8 (data, length, offset);
+
+ for (; nr_items > 0; nr_items--)
+ {
+ GibberRMulticastPacketSenderInfo *sender_info;
+ guint32 sender_id;
+ guint32 packet_id;
+
+ if (*offset + 8 > length)
+ return FALSE;
+
+ sender_id = get_guint32 (data, length, offset);
+ packet_id = get_guint32 (data, length, offset);
+ sender_info =
+ gibber_r_multicast_packet_sender_info_new (sender_id, packet_id);
+ g_array_append_val (depends, sender_info);
+ }
+
+ return TRUE;
+}
+
+static void
+packet_add_prefix (guint8 *data, gsize length, gsize *offset)
+{
+ guint8 prefix[] = PACKET_PREFIX;
+
+ g_assert (*offset + PACKET_PREFIX_LENGTH <= length);
+
+ memcpy (data + *offset, prefix, PACKET_PREFIX_LENGTH);
+ *offset += PACKET_PREFIX_LENGTH;
+}
+
+static void
+gibber_r_multicast_packet_build (GibberRMulticastPacket *packet)
+{
+ GibberRMulticastPacketPrivate *priv =
+ GIBBER_R_MULTICAST_PACKET_GET_PRIVATE (packet);
+ gsize needed_size;
+
+ if (priv->data != NULL)
+ {
+ /* Already serialized return cached version */
+ return;
+ }
+
+ needed_size = gibber_r_multicast_packet_calculate_size (packet);
+
+ g_assert (needed_size <= priv->max_data);
+
+ /* Trim down the maximum data size to what we actually need */
+ priv->max_data = needed_size;
+ priv->data = g_malloc0 (priv->max_data);
+ priv->size = 0;
+
+ packet_add_prefix (priv->data, priv->max_data, &(priv->size));
+ add_guint8 (priv->data, priv->max_data, &(priv->size), packet->version);
+ add_guint8 (priv->data, priv->max_data, &(priv->size), packet->type);
+ add_guint32 (priv->data, priv->max_data, &(priv->size), packet->sender);
+
+ if (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet))
+ {
+ /* Add common reliable packet data */
+ add_guint32 (priv->data, priv->max_data, &(priv->size),
+ packet->packet_id);
+ add_sender_info (priv->data, priv->max_data, &(priv->size),
+ packet->depends);
+ }
+
+ switch (packet->type) {
+ case PACKET_TYPE_WHOIS_REQUEST:
+ add_guint32 (priv->data, priv->max_data, &(priv->size),
+ packet->data.whois_request.sender_id);
+ break;
+ case PACKET_TYPE_WHOIS_REPLY:
+ add_string (priv->data, priv->max_data, &(priv->size),
+ packet->data.whois_reply.sender_name);
+ break;
+ case PACKET_TYPE_DATA:
+ add_guint8 (priv->data, priv->max_data, &(priv->size),
+ packet->data.data.flags);
+ add_guint16 (priv->data, priv->max_data, &(priv->size),
+ packet->data.data.stream_id);
+ add_guint32 (priv->data, priv->max_data, &(priv->size),
+ packet->data.data.total_size);
+
+ g_assert (priv->size + packet->data.data.payload_size == priv->max_data);
+
+ memcpy (priv->data + priv->size, packet->data.data.payload,
+ packet->data.data.payload_size);
+ priv->size += packet->data.data.payload_size;
+ break;
+ case PACKET_TYPE_REPAIR_REQUEST:
+ add_guint32 (priv->data, priv->max_data, &(priv->size),
+ packet->data.repair_request.sender_id);
+ add_guint32 (priv->data, priv->max_data, &(priv->size),
+ packet->data.repair_request.packet_id);
+ break;
+ case PACKET_TYPE_ATTEMPT_JOIN: {
+ guint i;
+ add_guint8 (priv->data, priv->max_data, &(priv->size),
+ packet->data.attempt_join.senders->len);
+
+ for (i = 0; i < packet->data.attempt_join.senders->len; i++) {
+ add_guint32 (priv->data, priv->max_data, &(priv->size),
+ g_array_index (packet->data.attempt_join.senders, guint32, i));
+ }
+ break;
+ }
+ case PACKET_TYPE_JOIN: {
+ guint i;
+ add_guint8 (priv->data, priv->max_data, &(priv->size),
+ packet->data.join.failures->len);
+
+ for (i = 0; i < packet->data.join.failures->len; i++) {
+ add_guint32 (priv->data, priv->max_data, &(priv->size),
+ g_array_index (packet->data.join.failures, guint32, i));
+ }
+ break;
+ }
+ case PACKET_TYPE_FAILURE: {
+ guint i;
+ add_guint8 (priv->data, priv->max_data, &(priv->size),
+ packet->data.failure.failures->len);
+
+ for (i = 0; i < packet->data.failure.failures->len; i++) {
+ add_guint32 (priv->data, priv->max_data, &(priv->size),
+ g_array_index (packet->data.failure.failures, guint32, i));
+ }
+ break;
+ }
+ case PACKET_TYPE_SESSION:
+ add_sender_info (priv->data, priv->max_data, &(priv->size),
+ packet->depends);
+ break;
+ case PACKET_TYPE_BYE:
+ break;
+ case PACKET_TYPE_NO_DATA:
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* If this fails our size precalculation is buggy */
+ g_assert (priv->size == priv->max_data);
+}
+
+gsize
+gibber_r_multicast_packet_add_payload (GibberRMulticastPacket *packet,
+ const guint8 *data, gsize size)
+{
+ GibberRMulticastPacketPrivate *priv =
+ GIBBER_R_MULTICAST_PACKET_GET_PRIVATE (packet);
+ gsize avail;
+
+ g_assert (packet->type == PACKET_TYPE_DATA);
+ g_assert (packet->data.data.payload == NULL);
+ g_assert (priv->data == NULL);
+
+ avail = MIN (size, priv->max_data -
+ gibber_r_multicast_packet_calculate_size (packet));
+
+ packet->data.data.payload = g_memdup (data, avail);
+ packet->data.data.payload_size = avail;
+
+ return avail;
+}
+
+static gboolean
+packet_check_prefix (const guint8 *data)
+{
+ int i;
+ guint8 prefix[] = PACKET_PREFIX;
+
+ for (i = 0; i < PACKET_PREFIX_LENGTH; i++)
+ if (data[i] != prefix[i])
+ return FALSE;
+
+ return TRUE;
+}
+
+#define GET_GUINT8(target) G_STMT_START { \
+ if (priv->size + 1 > priv->max_data) \
+ goto parse_error; \
+ target = get_guint8 (priv->data, priv->max_data, &(priv->size)); \
+} G_STMT_END
+
+#define GET_GUINT16(target) G_STMT_START { \
+ if (priv->size + 2 > priv->max_data) \
+ goto parse_error; \
+ target = get_guint16 (priv->data, priv->max_data, &(priv->size)); \
+} G_STMT_END
+
+#define GET_GUINT32(target) G_STMT_START { \
+ if (priv->size + 4 > priv->max_data) \
+ goto parse_error; \
+ target = get_guint32 (priv->data, priv->max_data, &(priv->size)); \
+} G_STMT_END
+
+/* Create a packet by parsing raw data, packet is immutable afterwards */
+GibberRMulticastPacket *
+gibber_r_multicast_packet_parse (const guint8 *data, gsize size,
+ GError **error)
+{
+ GibberRMulticastPacket *result = NULL;
+
+ GibberRMulticastPacketPrivate *priv;
+
+ if (size < PACKET_HEADER_SIZE || !packet_check_prefix (data))
+ goto parse_error;
+
+ result = g_object_new (GIBBER_TYPE_R_MULTICAST_PACKET, NULL);
+ priv = GIBBER_R_MULTICAST_PACKET_GET_PRIVATE (result);
+
+ priv->data = g_memdup (data, size);
+ priv->size = PACKET_PREFIX_LENGTH;
+ priv->max_data = size;
+
+ GET_GUINT8 (result->version);
+ if (result->version != PACKET_VERSION)
+ goto parse_error;
+
+ GET_GUINT8 (result->type);
+ GET_GUINT32 (result->sender);
+
+
+ if (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (result))
+ {
+ GET_GUINT32 (result->packet_id);
+ if (!get_sender_info (priv->data, priv->max_data, &(priv->size),
+ result->depends))
+ goto parse_error;
+ }
+
+ switch (result->type) {
+ case PACKET_TYPE_WHOIS_REQUEST:
+ GET_GUINT32 (result->data.whois_request.sender_id);
+ break;
+ case PACKET_TYPE_WHOIS_REPLY:
+ result->data.whois_reply.sender_name = get_string (priv->data,
+ priv->max_data, &(priv->size));
+ if (result->data.whois_reply.sender_name == NULL)
+ goto parse_error;
+ break;
+ case PACKET_TYPE_DATA:
+ GET_GUINT8 (result->data.data.flags);
+ GET_GUINT16 (result->data.data.stream_id);
+ GET_GUINT32 (result->data.data.total_size);
+
+ result->data.data.payload_size = priv->max_data - priv->size;
+ result->data.data.payload = g_memdup (priv->data + priv->size,
+ result->data.data.payload_size);
+ priv->size += result->data.data.payload_size;
+ break;
+ case PACKET_TYPE_REPAIR_REQUEST:
+ GET_GUINT32 (result->data.repair_request.sender_id);
+ GET_GUINT32 (result->data.repair_request.packet_id);
+ break;
+ case PACKET_TYPE_ATTEMPT_JOIN:
+ {
+ guint8 nr;
+ guint8 i;
+
+ GET_GUINT8 (nr);
+
+ result->data.attempt_join.senders = g_array_sized_new (FALSE, FALSE,
+ sizeof (guint32), nr);
+
+ for (i = 0; i < nr; i++)
+ {
+ guint32 sender;
+ GET_GUINT32 (sender);
+ gibber_r_multicast_packet_attempt_join_add_sender (result,
+ sender, NULL);
+ }
+ break;
+ }
+ case PACKET_TYPE_JOIN:
+ {
+ guint8 nr;
+ guint8 i;
+
+ GET_GUINT8 (nr);
+
+ result->data.join.failures = g_array_sized_new (FALSE, FALSE,
+ sizeof (guint32), nr);
+
+ for (i = 0; i < nr; i++) {
+ guint32 failure;
+
+ GET_GUINT32 (failure);
+ gibber_r_multicast_packet_join_add_failure (result,
+ failure, NULL);
+ }
+ break;
+ }
+ case PACKET_TYPE_FAILURE:
+ {
+ guint8 nr;
+ guint8 i;
+
+ GET_GUINT8 (nr);
+
+ result->data.failure.failures = g_array_sized_new (FALSE, FALSE,
+ sizeof (guint32), nr);
+
+ for (i = 0; i < nr; i++)
+ {
+ guint32 sender;
+
+ GET_GUINT32 (sender);
+ gibber_r_multicast_packet_failure_add_sender (result,
+ sender, NULL);
+ }
+ break;
+ }
+ case PACKET_TYPE_SESSION:
+ if (!get_sender_info (priv->data, priv->max_data, &(priv->size),
+ result->depends))
+ goto parse_error;
+ break;
+ case PACKET_TYPE_NO_DATA:
+ case PACKET_TYPE_BYE:
+ break;
+ default:
+ goto parse_error;
+ }
+
+ if (priv->size != priv->max_data)
+ goto parse_error;
+
+ return result;
+
+parse_error:
+ if (result != NULL)
+ g_object_unref (result);
+
+ g_set_error (error,
+ GIBBER_R_MULTICAST_PACKET_ERROR,
+ GIBBER_R_MULTICAST_PACKET_ERROR_PARSE_ERROR,
+ "Failed to parse packet");
+
+ return NULL;
+}
+
+/* Get the packets payload */
+guint8 *
+gibber_r_multicast_packet_get_payload (GibberRMulticastPacket *packet,
+ gsize *size)
+{
+ g_assert (packet->type == PACKET_TYPE_DATA);
+ g_assert (size != NULL);
+
+ *size = packet->data.data.payload_size;
+
+ return packet->data.data.payload;
+}
+
+/* Get the packets raw data, packet is immutable after this call */
+guint8 *
+gibber_r_multicast_packet_get_raw_data (GibberRMulticastPacket *packet,
+ gsize *size)
+{
+ GibberRMulticastPacketPrivate *priv =
+ GIBBER_R_MULTICAST_PACKET_GET_PRIVATE (packet);
+
+ /* Ensure the packet is serialized */
+ gibber_r_multicast_packet_build (packet);
+
+ *size = priv->size;
+
+ return priv->data;
+}
+
+gboolean
+gibber_r_multicast_packet_attempt_join_add_sender (
+ GibberRMulticastPacket *packet,
+ guint32 sender,
+ GError **error)
+{
+ g_assert (packet->type == PACKET_TYPE_ATTEMPT_JOIN);
+
+ g_array_append_val (packet->data.attempt_join.senders, sender);
+
+ return TRUE;
+}
+
+/* Add senders that have failed */
+gboolean
+gibber_r_multicast_packet_attempt_join_add_senders (
+ GibberRMulticastPacket *packet, GArray *senders, GError **error)
+{
+ g_assert (packet->type == PACKET_TYPE_ATTEMPT_JOIN);
+
+ g_array_append_vals (packet->data.attempt_join.senders, senders->data,
+ senders->len);
+
+ return TRUE;
+}
+
+
+gboolean
+gibber_r_multicast_packet_join_add_failure (GibberRMulticastPacket *packet,
+ guint32 failure, GError **error)
+{
+ g_assert (packet->type == PACKET_TYPE_JOIN);
+
+ g_array_append_val (packet->data.join.failures, failure);
+
+ return TRUE;
+}
+
+gboolean
+gibber_r_multicast_packet_join_add_failures (GibberRMulticastPacket *packet,
+ GArray *failures, GError **error)
+{
+ g_assert (packet->type == PACKET_TYPE_JOIN);
+
+ g_array_append_vals (packet->data.join.failures, failures->data,
+ failures->len);
+
+ return TRUE;
+}
+
+gboolean
+gibber_r_multicast_packet_failure_add_sender (GibberRMulticastPacket *packet,
+ guint32 sender, GError **error)
+{
+ g_assert (packet->type == PACKET_TYPE_FAILURE);
+
+ g_array_append_val (packet->data.failure.failures, sender);
+
+ return TRUE;
+}
+
+gboolean
+gibber_r_multicast_packet_failure_add_senders (
+ GibberRMulticastPacket *packet, GArray *senders, GError **error)
+{
+ g_assert (packet->type == PACKET_TYPE_FAILURE);
+
+ g_array_append_vals (packet->data.failure.failures, senders->data,
+ senders->len);
+
+ return TRUE;
+}
+
+gint32
+gibber_r_multicast_packet_diff (guint32 from, guint32 to)
+{
+ if (from > (G_MAXUINT32 - 0xffff) && to < 0xffff)
+ return G_MAXUINT32 - from + to + 1;
+
+ if (to > (G_MAXUINT32 - 0xffff) && from < 0xffff)
+ return - from - (G_MAXUINT32 - to) - 1;
+
+ if (from > to)
+ return -MIN(from - to, G_MAXINT);
+
+ return MIN(to - from, G_MAXINT);
+}
diff --git a/salut/lib/gibber/gibber-r-multicast-packet.h b/salut/lib/gibber/gibber-r-multicast-packet.h
new file mode 100644
index 000000000..2931fcadc
--- /dev/null
+++ b/salut/lib/gibber/gibber-r-multicast-packet.h
@@ -0,0 +1,262 @@
+/*
+ * gibber-r-multicast-packet.h - Header for GibberRMulticastPacket
+ * Copyright (C) 2007 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_R_MULTICAST_PACKET_H__
+#define __GIBBER_R_MULTICAST_PACKET_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+GQuark gibber_r_multicast_packet_error_quark (void);
+#define GIBBER_R_MULTICAST_PACKET_ERROR \
+ gibber_r_multicast_packet_error_quark ()
+
+typedef enum {
+ GIBBER_R_MULTICAST_PACKET_ERROR_PARSE_ERROR
+} GibberRMulticastPacketErrors;
+
+typedef enum {
+ /* Unreliable packets */
+ PACKET_TYPE_WHOIS_REQUEST = 0,
+ PACKET_TYPE_WHOIS_REPLY,
+ PACKET_TYPE_REPAIR_REQUEST,
+ PACKET_TYPE_SESSION,
+ /* Reliable packets */
+ FIRST_RELIABLE_PACKET = 0xf,
+ PACKET_TYPE_DATA = FIRST_RELIABLE_PACKET,
+ /* No data just acknowledgement */
+ PACKET_TYPE_NO_DATA,
+ /* Some nodes failed */
+ PACKET_TYPE_FAILURE,
+ /* Start a joining attempt */
+ PACKET_TYPE_ATTEMPT_JOIN,
+ /* The real join */
+ PACKET_TYPE_JOIN,
+ /* Leaving now, bye */
+ PACKET_TYPE_BYE,
+ PACKET_TYPE_INVALID
+} GibberRMulticastPacketType;
+
+#define GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET(p) \
+ (p->type >= FIRST_RELIABLE_PACKET && p->type < PACKET_TYPE_INVALID)
+
+typedef struct {
+ guint32 sender_id;
+ guint32 packet_id;
+} GibberRMulticastPacketSenderInfo;
+
+struct _GibberRMulticastPacketClass {
+ GObjectClass parent_class;
+};
+
+typedef struct _GibberRMulticastWhoisRequestPacket
+ GibberRMulticastWhoisRequestPacket;
+struct _GibberRMulticastWhoisRequestPacket {
+ guint32 sender_id;
+};
+
+typedef struct _GibberRMulticastWhoisReplyPacket
+ GibberRMulticastWhoisReplyPacket;
+
+struct _GibberRMulticastWhoisReplyPacket {
+ gchar *sender_name;
+};
+
+#define GIBBER_R_MULTICAST_DATA_PACKET_START 0x1
+#define GIBBER_R_MULTICAST_DATA_PACKET_END 0x2
+
+typedef struct _GibberRMulticastDataPacket GibberRMulticastDataPacket;
+struct _GibberRMulticastDataPacket {
+ /* These are actually 24 bits in the wire protocol */
+ guint8 flags;
+ guint32 total_size;
+
+ /* payload */
+ guint8 *payload;
+ gsize payload_size;
+
+ /* substream id */
+ guint16 stream_id;
+};
+
+typedef struct _GibberRMulticastRepairRequestPacket
+ GibberRMulticastRepairRequestPacket;
+struct _GibberRMulticastRepairRequestPacket {
+ /* Sender identifier */
+ guint32 sender_id;
+ /* packet identifier */
+ guint32 packet_id;
+};
+
+typedef struct _GibberRMulticastAttemptJoinPacket
+ GibberRMulticastAttemptJoinPacket;
+struct _GibberRMulticastAttemptJoinPacket {
+ /* Unknown sender identifiers */
+ GArray *senders;
+};
+
+typedef struct _GibberRMulticastFailurePacket GibberRMulticastFailurePacket;
+struct _GibberRMulticastFailurePacket {
+ /* failed sender identifiers */
+ GArray *failures;
+};
+
+typedef struct _GibberRMulticastJoinPacket
+ GibberRMulticastJoinPacket;
+struct _GibberRMulticastJoinPacket {
+ /* Unknown sender identifiers */
+ GArray *failures;
+};
+
+
+typedef struct _GibberRMulticastPacket GibberRMulticastPacket;
+typedef struct _GibberRMulticastPacketClass GibberRMulticastPacketClass;
+
+struct _GibberRMulticastPacket {
+ GObject parent;
+ GibberRMulticastPacketType type;
+ guint8 version;
+ /* sender */
+ guint32 sender;
+
+ /* packet identifier for reliable packets */
+ guint32 packet_id;
+
+ /* List of GibberRMulticastSenderInfo encoding dependency information for
+ * reliable packets or session information for session packets */
+ GArray *depends;
+
+ union {
+ GibberRMulticastWhoisRequestPacket whois_request;
+ GibberRMulticastWhoisReplyPacket whois_reply;
+ GibberRMulticastDataPacket data;
+ GibberRMulticastRepairRequestPacket repair_request;
+ GibberRMulticastAttemptJoinPacket attempt_join;
+ GibberRMulticastJoinPacket join;
+ GibberRMulticastFailurePacket failure;
+ } data;
+};
+
+GType gibber_r_multicast_packet_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_R_MULTICAST_PACKET \
+ (gibber_r_multicast_packet_get_type ())
+#define GIBBER_R_MULTICAST_PACKET(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_R_MULTICAST_PACKET, GibberRMulticastPacket))
+#define GIBBER_R_MULTICAST_PACKET_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_R_MULTICAST_PACKET, GibberRMulticastPacketClass))
+#define GIBBER_IS_R_MULTICAST_PACKET(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_R_MULTICAST_PACKET))
+#define GIBBER_IS_R_MULTICAST_PACKET_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_R_MULTICAST_PACKET))
+#define GIBBER_R_MULTICAST_PACKET_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_R_MULTICAST_PACKET, GibberRMulticastPacketClass))
+
+/* Start a new packet */
+GibberRMulticastPacket * gibber_r_multicast_packet_new (
+ GibberRMulticastPacketType type, guint32 sender, gsize max_size);
+
+/* Add depend if packet type is PACKET_TYPE_DATA otherwise add sender info if
+ * PACKET_TYPE_SESSION */
+gboolean gibber_r_multicast_packet_add_sender_info (
+ GibberRMulticastPacket *packet, guint32 receiver_id, guint32 packet_id,
+ GError **error);
+
+void gibber_r_multicast_packet_set_packet_id (GibberRMulticastPacket *packet,
+ guint32 packet_id);
+
+/* Set info for PACKET_TYPE_DATA packets */
+void gibber_r_multicast_packet_set_data_info (GibberRMulticastPacket *packet,
+ guint16 stream_id, guint8 flags, guint32 size);
+
+/* Set info for PACKET_TYPE_REPAIR_REQUEST packets */
+void gibber_r_multicast_packet_set_repair_request_info (
+ GibberRMulticastPacket *packet, guint32 sender_id, guint32 packet_id);
+
+/* Set the info for PACKET_TYPE_WHOIS_REQUEST packets */
+void gibber_r_multicast_packet_set_whois_request_info (
+ GibberRMulticastPacket *packet, const guint32 sender_id);
+
+/* Set the info for PACKET_TYPE_WHOIS_REPLY packets */
+void gibber_r_multicast_packet_set_whois_reply_info (
+ GibberRMulticastPacket *packet, const gchar *sender_name);
+
+/* Add the actual payload in PACKET_TYPE_DATA packets.
+ * No extra data might be set/added after this (extra depends or payload..) */
+gsize gibber_r_multicast_packet_add_payload (GibberRMulticastPacket *packet,
+ const guint8 *data, gsize size);
+
+/* Create a packet by parsing raw data, packet is immutable */
+GibberRMulticastPacket * gibber_r_multicast_packet_parse (const guint8 *data,
+ gsize size, GError **error);
+
+/* Get the packets payload */
+guint8 * gibber_r_multicast_packet_get_payload (GibberRMulticastPacket *packet,
+ gsize *size);
+
+/* Get the packets raw data, packet is immutable after this call */
+guint8 * gibber_r_multicast_packet_get_raw_data (GibberRMulticastPacket *packet,
+ gsize *size);
+
+/* Add sender we want to start joining with to the attempt_join */
+gboolean gibber_r_multicast_packet_attempt_join_add_sender (
+ GibberRMulticastPacket *packet,
+ guint32 sender,
+ GError **error);
+
+gboolean gibber_r_multicast_packet_attempt_join_add_senders (
+ GibberRMulticastPacket *packet,
+ GArray *senders,
+ GError **error);
+
+/* Add senders that have failed */
+gboolean gibber_r_multicast_packet_join_add_failure (
+ GibberRMulticastPacket *packet,
+ guint32 failure,
+ GError **error);
+
+gboolean gibber_r_multicast_packet_join_add_failures (
+ GibberRMulticastPacket *packet,
+ GArray *failures,
+ GError **error);
+
+/* Add senders that have failed */
+gboolean gibber_r_multicast_packet_failure_add_sender (
+ GibberRMulticastPacket *packet,
+ guint32 sender,
+ GError **error);
+
+gboolean gibber_r_multicast_packet_failure_add_senders (
+ GibberRMulticastPacket *packet,
+ GArray *sender,
+ GError **error);
+
+
+/* Utility function to calculate the difference between two packet id's.
+ * Correctly handle overflow conditions and CLAMP to G_MAXINT32 and
+ * -G_MAXINT32 */
+gint32
+gibber_r_multicast_packet_diff (guint32 from, guint32 to);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_R_MULTICAST_PACKET_H__*/
diff --git a/salut/lib/gibber/gibber-r-multicast-sender.c b/salut/lib/gibber/gibber-r-multicast-sender.c
new file mode 100644
index 000000000..936dd87bf
--- /dev/null
+++ b/salut/lib/gibber/gibber-r-multicast-sender.c
@@ -0,0 +1,2156 @@
+/*
+ * gibber-r-multicast-sender.c - Source for GibberRMulticastSender
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gibber-r-multicast-sender.h"
+#include "gibber-util.h"
+#include "gibber-signals-marshal.h"
+
+#define DEBUG_FLAG DEBUG_RMULTICAST_SENDER
+#include "gibber-debug.h"
+
+#define DEBUG_SENDER(sender, format, ...) \
+ DEBUG("%s %x: " format, sender->name, sender->id, ##__VA_ARGS__)
+
+#define PACKET_CACHE_SIZE 256
+
+#define MIN_DO_REPAIR_TIMEOUT 50
+#define MAX_DO_REPAIR_TIMEOUT 100
+
+#define MIN_INITIAL_REPAIR_TIMEOUT 150
+#define MAX_INITIAL_REPAIR_TIMEOUT 250
+
+#define MIN_REPAIR_TIMEOUT 500
+#define MAX_REPAIR_TIMEOUT 800
+
+#define MIN_FIRST_WHOIS_TIMEOUT 50
+#define MAX_FIRST_WHOIS_TIMEOUT 200
+
+#define MIN_WHOIS_TIMEOUT 400
+#define MAX_WHOIS_TIMEOUT 600
+
+#define MIN_WHOIS_REPLY_TIMEOUT 50
+#define MAX_WHOIS_REPLY_TIMEOUT 200
+
+/* At least one packet must be popped every 5 minutes.. Reliable keepalives
+ * are send out every three minutes.. */
+#define MAX_PROGRESS_TIMEOUT 300000
+
+/* The senders name should be discovered within about 10 seconds or else it is
+ * fauly */
+#define NAME_DISCOVERY_TIME 10000
+
+#define GIBBER_R_MULTICAST_SENDER_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_R_MULTICAST_SENDER, \
+ GibberRMulticastSenderPrivate))
+
+/* private structure */
+typedef struct _GibberRMulticastSenderPrivate GibberRMulticastSenderPrivate;
+
+static void set_state (GibberRMulticastSender *sender,
+ GibberRMulticastSenderState state);
+
+struct _GibberRMulticastSenderPrivate
+{
+ gboolean dispose_has_run;
+ /* hash table with packets */
+ GHashTable *packet_cache;
+
+ /* Table with acks per sender
+ * guint32 * => owned AckInfo * */
+ GHashTable *acks;
+
+ /* Sendergroup to which we belong */
+ GibberRMulticastSenderGroup *group;
+
+ /* Very first packet number in the current window */
+ guint32 first_packet;
+
+ /* whois reply/request timer */
+ guint whois_timer;
+
+ /* timer untill which a failure even occurs */
+ guint fail_timer;
+
+ /* Whether we are holding back data currently */
+ gboolean holding_data;
+ guint32 holding_point;
+
+ /* Whether we went know the data starting point or not */
+ gboolean start_data;
+ guint32 start_point;
+
+ /* Endpoint is just there in case we are in failure mode */
+ guint32 end_point;
+};
+
+typedef struct {
+ guint32 sender_id;
+ guint32 packet_id;
+ /* First packet that had this ack */
+ guint32 first_packet_id;
+} AckInfo;
+
+static void
+ack_info_free (gpointer data)
+{
+ g_slice_free (AckInfo, data);
+}
+
+static AckInfo *
+ack_info_new (guint32 sender_id)
+{
+ AckInfo *result;
+ result = g_slice_new0 (AckInfo);
+ result->sender_id = sender_id;
+ return result;
+}
+
+
+struct _group_ht_data {
+ GibberRMulticastSenderGroup *group;
+ GibberRMulticastSender *target;
+ GibberRMulticastSender *sender;
+};
+
+static AckInfo *
+gibber_r_multicast_sender_get_ackinfo (GibberRMulticastSender *sender,
+ guint32 sender_id);
+
+GibberRMulticastSenderGroup *
+gibber_r_multicast_sender_group_new (void)
+{
+ GibberRMulticastSenderGroup *result;
+ result = g_slice_new0 (GibberRMulticastSenderGroup);
+
+ result->senders = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+ result->pop_queue = g_queue_new ();
+ result->pending_removal = g_ptr_array_new ();
+ return result;
+}
+
+void
+gibber_r_multicast_sender_group_free (GibberRMulticastSenderGroup *group)
+{
+ GHashTable *h;
+ guint i;
+
+ g_assert (group->popping == FALSE);
+
+ h = group->senders;
+ group->senders = NULL;
+ g_hash_table_unref (h);
+
+ for (i = 0; i < group->pending_removal->len ; i++)
+ {
+ g_object_unref (G_OBJECT (
+ g_ptr_array_index (group->pending_removal, i)));
+ }
+
+ g_ptr_array_unref (group->pending_removal);
+
+ g_queue_free (group->pop_queue);
+ g_slice_free (GibberRMulticastSenderGroup, group);
+}
+
+static void
+stop_sender (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER(value);
+
+ gibber_r_multicast_sender_stop (sender);
+}
+
+void
+gibber_r_multicast_sender_group_stop (GibberRMulticastSenderGroup *group)
+{
+ g_hash_table_foreach (group->senders, stop_sender, NULL);
+ group->stopped = TRUE;
+}
+
+void
+gibber_r_multicast_sender_group_add (GibberRMulticastSenderGroup *group,
+ GibberRMulticastSender *sender)
+{
+ DEBUG ("Adding %x to sender group", sender->id);
+ g_hash_table_insert (group->senders, GUINT_TO_POINTER (sender->id), sender);
+}
+
+
+GibberRMulticastSender *
+gibber_r_multicast_sender_group_lookup (GibberRMulticastSenderGroup *group,
+ guint32 sender_id)
+{
+ return g_hash_table_lookup (group->senders, GUINT_TO_POINTER (sender_id));
+}
+
+static gboolean
+find_by_name (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (value);
+ const gchar *name = (gchar *) user_data;
+
+ if (sender->state == GIBBER_R_MULTICAST_SENDER_STATE_PENDING_REMOVAL)
+ return FALSE;
+
+ return !gibber_strdiff (sender->name, name);
+}
+
+GibberRMulticastSender *
+gibber_r_multicast_sender_group_lookup_by_name (
+ GibberRMulticastSenderGroup *group, const gchar *name)
+{
+ return g_hash_table_find (group->senders, find_by_name, (gpointer) name);
+}
+
+static void
+cleanup_acks (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (value);
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ g_hash_table_remove (priv->acks, (guint32 *) user_data);
+}
+
+static void
+gibber_r_multicast_sender_group_gc_acks (GibberRMulticastSenderGroup *group,
+ guint32 sender_id)
+{
+ guint i;
+ GibberRMulticastSender *s;
+ /* If there are no more senders for sender_id in the list, clean up the acks
+ */
+ if (g_hash_table_lookup (group->senders, GUINT_TO_POINTER (sender_id))
+ != NULL)
+ return;
+
+ for (i = 0; i < group->pending_removal->len ; i++)
+ {
+ s = GIBBER_R_MULTICAST_SENDER (
+ g_ptr_array_index (group->pending_removal, i));
+ if (s->id == sender_id)
+ return;
+ }
+
+ g_hash_table_foreach (group->senders, cleanup_acks, &sender_id);
+}
+
+void
+gibber_r_multicast_sender_group_remove (GibberRMulticastSenderGroup *group,
+ guint32 sender_id)
+{
+ GibberRMulticastSender *s;
+
+ DEBUG ("Removing %x from sender group", sender_id);
+
+ s = g_hash_table_lookup (group->senders, GUINT_TO_POINTER(sender_id));
+
+ if (s == NULL)
+ {
+ DEBUG ("Can't remove unknown sender id: %x", sender_id);
+ return;
+ }
+
+ g_queue_remove (group->pop_queue, s);
+ set_state (s, GIBBER_R_MULTICAST_SENDER_STATE_PENDING_REMOVAL);
+
+ if (gibber_r_multicast_sender_packet_cache_size (s) > 0)
+ {
+ DEBUG ("Keeping %x in cache, %d items left", sender_id,
+ gibber_r_multicast_sender_packet_cache_size (s));
+ gibber_r_multicast_sender_stop (s);
+ g_hash_table_steal (group->senders, GUINT_TO_POINTER (sender_id));
+ g_ptr_array_add (group->pending_removal, s);
+ }
+ else
+ {
+ g_hash_table_remove (group->senders, GUINT_TO_POINTER(sender_id));
+ gibber_r_multicast_sender_group_gc_acks (group, sender_id);
+ }
+
+}
+
+static void
+create_sender_array (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (value);
+ GArray *array = (GArray *) user_data;
+ AckInfo info;
+
+ if (sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_STOPPED)
+ return;
+
+ info.sender_id = sender->id;
+ info.packet_id = sender->next_input_packet;
+
+ g_array_append_val (array, info);
+}
+
+static void
+update_sender_acks (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (value);
+ GArray *array = (GArray *) user_data;
+ guint i;
+
+ if (sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_STOPPED)
+ return;
+
+ for (i = 0; i < array->len ; i++)
+ {
+ AckInfo *info = &g_array_index (array, AckInfo, i);
+ AckInfo *ack;
+
+ if (sender->id == info->sender_id)
+ continue;
+
+ if ((ack = gibber_r_multicast_sender_get_ackinfo (sender,
+ info->sender_id)) != NULL)
+ {
+ if (gibber_r_multicast_packet_diff (ack->packet_id,
+ info->packet_id) > 0)
+ info->packet_id = ack->packet_id;
+ }
+ else
+ {
+ g_array_remove_index_fast (array, i);
+ /* The last element is now placed at location i, so retry i */
+ i--;
+ continue;
+ }
+ }
+}
+
+static AckInfo *
+get_direct_ack (GibberRMulticastSender *sender, GibberRMulticastSender *target)
+{
+ AckInfo *ack;
+
+ ack = gibber_r_multicast_sender_get_ackinfo (sender, target->id);
+
+ if (G_LIKELY (ack != NULL))
+ {
+ /* Returning the direct ack if there is one */
+ if (G_LIKELY (gibber_r_multicast_packet_diff (
+ target->next_output_packet, ack->packet_id) >= 0))
+ return ack;
+ }
+
+ return NULL;
+}
+
+static gboolean
+find_indirect_ack (gpointer key, gpointer value, gpointer user_data)
+{
+ struct _group_ht_data *hd = (struct _group_ht_data *) user_data;
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (value);
+ AckInfo *target_ack, *ack;
+
+ if (sender == hd->sender)
+ return FALSE;
+
+ target_ack = get_direct_ack (sender, hd->target);
+
+ if (target_ack == NULL)
+ return FALSE;
+
+ ack = gibber_r_multicast_sender_get_ackinfo (hd->sender, sender->id);
+ if (ack == NULL)
+ return FALSE;
+
+ return gibber_r_multicast_packet_diff (target_ack->first_packet_id,
+ ack->packet_id) > 0;
+}
+
+static gboolean
+failure_not_acked (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (value);
+ struct _group_ht_data *hd = (struct _group_ht_data *) user_data;
+
+ if (sender->state == GIBBER_R_MULTICAST_SENDER_STATE_PENDING_REMOVAL)
+ return FALSE;
+
+ /* A failure is acked iff each sender has acked it's last packet (direct ack)
+ * or a sender acked a packet of another sender acking the failures last
+ * packet (indirect ack) or if the sender never has even heard of this node.
+ * */
+
+ if (get_direct_ack (sender, hd->target) != NULL)
+ return FALSE;
+
+ hd->sender = sender;
+
+ return g_hash_table_find (hd->group->senders, find_indirect_ack, hd) == NULL;
+}
+
+static gboolean
+can_gc_sender (GibberRMulticastSenderGroup *group,
+ GibberRMulticastSender *sender)
+{
+ struct _group_ht_data hd;
+ GibberRMulticastSender *ret;
+
+ hd.group = group;
+ hd.target = sender;
+
+ ret = g_hash_table_find (group->senders, failure_not_acked, &hd);
+
+ if (ret == NULL)
+ DEBUG_SENDER (sender, "Removed by GC");
+ else
+ DEBUG_SENDER (sender, "Not removed by GC because of %s (%x)",
+ ret->name, ret->id);
+
+ return ret == NULL;
+}
+
+static void
+gibber_r_multicast_sender_group_gc (GibberRMulticastSenderGroup *group)
+{
+ GArray *array;
+ guint i;
+
+ array = g_array_sized_new (FALSE, TRUE, sizeof (AckInfo),
+ g_hash_table_size (group->senders));
+
+ g_hash_table_foreach (group->senders, create_sender_array, array);
+ g_hash_table_foreach (group->senders, update_sender_acks, array);
+
+ for (i = 0; i < array->len ; i++)
+ {
+ AckInfo *info = &g_array_index (array, AckInfo, i);
+ GibberRMulticastSender *sender = g_hash_table_lookup (group->senders,
+ GUINT_TO_POINTER (info->sender_id));
+
+ gibber_r_multicast_sender_ack (sender, info->packet_id);
+ }
+
+ g_array_unref (array);
+
+ /* Check if we can remove pending removals */
+ for (i = 0; i < group->pending_removal->len ; i++)
+ {
+ GibberRMulticastSender *s;
+
+ s = GIBBER_R_MULTICAST_SENDER (g_ptr_array_index (group->pending_removal,
+ i));
+ if (can_gc_sender (group, s))
+ {
+ g_ptr_array_remove_index_fast (group->pending_removal, i);
+ gibber_r_multicast_sender_group_gc_acks (group, s->id);
+ g_object_unref (s);
+ /* Last entry has replaced i, so force a retry of i */
+ i--;
+ }
+ }
+}
+
+gboolean
+gibber_r_multicast_sender_group_push_packet (
+ GibberRMulticastSenderGroup *group, GibberRMulticastPacket *packet)
+{
+ gboolean handled = FALSE;
+ GibberRMulticastSender *sender;
+ guint i;
+
+ if (packet->type == PACKET_TYPE_WHOIS_REQUEST)
+ sender = gibber_r_multicast_sender_group_lookup (group,
+ packet->data.whois_request.sender_id);
+ else
+ sender = gibber_r_multicast_sender_group_lookup (group,
+ packet->sender);
+
+ switch (packet->type)
+ {
+ case PACKET_TYPE_WHOIS_REQUEST:
+ /* Pending removal nodes still reply to WHOIS_REQUEST to prevent new
+ * nodes from taking the same id */
+ if (sender == NULL)
+ {
+ GibberRMulticastSender *s;
+ for (i = 0; i < group->pending_removal->len ; i++)
+ {
+ s = GIBBER_R_MULTICAST_SENDER (
+ g_ptr_array_index (group->pending_removal, i));
+ if (s->id == packet->data.whois_request.sender_id)
+ {
+ sender = s;
+ break;
+ }
+ }
+ }
+ /* fallthrough */
+ case PACKET_TYPE_WHOIS_REPLY:
+ if (sender != NULL)
+ {
+ gibber_r_multicast_sender_whois_push (sender, packet);
+ handled = TRUE;
+ }
+ break;
+ case PACKET_TYPE_REPAIR_REQUEST:
+ {
+ GibberRMulticastSender *rsender;
+ guint32 sender_id = packet->data.repair_request.sender_id;
+ guint32 packet_id = packet->data.repair_request.packet_id;
+
+ rsender = gibber_r_multicast_sender_group_lookup (group, sender_id);
+
+ g_assert (sender_id != 0);
+
+ if (rsender != NULL &&
+ gibber_r_multicast_sender_repair_request (rsender, packet_id))
+ {
+ /* rsender took up the repair request. */
+ handled = TRUE;
+ break;
+ }
+
+ for (i = 0; i < group->pending_removal->len ; i++)
+ {
+ rsender = GIBBER_R_MULTICAST_SENDER (
+ g_ptr_array_index (group->pending_removal, i));
+ if (rsender->id == sender_id)
+ {
+ if (gibber_r_multicast_sender_repair_request (rsender,
+ packet_id))
+ {
+ handled = TRUE;
+ break;
+ }
+ }
+ }
+ DEBUG ("Ignoring repair request for unknown original sender");
+ break;
+ }
+ case PACKET_TYPE_SESSION:
+ /* Session message aren't handled by us. But if we know the sender it's
+ * at least not a foreign sender */
+ if (sender != NULL)
+ handled = TRUE;
+ break;
+ default:
+ if (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet))
+ {
+ if (sender != NULL
+ && sender->state > GIBBER_R_MULTICAST_SENDER_STATE_NEW )
+ {
+ gibber_r_multicast_sender_push (sender, packet);
+ handled = TRUE;
+ }
+ else
+ {
+ for (i = 0; i < group->pending_removal->len ; i++)
+ {
+ sender = GIBBER_R_MULTICAST_SENDER (
+ g_ptr_array_index (group->pending_removal, i));
+ if (sender->id == packet->sender)
+ {
+ /* Say we have handled a reliable packet if there is any
+ * node to remove with the right sender id.. This means
+ * we handle packets for a removed sender longer than
+ * strictly needed, but this doesn't hurt */
+ handled = TRUE;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ DEBUG ("Received unhandled packet type!!, ignoring");
+ }
+ }
+
+ return handled;
+}
+
+static void schedule_repair (GibberRMulticastSender *sender, guint32 id);
+static void schedule_do_repair (GibberRMulticastSender *sender, guint32 id);
+static void schedule_whois_request (GibberRMulticastSender *sender,
+ gboolean rescheduled);
+static gboolean name_discovery_failed_cb (gpointer data);
+static void schedule_progress_timer (GibberRMulticastSender *self);
+
+G_DEFINE_TYPE(GibberRMulticastSender, gibber_r_multicast_sender, G_TYPE_OBJECT)
+
+/* signal enum */
+enum
+{
+ REPAIR_REQUEST,
+ REPAIR_MESSAGE,
+ WHOIS_REPLY,
+ WHOIS_REQUEST,
+ NAME_DISCOVERED,
+ RECEIVED_DATA,
+ RECEIVED_CONTROL_PACKET,
+ FAILED,
+ LAST_SIGNAL
+};
+
+/* properties */
+enum {
+ PROP_SENDER_GROUP = 1,
+ LAST_PROPERTY
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct {
+ guint32 packet_id;
+ guint timeout;
+ gboolean repeating;
+ GibberRMulticastPacket *packet;
+ GibberRMulticastSender *sender;
+ gboolean acked;
+ gboolean popped;
+} PacketInfo;
+
+static void
+packet_info_free (gpointer data)
+{
+ PacketInfo *p = (PacketInfo *) data;
+ if (p->packet != NULL) {
+ g_object_unref (p->packet);
+ }
+
+ if (p->timeout != 0) {
+ g_source_remove (p->timeout);
+ }
+ g_slice_free (PacketInfo, data);
+}
+
+static PacketInfo *
+packet_info_new (GibberRMulticastSender*sender, guint32 packet_id)
+{
+ PacketInfo *result;
+ result = g_slice_new0 (PacketInfo);
+ result->packet_id = packet_id;
+ result->sender = sender;
+ return result;
+}
+
+static void
+gibber_r_multicast_sender_init (GibberRMulticastSender *obj)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (obj);
+
+ /* allocate any data required by the object here */
+ priv->packet_cache = g_hash_table_new_full (g_int_hash, g_int_equal,
+ NULL, packet_info_free);
+
+ priv->acks = g_hash_table_new_full (g_int_hash, g_int_equal,
+ NULL, ack_info_free);
+}
+
+static void gibber_r_multicast_sender_dispose (GObject *object);
+static void gibber_r_multicast_sender_finalize (GObject *object);
+
+static void
+gibber_r_multicast_sender_set_property (GObject *object,
+ guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (object);
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ switch (property_id) {
+ case PROP_SENDER_GROUP:
+ priv->group =
+ (GibberRMulticastSenderGroup *) g_value_get_pointer (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gibber_r_multicast_sender_class_init (
+ GibberRMulticastSenderClass *gibber_r_multicast_sender_class)
+{
+ GObjectClass *object_class =
+ G_OBJECT_CLASS (gibber_r_multicast_sender_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (gibber_r_multicast_sender_class,
+ sizeof (GibberRMulticastSenderPrivate));
+
+ object_class->dispose = gibber_r_multicast_sender_dispose;
+ object_class->finalize = gibber_r_multicast_sender_finalize;
+
+ object_class->set_property = gibber_r_multicast_sender_set_property;
+
+ signals[REPAIR_REQUEST] = g_signal_new ("repair-request",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_sender_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ signals[REPAIR_MESSAGE] = g_signal_new ("repair-message",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_sender_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GIBBER_TYPE_R_MULTICAST_PACKET);
+
+ signals[RECEIVED_DATA] = g_signal_new ("received-data",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_sender_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _gibber_signals_marshal_VOID__UINT_POINTER_ULONG,
+ G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_ULONG);
+
+ signals[RECEIVED_CONTROL_PACKET] = g_signal_new ("received-control-packet",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_sender_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GIBBER_TYPE_R_MULTICAST_PACKET);
+
+ signals[WHOIS_REPLY] = g_signal_new ("whois-reply",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_sender_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[WHOIS_REQUEST] = g_signal_new ("whois-request",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_sender_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[FAILED] = g_signal_new ("failed",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_sender_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[NAME_DISCOVERED] = g_signal_new ("name-discovered",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_sender_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ param_spec = g_param_spec_pointer ("sendergroup",
+ "Sender Group",
+ "Group of senders",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, PROP_SENDER_GROUP,
+ param_spec);
+}
+
+void
+gibber_r_multicast_sender_dispose (GObject *object)
+{
+ GibberRMulticastSender *self = GIBBER_R_MULTICAST_SENDER (object);
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (self);
+
+ DEBUG_SENDER (self, "disposing");
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ g_hash_table_unref (priv->packet_cache);
+ g_hash_table_unref (priv->acks);
+
+ if (priv->whois_timer != 0)
+ {
+ g_source_remove (priv->whois_timer);
+ priv->whois_timer = 0;
+ }
+
+ if (priv->fail_timer != 0)
+ {
+ g_source_remove (priv->fail_timer);
+ priv->fail_timer = 0;
+ }
+
+ if (G_OBJECT_CLASS (gibber_r_multicast_sender_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_r_multicast_sender_parent_class)->dispose (object);
+}
+
+void
+gibber_r_multicast_sender_finalize (GObject *object)
+{
+ GibberRMulticastSender *self = GIBBER_R_MULTICAST_SENDER (object);
+ /*GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (self);
+ */
+
+ /* free any data held directly by the object here */
+ g_free (self->name);
+
+ G_OBJECT_CLASS (gibber_r_multicast_sender_parent_class)->finalize (object);
+}
+
+static void
+set_state (GibberRMulticastSender *sender,
+ GibberRMulticastSenderState state)
+{
+ g_assert (sender->state <= state);
+
+ sender->state = state;
+}
+
+GibberRMulticastSender *
+gibber_r_multicast_sender_new (guint32 id, const gchar *name,
+ GibberRMulticastSenderGroup *group)
+{
+ GibberRMulticastSender *sender;
+ GibberRMulticastSenderPrivate *priv;
+
+ sender = g_object_new (GIBBER_TYPE_R_MULTICAST_SENDER, "sendergroup", group,
+ NULL);
+ priv = GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ g_assert (group != NULL);
+
+ sender->id = id;
+ sender->name = g_strdup (name);
+
+ if (sender->name == NULL)
+ {
+ schedule_whois_request (sender, FALSE);
+ priv->fail_timer = g_timeout_add (NAME_DISCOVERY_TIME,
+ name_discovery_failed_cb, sender);
+ }
+ else
+ {
+ schedule_progress_timer (sender);
+ }
+
+ return sender;
+}
+
+static void
+packet_info_try_gc (GibberRMulticastSender *sender, PacketInfo *info)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ guint32 packet_id, i;
+
+ if (!info->acked || !info->popped || info->repeating)
+ return;
+
+ packet_id = info->packet_id;
+ g_hash_table_remove (priv->packet_cache, &packet_id);
+
+ if (packet_id == priv->first_packet)
+ {
+ for (i = packet_id; i != sender->next_output_data_packet; i++)
+ if (g_hash_table_lookup (priv->packet_cache, &i) != NULL)
+ break;
+
+ priv->first_packet = i;
+ }
+}
+
+static void
+cancel_failure_timers (GibberRMulticastSender *sender)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ /* Cancel timers that are not needed anymore now the sender has failed */
+ if (priv->fail_timer != 0)
+ {
+ g_source_remove (priv->fail_timer);
+ priv->fail_timer = 0;
+ }
+
+ /* failed, no need to get our name anymore */
+ if (priv->whois_timer != 0)
+ {
+ g_source_remove (priv->whois_timer);
+ priv->whois_timer = 0;
+ }
+}
+
+static void
+signal_data (GibberRMulticastSender *sender, guint16 stream_id,
+ guint8 *data, gsize size)
+{
+ set_state (sender,
+ MAX(GIBBER_R_MULTICAST_SENDER_STATE_DATA_RUNNING, sender->state));
+
+ g_signal_emit (sender, signals[RECEIVED_DATA], 0, stream_id, data, size);
+}
+
+static void
+signal_control_packet (GibberRMulticastSender *sender,
+ GibberRMulticastPacket *packet)
+{
+ set_state (sender,
+ MAX (GIBBER_R_MULTICAST_SENDER_STATE_RUNNING, sender->state));
+
+ g_signal_emit (sender, signals[RECEIVED_CONTROL_PACKET], 0, packet);
+}
+
+static void
+signal_failure (GibberRMulticastSender *sender)
+{
+ if (sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ return;
+
+ DEBUG_SENDER (sender, "Signalling senders failure");
+ cancel_failure_timers (sender);
+ g_signal_emit (sender, signals[FAILED], 0);
+}
+
+static gboolean
+name_discovery_failed_cb (gpointer data)
+{
+ GibberRMulticastSender *self = GIBBER_R_MULTICAST_SENDER (data);
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (self);
+
+ DEBUG_SENDER (self, "Failed to discover name in time");
+
+ g_assert (priv->whois_timer != 0);
+
+ g_source_remove (priv->whois_timer);
+ priv->whois_timer = 0;
+ priv->fail_timer = 0;
+
+ signal_failure (self);
+
+ return FALSE;
+}
+
+static gboolean
+progress_failed_cb (gpointer data)
+{
+ GibberRMulticastSender *self = GIBBER_R_MULTICAST_SENDER (data);
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (self);
+
+ DEBUG_SENDER (self, "Failed to make progress in time");
+
+ priv->fail_timer = 0;
+
+ signal_failure (self);
+
+ return FALSE;
+}
+
+static void
+schedule_progress_timer (GibberRMulticastSender *self)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (self);
+
+ /* If we didn't discover the name, that timer is still running */
+ if (self->name == NULL)
+ return;
+
+ /* No need for a watchdog if it has failed already */
+ if (self->state >= GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ return;
+
+ if (priv->fail_timer != 0)
+ g_source_remove (priv->fail_timer);
+
+ priv->fail_timer = g_timeout_add (MAX_PROGRESS_TIMEOUT,
+ progress_failed_cb, self);
+}
+
+static void
+stop_whois_discovery (GibberRMulticastSender *self)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (self);
+
+ if (priv->whois_timer != 0)
+ {
+ g_source_remove (priv->whois_timer);
+ priv->whois_timer = 0;
+ }
+
+ if (priv->fail_timer != 0)
+ {
+ g_source_remove (priv->fail_timer);
+ priv->fail_timer = 0;
+ }
+
+}
+
+static void
+name_discovered (GibberRMulticastSender *self, const gchar *name)
+{
+ stop_whois_discovery (self);
+
+ self->name = g_strdup (name);
+ DEBUG_SENDER (self, "Name discovered");
+ g_signal_emit (self, signals[NAME_DISCOVERED], 0, self->name);
+
+ schedule_progress_timer (self);
+}
+
+static gboolean
+request_repair (gpointer data)
+{
+ PacketInfo *info = (PacketInfo *) data;
+
+ DEBUG_SENDER (info->sender, "Sending out repair request for 0x%x",
+ info->packet_id);
+
+ info->timeout = 0;
+ g_signal_emit (info->sender, signals[REPAIR_REQUEST], 0, info->packet_id);
+ schedule_repair (info->sender, info->packet_id);
+
+ return FALSE;
+}
+
+
+static void
+schedule_repair (GibberRMulticastSender *sender, guint32 id)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ PacketInfo *info;
+ guint timeout;
+
+ if (sender->state > GIBBER_R_MULTICAST_SENDER_STATE_STOPPED)
+ return;
+
+ info = g_hash_table_lookup (priv->packet_cache, &id);
+
+ if (info != NULL && (info->packet != NULL || info->timeout != 0)) {
+ return;
+ }
+
+ if (info == NULL)
+ {
+ info = packet_info_new (sender, id);
+ g_hash_table_insert (priv->packet_cache, &info->packet_id, info);
+ timeout = g_random_int_range (MIN_INITIAL_REPAIR_TIMEOUT,
+ MAX_INITIAL_REPAIR_TIMEOUT);
+ }
+ else
+ {
+ timeout = g_random_int_range (MIN_REPAIR_TIMEOUT, MAX_REPAIR_TIMEOUT);
+ }
+
+ info->timeout = g_timeout_add (timeout, request_repair, info);
+ DEBUG_SENDER (sender,
+ "Scheduled repair request for 0x%x in %d ms", id, timeout);
+}
+
+static gboolean
+do_repair (gpointer data)
+{
+ PacketInfo *info = (PacketInfo *) data;
+
+ g_assert (info != NULL && info->packet != NULL);
+
+ DEBUG_SENDER (info->sender, "Sending Repair message for 0x%x",
+ info->packet_id);
+
+ info->timeout = 0;
+ g_signal_emit (info->sender, signals[REPAIR_MESSAGE], 0, info->packet);
+
+ if (info->repeating)
+ {
+ schedule_do_repair (info->sender, info->packet_id);
+ }
+
+ return FALSE;
+}
+
+static void
+schedule_do_repair (GibberRMulticastSender *sender, guint32 id)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ PacketInfo *info;
+ guint timeout;
+
+ info = g_hash_table_lookup (priv->packet_cache, &id);
+
+ g_assert (info != NULL && info->packet != NULL);
+ if (info->timeout != 0)
+ {
+ /* Repair already scheduled, ignore */
+ return;
+ }
+
+ timeout = g_random_int_range (MIN_DO_REPAIR_TIMEOUT, MAX_DO_REPAIR_TIMEOUT);
+ info->timeout = g_timeout_add (timeout, do_repair, info);
+ DEBUG_SENDER (sender, "Scheduled repair for 0x%x in %d ms", id, timeout);
+}
+
+static gboolean
+do_whois_reply (gpointer data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (data);
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ DEBUG_SENDER (sender, "Sending out whois reply");
+ g_signal_emit (sender, signals[WHOIS_REPLY], 0);
+ priv->whois_timer = 0;
+
+ return FALSE;
+}
+
+static gboolean
+do_whois_request (gpointer data)
+{
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (data);
+
+ schedule_whois_request (sender, TRUE);
+
+ DEBUG_SENDER (sender, "Sending out whois request");
+ g_signal_emit (sender, signals[WHOIS_REQUEST], 0);
+
+ return FALSE;
+}
+
+static void
+schedule_whois_request (GibberRMulticastSender *sender, gboolean rescheduled)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ gint timeout;
+
+ if (sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ return;
+
+ if (rescheduled)
+ timeout = g_random_int_range (MIN_WHOIS_TIMEOUT, MAX_WHOIS_TIMEOUT);
+ else
+ timeout = g_random_int_range (MIN_FIRST_WHOIS_TIMEOUT,
+ MAX_FIRST_WHOIS_TIMEOUT);
+
+ DEBUG_SENDER (sender, "(Re)Scheduled whois request in %d ms", timeout);
+
+ if (priv->whois_timer != 0)
+ g_source_remove (priv->whois_timer);
+
+ priv->whois_timer = g_timeout_add (timeout, do_whois_request, sender);
+}
+
+static gboolean
+check_depends (GibberRMulticastSender *sender, GibberRMulticastPacket *packet,
+ gboolean data)
+{
+ guint i;
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ g_assert (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet));
+
+ for (i = 0; i < packet->depends->len; i++)
+ {
+ GibberRMulticastSender *s;
+ GibberRMulticastPacketSenderInfo *sender_info;
+ guint32 other;
+
+ sender_info = g_array_index (packet->depends,
+ GibberRMulticastPacketSenderInfo *, i);
+
+ s = gibber_r_multicast_sender_group_lookup (priv->group,
+ sender_info->sender_id);
+
+ if (s == NULL
+ || s->state == GIBBER_R_MULTICAST_SENDER_STATE_NEW
+ || s->state == GIBBER_R_MULTICAST_SENDER_STATE_UNKNOWN_FAILED)
+ {
+ DEBUG_SENDER (sender,
+ "Unknown node in dependency list of packet %x: %x",
+ sender_info->sender_id, packet->packet_id);
+ continue;
+ }
+
+ if (data)
+ other = s->next_output_data_packet;
+ else
+ other = s->next_output_packet;
+
+ if (gibber_r_multicast_packet_diff (sender_info->packet_id, other) < 0)
+ {
+ DEBUG_SENDER (sender,
+ "Waiting node %x to complete it's messages up to %x",
+ sender_info->sender_id, sender_info->packet_id);
+ if (s->state == GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ {
+ DEBUG_SENDER (sender,
+ "Asking failed node %x to complete it's messages up to %x",
+ sender_info->sender_id, sender_info->packet_id);
+ gibber_r_multicast_sender_update_end (s, sender_info->packet_id);
+ }
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+update_next_data_output_state (GibberRMulticastSender *self)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (self);
+
+ self->next_output_data_packet++;
+
+ for (; self->next_output_data_packet != self->next_output_packet;
+ self->next_output_data_packet++)
+ {
+ PacketInfo *p;
+ p = g_hash_table_lookup (priv->packet_cache,
+ &(self->next_output_data_packet));
+
+ if (p == NULL)
+ continue;
+
+ if (p->packet->type == PACKET_TYPE_DATA
+ && (p->packet->data.data.flags & GIBBER_R_MULTICAST_DATA_PACKET_END))
+ {
+ break;
+ }
+ }
+}
+
+static gboolean
+pop_data_packet (GibberRMulticastSender *sender)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ PacketInfo *p;
+ guint16 stream_id;
+
+ /* If we're holding before this, skip */
+ if (priv->holding_data &&
+ gibber_r_multicast_packet_diff (sender->next_output_data_packet,
+ priv->holding_point)
+ <= 0)
+ {
+ DEBUG_SENDER (sender, "Holding back data finishing at %x",
+ sender->next_output_data_packet);
+ return FALSE;
+ }
+
+ DEBUG_SENDER (sender, "Trying to pop data finishing at %x",
+ sender->next_output_data_packet);
+
+ p = g_hash_table_lookup (priv->packet_cache,
+ &sender->next_output_data_packet);
+ g_assert (p != NULL);
+
+ g_assert (p->packet->data.data.flags & GIBBER_R_MULTICAST_DATA_PACKET_END);
+
+ stream_id = p->packet->data.data.stream_id;
+
+ /* Backwards search for the start, validate the pieces and check the size */
+ if (!(p->packet->data.data.flags & GIBBER_R_MULTICAST_DATA_PACKET_START))
+ {
+ guint32 i;
+ gboolean found = FALSE;
+
+ for (i = p->packet->packet_id - 1;
+ gibber_r_multicast_packet_diff (priv->first_packet, i) >= 0; i--)
+ {
+ p = g_hash_table_lookup (priv->packet_cache, &i);
+ if (p == NULL)
+ continue;
+
+ if (p->packet->type == PACKET_TYPE_DATA
+ && p->packet->data.data.stream_id == stream_id
+ && (p->packet->data.data.flags &
+ GIBBER_R_MULTICAST_DATA_PACKET_START))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ /* If we couldn't find the start it must have happened before we
+ * joined the causal ordering */
+ DEBUG_SENDER (sender,
+ "Ignoring data starting before our first packet");
+ update_next_data_output_state (sender);
+ return TRUE;
+ }
+ }
+
+ /* p is guaranteed to be the PacketInfo of the first packet */
+
+ /* If there is data from before our startpoint, ignore it */
+ if (sender->state != GIBBER_R_MULTICAST_SENDER_STATE_DATA_RUNNING
+ && !priv->start_data)
+ {
+ DEBUG_SENDER (sender,
+ "Ignoring data as we don't have a data startpoint yet");
+ update_next_data_output_state (sender);
+ return TRUE;
+ }
+
+ if (priv->start_data &&
+ gibber_r_multicast_packet_diff (priv->start_point, p->packet_id) < 0)
+ {
+ DEBUG_SENDER (sender,
+ "Ignoring data from before the data startpoint");
+ update_next_data_output_state (sender);
+ return TRUE;
+ }
+
+
+ if (!check_depends (sender, p->packet, TRUE))
+ {
+ return FALSE;
+ }
+
+ /* Everything is fine, now do a forward pass to gather all the payload, we
+ * could have cached this info, but oh well */
+ DEBUG_SENDER (sender, "Popping data 0x%x -> 0x%x stream_id: %x",
+ p->packet_id, sender->next_output_data_packet,
+ p->packet->data.data.stream_id);
+
+ if (p->packet->packet_id == sender->next_output_data_packet)
+ {
+ gsize size;
+ guint8 *data;
+
+ data = gibber_r_multicast_packet_get_payload (p->packet, &size);
+
+ if (size != p->packet->data.data.total_size)
+ goto incorrect_data_size;
+
+ update_next_data_output_state (sender);
+ signal_data (sender, p->packet->data.data.stream_id, data, size);
+
+ p->popped = TRUE;
+ packet_info_try_gc (sender, p);
+ }
+ else
+ {
+ gsize off = 0;
+ guint8 *data = NULL, *d;
+ guint32 payload_size, i;
+ gsize size;
+
+ payload_size = p->packet->data.data.total_size;
+ data = g_malloc (payload_size);
+
+ for (i = p->packet_id ; i != sender->next_output_data_packet + 1 ; i++)
+ {
+ PacketInfo *tp = g_hash_table_lookup (priv->packet_cache, &i);
+
+ if (tp == NULL)
+ continue;
+
+ if (tp->packet->type == PACKET_TYPE_DATA
+ && tp->packet->data.data.stream_id == stream_id)
+ {
+ d = gibber_r_multicast_packet_get_payload (tp->packet, &size);
+ if (off + size > payload_size)
+ {
+ off += size;
+ break;
+ }
+
+ memcpy (data + off, d, size);
+ off += size;
+
+ tp->popped = TRUE;
+ packet_info_try_gc (sender, tp);
+ }
+ }
+
+ if (off != payload_size)
+ {
+ g_free (data);
+ goto incorrect_data_size;
+ }
+
+ update_next_data_output_state (sender);
+ signal_data (sender, stream_id, data, payload_size);
+ g_free (data);
+ }
+
+ return TRUE;
+
+incorrect_data_size:
+
+ DEBUG_SENDER (sender, "Data packet didn't have the claimed amount of data");
+ signal_failure (sender);
+ return FALSE;
+}
+
+static void
+update_acks (GibberRMulticastSender *sender, GibberRMulticastPacket *packet)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ guint i;
+ gboolean updated = FALSE;
+
+ for (i = 0; i < packet->depends->len; i++)
+ {
+ GibberRMulticastPacketSenderInfo *senderinfo;
+ AckInfo *info;
+
+ senderinfo = g_array_index (packet->depends,
+ GibberRMulticastPacketSenderInfo *, i);
+
+ info = (AckInfo *) g_hash_table_lookup (priv->acks,
+ &senderinfo->sender_id);
+
+ if (G_UNLIKELY(info == NULL))
+ {
+ info = ack_info_new (senderinfo->sender_id);
+ g_hash_table_insert (priv->acks, &info->sender_id, info);
+ info->packet_id = senderinfo->packet_id;
+ info->first_packet_id = packet->packet_id;
+ updated = TRUE;
+ }
+
+ if (gibber_r_multicast_packet_diff (info->packet_id,
+ senderinfo->packet_id) < 0)
+ {
+ DEBUG_SENDER (sender, "Acks are going backward!");
+ signal_failure (sender);
+ return;
+ }
+
+ if (gibber_r_multicast_packet_diff (info->packet_id,
+ senderinfo->packet_id) > 0)
+ {
+ info->packet_id = senderinfo->packet_id;
+ info->first_packet_id = packet->packet_id;
+ updated = TRUE;
+ }
+ }
+
+ if (updated)
+ {
+ gibber_r_multicast_sender_group_gc (priv->group);
+ }
+}
+
+static gboolean
+pop_packet (GibberRMulticastSender *sender)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ PacketInfo *p;
+
+ DEBUG_SENDER (sender, "Next output: 0x%x Next output data: 0x%x",
+ sender->next_output_packet, sender->next_output_data_packet);
+
+ if (sender->next_output_data_packet != sender->next_output_packet)
+ {
+ /* We saw the end of some data message before the end of the data stream,
+ * first try if we can pop this */
+ if (pop_data_packet (sender))
+ return TRUE;
+ }
+
+ if (sender->state == GIBBER_R_MULTICAST_SENDER_STATE_FAILED
+ && gibber_r_multicast_packet_diff (priv->end_point,
+ sender->next_output_packet) >= 0)
+ {
+ DEBUG_SENDER (sender, "Not looking at packets behind the endpoint");
+ return FALSE;
+ }
+
+ p = g_hash_table_lookup (priv->packet_cache, &(sender->next_output_packet));
+
+ DEBUG_SENDER (sender, "Looking at 0x%x", sender->next_output_packet);
+
+ if (p == NULL || p->packet == NULL)
+ {
+ /* No packet yet.. too bad :( */
+ DEBUG_SENDER(sender, "No new packets to pop");
+ return FALSE;
+ }
+
+ g_assert (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (p->packet));
+
+ update_acks (sender, p->packet);
+
+ if (!check_depends (sender, p->packet, FALSE))
+ {
+ return FALSE;
+ }
+
+ if (p->packet->type == PACKET_TYPE_DATA)
+ {
+ /* A data packet. If we had a potential end before this one, skip it
+ * we're holding back the data for some reason otherwise check
+ * if it's an end */
+
+ if (sender->next_output_data_packet == sender->next_output_packet)
+ {
+ sender->next_output_packet++;
+ /* If this is the end, try to pop it. Otherwise ignore */
+ if (p->packet->data.data.flags & GIBBER_R_MULTICAST_DATA_PACKET_END)
+ {
+ /* If we could pop this, then advance next_output_data_packet
+ * otherwise keep it at this location */
+ pop_data_packet (sender);
+ }
+ else
+ {
+ sender->next_output_data_packet++;
+ }
+ }
+ else
+ {
+ sender->next_output_packet++;
+ }
+ }
+ else
+ {
+ if (sender->next_output_packet == sender->next_output_data_packet)
+ sender->next_output_data_packet++;
+
+ sender->next_output_packet++;
+
+ if (p->packet->type != PACKET_TYPE_NO_DATA)
+ {
+ signal_control_packet (sender, p->packet);
+ }
+
+ p->popped = TRUE;
+ packet_info_try_gc (sender, p);
+ }
+
+ /* We successfully popped a new packet (weee), reschedule our watch dog */
+ schedule_progress_timer (sender);
+
+ return TRUE;
+}
+
+static gboolean
+do_pop_packets (GibberRMulticastSender *sender)
+{
+ gboolean popped = FALSE;
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ if (sender->state < GIBBER_R_MULTICAST_SENDER_STATE_PREPARING
+ || sender->state > GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ {
+ /* No popping untill we have at least some information */
+ return FALSE;
+ }
+
+ /* Don't pop if our sender group was stopped */
+ if (priv->group->stopped)
+ return FALSE;
+
+ g_object_ref (sender);
+
+ while (sender->state <= GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ {
+ if (!pop_packet (sender))
+ break;
+
+ popped = TRUE;
+ }
+
+ g_object_unref (sender);
+
+ return popped;
+}
+
+static void
+senders_collect (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *s = GIBBER_R_MULTICAST_SENDER(value);
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (user_data);
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ if (s->state < GIBBER_R_MULTICAST_SENDER_STATE_PENDING_REMOVAL)
+ g_queue_push_tail (priv->group->pop_queue, s);
+}
+
+
+static void
+pop_packets (GibberRMulticastSender *sender)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ gboolean pop;
+
+
+ if (priv->group->popping)
+ {
+ if (!g_queue_find (priv->group->pop_queue, sender))
+ {
+ /* Ensure that data is popped at the next opportunity */
+ g_queue_push_tail (priv->group->pop_queue, sender);
+ }
+ return;
+ }
+
+
+ priv->group->popping = TRUE;
+
+ g_object_ref (sender);
+
+ pop = do_pop_packets (sender);
+
+ /* If something is popped or a node queued itself for popping, go for it */
+ while (pop || g_queue_peek_head (priv->group->pop_queue) != NULL)
+ {
+ GibberRMulticastSender *s;
+
+ /* If something was popped, try to pop as much as possible from others in
+ * this group. Else just pop all senders in the queue */
+ if (pop)
+ {
+ while (g_queue_pop_head (priv->group->pop_queue) != NULL)
+ /* pass */;
+
+ g_hash_table_foreach (priv->group->senders, senders_collect, sender);
+ }
+
+ pop = FALSE;
+ while ((s = g_queue_pop_head (priv->group->pop_queue)) != NULL)
+ {
+ pop |= do_pop_packets (s);
+ }
+ }
+
+ priv->group->popping = FALSE;
+ g_object_unref (sender);
+}
+
+static void
+insert_packet (GibberRMulticastSender *sender, GibberRMulticastPacket *packet)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ PacketInfo *info;
+
+ g_assert (sender->state > GIBBER_R_MULTICAST_SENDER_STATE_NEW);
+
+ info = g_hash_table_lookup (priv->packet_cache, &packet->packet_id);
+ if (info != NULL && info->packet != NULL)
+ {
+ /* Already seen this packet */
+ DEBUG_SENDER (sender, "Detect resent of packet 0x%x", packet->packet_id);
+ return;
+ }
+
+ if (info == NULL)
+ {
+ info = packet_info_new (sender, packet->packet_id);
+ g_hash_table_insert (priv->packet_cache, &info->packet_id, info);
+ }
+
+ if (info->timeout != 0)
+ {
+ g_source_remove (info->timeout);
+ info->timeout = 0;
+ }
+
+ DEBUG_SENDER (sender, "Inserting packet 0x%x", packet->packet_id);
+ info->packet = g_object_ref (packet);
+
+ if (gibber_r_multicast_packet_diff (sender->next_input_packet,
+ packet->packet_id) >= 0)
+ {
+ /* Potentially needs some repairs */
+ guint32 i;
+ for (i = sender->next_input_packet; i != packet->packet_id; i++)
+ {
+ schedule_repair (sender, i);
+ }
+ sender->next_input_packet = packet->packet_id + 1;
+ }
+
+ /* pop out as many packets as we can */
+ pop_packets (sender);
+
+ return;
+}
+
+
+void
+gibber_r_multicast_sender_update_start (GibberRMulticastSender *sender,
+ guint32 packet_id)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ DEBUG_SENDER (sender, "Updating start to %x", packet_id);
+ g_assert (sender->state < GIBBER_R_MULTICAST_SENDER_STATE_FAILED);
+
+ if (sender->state == GIBBER_R_MULTICAST_SENDER_STATE_NEW)
+ {
+ g_assert (g_hash_table_size (priv->packet_cache) == 0);
+
+ set_state (sender, GIBBER_R_MULTICAST_SENDER_STATE_PREPARING);
+
+ sender->next_input_packet = packet_id;
+ sender->next_output_packet = packet_id;
+ sender->next_output_data_packet = packet_id;
+ priv->first_packet = packet_id;
+ }
+ else if (gibber_r_multicast_packet_diff (sender->next_input_packet,
+ packet_id) > 0)
+ {
+ /* Remove all repair requests for packets up to this packet_id */
+ guint32 i;
+ for (i = priv->first_packet; i < packet_id; i++)
+ {
+ PacketInfo *info;
+ info = g_hash_table_lookup (priv->packet_cache, &i);
+ if (info != NULL && info->packet == NULL && info->timeout != 0)
+ {
+ g_source_remove (info->timeout);
+ info->timeout = 0;
+ }
+ }
+
+ sender->next_input_packet = packet_id;
+ sender->next_output_packet = packet_id;
+ sender->next_output_data_packet = packet_id;
+ }
+}
+
+void
+gibber_r_multicast_sender_update_end (GibberRMulticastSender *sender,
+ guint32 packet_id)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ g_assert (sender->state == GIBBER_R_MULTICAST_SENDER_STATE_FAILED);
+
+ if (gibber_r_multicast_packet_diff (priv->end_point, packet_id) >= 0)
+ {
+ DEBUG_SENDER (sender, "Updating end to %x", packet_id);
+ priv->end_point = packet_id;
+ pop_packets (sender);
+ }
+}
+
+void
+gibber_r_multicast_sender_set_failed (GibberRMulticastSender *sender)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ if (sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ return;
+
+ if (sender->state < GIBBER_R_MULTICAST_SENDER_STATE_PREPARING)
+ {
+ DEBUG_SENDER (sender, "Failed before we knew anything");
+ set_state (sender, GIBBER_R_MULTICAST_SENDER_STATE_UNKNOWN_FAILED);
+ }
+ else
+ {
+ set_state (sender, GIBBER_R_MULTICAST_SENDER_STATE_FAILED);
+ priv->end_point = sender->next_output_packet;
+ DEBUG_SENDER (sender, "Marked sender as failed. Endpoint %x",
+ priv->end_point);
+ }
+
+ cancel_failure_timers (sender);
+}
+
+void
+gibber_r_multicast_sender_set_data_start (GibberRMulticastSender *sender,
+ guint32 packet_id)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ g_assert (sender->state < GIBBER_R_MULTICAST_SENDER_STATE_DATA_RUNNING);
+
+ DEBUG_SENDER (sender, "Setting data start at 0x%x", packet_id);
+
+ priv->start_data = TRUE;
+ priv->start_point = packet_id;
+}
+
+void
+gibber_r_multicast_sender_push (GibberRMulticastSender *sender,
+ GibberRMulticastPacket *packet)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ gint diff;
+
+ g_assert (sender->id == packet->sender);
+
+ if (sender->state < GIBBER_R_MULTICAST_SENDER_STATE_PREPARING)
+ {
+ /* Don't know where to start, so ignore..
+ * A potential optimisation would be to cache a limited amount anyway, so
+ * we don't have to repair them if we should have catched these anyways
+ * */
+ return;
+ }
+
+ diff = gibber_r_multicast_packet_diff (sender->next_output_packet,
+ packet->packet_id);
+
+ if (diff >= 0 && diff < PACKET_CACHE_SIZE) {
+ insert_packet (sender, packet);
+ return;
+ }
+
+ if (diff < 0 && gibber_r_multicast_packet_diff (priv->first_packet,
+ packet->packet_id) >= 0)
+ {
+ /* We already had this one, silently ignore */
+ DEBUG_SENDER (sender, "Detect resent of packet 0x%x",
+ packet->packet_id);
+ return;
+ }
+
+ DEBUG_SENDER (sender, "Packet 0x%x out of range, dropping (%x %x %x)",
+ packet->packet_id, priv->first_packet,
+ sender->next_output_packet, sender->next_input_packet);
+}
+
+gboolean
+gibber_r_multicast_sender_repair_request (GibberRMulticastSender *sender,
+ guint32 id)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+ gint diff;
+ PacketInfo *info;
+
+ if (sender->state < GIBBER_R_MULTICAST_SENDER_STATE_PREPARING)
+ {
+ DEBUG_SENDER (sender, "ignore repair request");
+ return FALSE;
+ }
+
+ info = g_hash_table_lookup (priv->packet_cache, &id);
+ if (info != NULL && info->packet != NULL)
+ {
+ schedule_do_repair (sender, id);
+ return TRUE;
+ }
+
+ diff = gibber_r_multicast_packet_diff (sender->next_output_packet, id);
+
+ if (diff >= 0 && diff < PACKET_CACHE_SIZE)
+ {
+ if (sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_STOPPED)
+ /* Beyond stopped state we only send out repairs for packets we have */
+ return FALSE;
+
+ if (info == NULL)
+ {
+ guint32 i;
+
+ for (i = sender->next_output_packet ; i != id + 1; i++)
+ {
+ schedule_repair (sender, i);
+ }
+ }
+ else
+ {
+ /* else we already knew about the packets existance, but didn't see
+ the packet just yet. Which means we already have a repair timeout
+ running */
+ g_assert (info->timeout != 0);
+ /* Reschedule the repair */
+ g_source_remove (info->timeout);
+ info->timeout = 0;
+ schedule_repair (sender, id);
+ }
+
+ return TRUE;
+ }
+
+ DEBUG_SENDER (sender, "Repair request packet 0x%x out of range, ignoring",
+ id);
+
+ return FALSE;
+}
+
+gboolean
+gibber_r_multicast_sender_seen (GibberRMulticastSender *sender, guint32 id)
+{
+ gint diff;
+ guint32 i, last;
+
+ g_assert (sender != NULL);
+ DEBUG_SENDER(sender, "Seen next packet 0x%x", id);
+
+ if (sender->state < GIBBER_R_MULTICAST_SENDER_STATE_PREPARING
+ || sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_UNKNOWN_FAILED)
+ {
+ return FALSE;
+ }
+
+ diff = gibber_r_multicast_packet_diff (sender->next_input_packet, id);
+ if (diff < 0)
+ return TRUE;
+
+ last = sender->next_output_packet + PACKET_CACHE_SIZE;
+
+ /* Ensure that we don't overfill the CACHE */
+ last = gibber_r_multicast_packet_diff (last, id) > 0 ? last : id;
+
+ for (i = sender->next_input_packet; i != last; i ++)
+ {
+ schedule_repair (sender, i);
+ }
+ return FALSE;
+}
+
+void
+gibber_r_multicast_senders_updated (GibberRMulticastSender *sender)
+{
+ pop_packets (sender);
+}
+
+void
+gibber_r_multicast_sender_whois_push (GibberRMulticastSender *sender,
+ const GibberRMulticastPacket *packet)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ switch (packet->type) {
+ case PACKET_TYPE_WHOIS_REQUEST:
+ g_assert (packet->data.whois_request.sender_id == sender->id);
+
+ if (sender->name != NULL)
+ {
+ if (priv->whois_timer == 0)
+ {
+ gint timeout = g_random_int_range (MIN_WHOIS_REPLY_TIMEOUT,
+ MAX_WHOIS_REPLY_TIMEOUT);
+ priv->whois_timer =
+ g_timeout_add (timeout, do_whois_reply, sender);
+ DEBUG_SENDER (sender, "Scheduled whois reply in %d ms", timeout);
+ }
+ }
+ else
+ {
+ schedule_whois_request (sender, TRUE);
+ }
+ break;
+ case PACKET_TYPE_WHOIS_REPLY:
+ g_assert (packet->sender == sender->id);
+
+ if (sender->name == NULL)
+ {
+ name_discovered (sender, packet->data.whois_reply.sender_name);
+ }
+ else
+ {
+ /* FIXME: collision detection */
+ stop_whois_discovery (sender);
+ }
+
+ pop_packets (sender);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+void
+gibber_r_multicast_sender_set_packet_repeat (GibberRMulticastSender *sender,
+ guint32 packet_id, gboolean repeat)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ PacketInfo *info;
+
+ info = g_hash_table_lookup (priv->packet_cache, &packet_id);
+ g_assert (info != NULL && info->packet != NULL);
+
+ if (info->repeating == repeat)
+ {
+ return;
+ }
+
+ info->repeating = repeat;
+
+ if (repeat)
+ {
+ if (info->timeout == 0)
+ schedule_do_repair (sender, packet_id);
+ }
+ else
+ {
+ packet_info_try_gc (sender, info);
+ }
+
+ /* FIXME: If repeat is turned off, we repeat it at least once more as there
+ * might have been a repair request after the last repeating.. This is
+ * ofcourse suboptimal */
+}
+
+guint
+gibber_r_multicast_sender_packet_cache_size ( GibberRMulticastSender *sender)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ /* The important cache size is until our cutoff point, which can be less
+ * then the last packet we actually did receive from this sender */
+ if (sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_FAILED)
+ return gibber_r_multicast_packet_diff (priv->first_packet,
+ priv->end_point);
+
+ return gibber_r_multicast_packet_diff (priv->first_packet,
+ sender->next_input_packet);
+}
+
+static AckInfo *
+gibber_r_multicast_sender_get_ackinfo (GibberRMulticastSender *sender,
+ guint32 sender_id)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ return (AckInfo *) g_hash_table_lookup (priv->acks, &sender_id);
+}
+
+void
+gibber_r_multicast_sender_ack (GibberRMulticastSender *sender, guint32 ack)
+{
+ guint32 i;
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE (sender);
+
+ if (gibber_r_multicast_packet_diff (priv->first_packet, ack) < 0)
+ {
+ return;
+ }
+
+ for (i = priv->first_packet ; i != ack; i++)
+ {
+ PacketInfo *info;
+
+ info = g_hash_table_lookup (priv->packet_cache, &i);
+ if (info == NULL)
+ continue;
+
+ info->acked = TRUE;
+ packet_info_try_gc (sender, info);
+ }
+}
+
+
+
+/* Tell the sender to not signal data starting from this packet */
+void
+gibber_r_multicast_sender_hold_data (GibberRMulticastSender *sender,
+ guint32 packet_id)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE(sender);
+
+ priv->holding_data = TRUE;
+ priv->holding_point = packet_id;
+ DEBUG_SENDER (sender, "Holding data starting at %x", packet_id);
+
+ /* Pop packets in case the holding_point moved forward */
+ pop_packets (sender);
+}
+
+/* Stop holding back data of the sender */
+void
+gibber_r_multicast_sender_release_data (GibberRMulticastSender *sender)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE(sender);
+
+ DEBUG_SENDER (sender, "Releasing data");
+ priv->holding_data = FALSE;
+ pop_packets (sender);
+}
+
+static void
+stop_packet (gpointer key, gpointer value, gpointer user_data)
+{
+ PacketInfo *p = (PacketInfo *) value;
+
+ if (p->timeout != 0)
+ {
+ g_source_remove (p->timeout);
+ p->timeout = 0;
+ }
+}
+
+void
+gibber_r_multicast_sender_stop (GibberRMulticastSender *sender)
+{
+ GibberRMulticastSenderPrivate *priv =
+ GIBBER_R_MULTICAST_SENDER_GET_PRIVATE(sender);
+
+ if (sender->state >= GIBBER_R_MULTICAST_SENDER_STATE_STOPPED)
+ return;
+
+ if (priv->whois_timer != 0)
+ {
+ g_source_remove (priv->whois_timer);
+ priv->whois_timer = 0;
+ }
+
+ g_hash_table_foreach (priv->packet_cache, stop_packet, NULL);
+ set_state (sender, GIBBER_R_MULTICAST_SENDER_STATE_STOPPED);
+}
+
+void
+_gibber_r_multicast_TEST_sender_fail (GibberRMulticastSender *sender)
+{
+ signal_failure (sender);
+}
+
diff --git a/salut/lib/gibber/gibber-r-multicast-sender.h b/salut/lib/gibber/gibber-r-multicast-sender.h
new file mode 100644
index 000000000..2642318cb
--- /dev/null
+++ b/salut/lib/gibber/gibber-r-multicast-sender.h
@@ -0,0 +1,185 @@
+/*
+ * gibber-r-multicast-sender.h - Header for GibberRMulticastSender
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_R_MULTICAST_SENDER_H__
+#define __GIBBER_R_MULTICAST_SENDER_H__
+
+#include <glib-object.h>
+
+#include "gibber-r-multicast-packet.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberRMulticastSenderGroup GibberRMulticastSenderGroup;
+
+struct _GibberRMulticastSenderGroup {
+ /* <public>
+ * GUINT_TO_POINTER (sender_id) => owned GibberRMulticastSender */
+ GHashTable *senders;
+ /* <private> */
+ gboolean popping;
+ gboolean stopped;
+ /* queue of GibberRMulticast GibberRMulticastSender */
+ GQueue *pop_queue;
+ /* GArray of pending removal GibberRMulticastSenders */
+ GPtrArray *pending_removal;
+};
+
+typedef struct _GibberRMulticastSender GibberRMulticastSender;
+typedef struct _GibberRMulticastSenderClass GibberRMulticastSenderClass;
+
+struct _GibberRMulticastSenderClass {
+ GObjectClass parent_class;
+};
+
+typedef enum {
+ /* We have no info about this sender whatsoever */
+ GIBBER_R_MULTICAST_SENDER_STATE_NEW = 0,
+ /* We know the sequence number we have to start from, but haven't send out
+ * any data yet */
+ GIBBER_R_MULTICAST_SENDER_STATE_PREPARING,
+ /* Non-data Packets are flowing */
+ GIBBER_R_MULTICAST_SENDER_STATE_RUNNING,
+ /* Data is flowing */
+ GIBBER_R_MULTICAST_SENDER_STATE_DATA_RUNNING,
+ /* Node has failed, still pop packets but stop depending on it */
+ GIBBER_R_MULTICAST_SENDER_STATE_FAILED,
+ GIBBER_R_MULTICAST_SENDER_STATE_UNKNOWN_FAILED,
+ GIBBER_R_MULTICAST_SENDER_STATE_STOPPED,
+ /* Will be removed as soon as all dependencies are resolved */
+ GIBBER_R_MULTICAST_SENDER_STATE_PENDING_REMOVAL,
+} GibberRMulticastSenderState;
+
+struct _GibberRMulticastSender {
+ GObject parent;
+ gchar *name;
+ guint32 id;
+
+ GibberRMulticastSenderState state;
+
+ /* Next packet we want to send out */
+ guint32 next_output_packet;
+ /* Next data packet we want to send out. Can be different from
+ * next_output_packet iff holding back data or a fragmented data message is
+ * interleaved with control messages.. Guaranteed to be <=
+ * next_output_packet */
+ guint32 next_output_data_packet;
+
+ /* Next packet we expect from the sender */
+ guint32 next_input_packet;
+};
+
+GType gibber_r_multicast_sender_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_R_MULTICAST_SENDER \
+ (gibber_r_multicast_sender_get_type ())
+#define GIBBER_R_MULTICAST_SENDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_R_MULTICAST_SENDER, \
+ GibberRMulticastSender))
+#define GIBBER_R_MULTICAST_SENDER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_R_MULTICAST_SENDER, \
+ GibberRMulticastSenderClass))
+#define GIBBER_IS_R_MULTICAST_SENDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_R_MULTICAST_SENDER))
+#define GIBBER_IS_R_MULTICAST_SENDER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_R_MULTICAST_SENDER))
+#define GIBBER_R_MULTICAST_SENDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_R_MULTICAST_SENDER, \
+ GibberRMulticastSenderClass))
+
+GibberRMulticastSenderGroup *gibber_r_multicast_sender_group_new (void);
+
+void gibber_r_multicast_sender_group_free (GibberRMulticastSenderGroup *group);
+void gibber_r_multicast_sender_group_stop (GibberRMulticastSenderGroup *group);
+void gibber_r_multicast_sender_group_add (GibberRMulticastSenderGroup *group,
+ GibberRMulticastSender *sender);
+
+GibberRMulticastSender * gibber_r_multicast_sender_group_lookup (
+ GibberRMulticastSenderGroup *group, guint32 sender_id);
+
+GibberRMulticastSender * gibber_r_multicast_sender_group_lookup_by_name (
+ GibberRMulticastSenderGroup *group, const gchar *name);
+
+void gibber_r_multicast_sender_group_remove (
+ GibberRMulticastSenderGroup *group, guint32 sender_id);
+
+gboolean gibber_r_multicast_sender_group_push_packet (
+ GibberRMulticastSenderGroup *group, GibberRMulticastPacket *packet);
+
+GibberRMulticastSender *gibber_r_multicast_sender_new (guint32 id,
+ const gchar *name, GibberRMulticastSenderGroup *group);
+
+/* Sequence for this sender starts at packet_id */
+void gibber_r_multicast_sender_update_start (GibberRMulticastSender *sender,
+ guint32 packet_id);
+
+void gibber_r_multicast_sender_update_end (GibberRMulticastSender *sender,
+ guint32 packet_id);
+
+void gibber_r_multicast_sender_set_failed (GibberRMulticastSender *sender);
+
+void gibber_r_multicast_sender_set_data_start (GibberRMulticastSender *sender,
+ guint32 packet_id);
+
+/* Tell the sender to not signal data starting from this packet */
+void gibber_r_multicast_sender_hold_data (GibberRMulticastSender *sender,
+ guint32 packet_id);
+
+
+/* Stop holding back data of the sender */
+void gibber_r_multicast_sender_release_data (GibberRMulticastSender *sender);
+
+void gibber_r_multicast_sender_push (GibberRMulticastSender *sender,
+ GibberRMulticastPacket *packet);
+
+void
+gibber_r_multicast_senders_updated (GibberRMulticastSender *sender);
+
+/* Returns TRUE if we were up to dated */
+gboolean gibber_r_multicast_sender_seen (GibberRMulticastSender *sender,
+ guint32 id);
+
+gboolean gibber_r_multicast_sender_repair_request (
+ GibberRMulticastSender *sender, guint32 id);
+
+void gibber_r_multicast_sender_whois_push (GibberRMulticastSender *sender,
+ const GibberRMulticastPacket *packet);
+
+void gibber_r_multicast_sender_set_packet_repeat (
+ GibberRMulticastSender *sender, guint32 packet_id, gboolean repeat);
+
+/* Returns the amount of unpopped or unacked packets */
+guint gibber_r_multicast_sender_packet_cache_size (
+ GibberRMulticastSender *sender);
+
+/* Ack management */
+void gibber_r_multicast_sender_ack (GibberRMulticastSender *sender,
+ guint32 ack);
+
+/* Stop all pending requests, only reply to repair requests */
+void gibber_r_multicast_sender_stop (GibberRMulticastSender *sender);
+
+/* Trigger failure detection, for testing only */
+void _gibber_r_multicast_TEST_sender_fail (GibberRMulticastSender *sender);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_R_MULTICAST_SENDER_H__*/
diff --git a/salut/lib/gibber/gibber-r-multicast-transport.c b/salut/lib/gibber/gibber-r-multicast-transport.c
new file mode 100644
index 000000000..7c845ab98
--- /dev/null
+++ b/salut/lib/gibber/gibber-r-multicast-transport.c
@@ -0,0 +1,1764 @@
+/*
+ * gibber-r-multicast-transport.c - Source for GibberRMulticastTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DEBUG_FLAG DEBUG_RMULTICAST
+#include "gibber-debug.h"
+
+#include "gibber-r-multicast-transport.h"
+#include "gibber-r-multicast-packet.h"
+#include "gibber-r-multicast-sender.h"
+
+#define MIN_ATTEMPT_JOIN_TIMEOUT 100
+#define MAX_ATTEMPT_JOIN_TIMEOUT 400
+
+/* Time between the last attempt join packet and the start of the joining phase
+ */
+#define MIN_JOINING_START_TIMEOUT 4800
+#define MAX_JOINING_START_TIMEOUT 5200
+
+/* Time-out before within which we should have complete a failure */
+#define FAILURE_TIMEOUT 60000
+
+/* Time-out before within which we should have complete a join phase */
+#define JOIN_TIMEOUT 30000
+
+G_DEFINE_TYPE(GibberRMulticastTransport, gibber_r_multicast_transport,
+ GIBBER_TYPE_TRANSPORT)
+
+/* signal enum */
+enum
+{
+ NEW_SENDERS,
+ LOST_SENDERS,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum {
+ PROP_TRANSPORT = 1,
+ LAST_PROPERTY
+};
+
+/* check join returns */
+typedef enum {
+ JOIN_EQUAL,
+ JOIN_LESS_INFO,
+ JOIN_MORE_INFO,
+ JOIN_FAILED,
+} CheckJoinReturn;
+
+/* private structure */
+typedef struct _GibberRMulticastTransportPrivate
+ GibberRMulticastTransportPrivate;
+
+typedef enum {
+ /* Not connected yet */
+ STATE_DISCONNECTED = 0,
+ /* Normal traffic */
+ STATE_NORMAL,
+ /* Gathering new people */
+ STATE_GATHERING,
+ /* Joining */
+ STATE_JOINING,
+ /* Resetting */
+ STATE_RESETTING
+} State;
+
+struct _GibberRMulticastTransportPrivate
+{
+ gboolean dispose_has_run;
+ GibberRMulticastCausalTransport *transport;
+ GHashTable *members;
+
+ guint32 attempt_join_id;
+ gboolean repeating_join;
+ gboolean send_empty;
+ guint timeout;
+
+ guint joining_timeout;
+ /* People who were in the join message you sent */
+ GArray *send_join;
+ /* People who were marked as failures in our join */
+ GArray *send_join_failures;
+
+ /* People we saw to have failed, but not send a failure packet for yet */
+ GArray *pending_failures;
+
+ State state;
+
+ gulong reconnect_handler;
+};
+
+typedef enum {
+ /* Never heard of this guy before */
+ MEMBER_STATE_UNKNOWN = 0,
+ /* Start of the join attempt (Don't know packet startpoint yet) */
+ MEMBER_STATE_ATTEMPT_JOIN_STARTED,
+ /* Need to repeat attempt join information to member */
+ MEMBER_STATE_ATTEMPT_JOIN_REPEAT,
+ /* Is a member of our reliable causal graph */
+ MEMBER_STATE_ATTEMPT_JOIN_DONE,
+ /* Received a JOIN message from this member */
+ MEMBER_STATE_JOINING,
+ /* Actually a member */
+ MEMBER_STATE_MEMBER,
+ /* failure! */
+ MEMBER_STATE_FAILING,
+ /* Node failing that was a member */
+ MEMBER_STATE_MEMBER_FAILING,
+ /* Failed before we even know what this node was all about */
+ MEMBER_STATE_INSTANT_FAILURE,
+} MemberState;
+
+typedef struct {
+ MemberState state;
+ guint32 id;
+ gboolean agreed_join;
+ guint32 join_packet_id;
+ /* Failures recorded by this node */
+ GArray *failures;
+ GibberRMulticastTransport *transport;
+
+ guint fail_timeout;
+} MemberInfo;
+
+#define GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_R_MULTICAST_TRANSPORT, \
+ GibberRMulticastTransportPrivate))
+
+static void stop_send_attempt_join (GibberRMulticastTransport *self);
+static void check_failure_completion (GibberRMulticastTransport *self,
+ guint32 id);
+static void fail_member (GibberRMulticastTransport *self, MemberInfo *info,
+ guint32 id);
+
+static void free_member_info (gpointer info);
+static MemberInfo *member_get_info (GibberRMulticastTransport *self,
+ guint32 id);
+static MemberState member_get_state (GibberRMulticastTransport *self,
+ guint32 id);
+static void check_join_agreement (GibberRMulticastTransport *self);
+
+struct HTData {
+ GArray *senders;
+ gboolean need_repeat;
+};
+
+static void
+gibber_r_multicast_transport_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec) {
+ GibberRMulticastTransport *transport = GIBBER_R_MULTICAST_TRANSPORT(object);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(transport);
+ switch (property_id) {
+ case PROP_TRANSPORT:
+ priv->transport = GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (
+ g_value_dup_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gibber_r_multicast_transport_get_property (GObject *object,
+ guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GibberRMulticastTransport *transport = GIBBER_R_MULTICAST_TRANSPORT (object);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (transport);
+ switch (property_id)
+ {
+ case PROP_TRANSPORT:
+ g_value_set_object (value, priv->transport);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gibber_r_multicast_transport_init (GibberRMulticastTransport *obj)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (obj);
+
+ priv->members = g_hash_table_new_full (g_int_hash, g_int_equal,
+ NULL, free_member_info);
+
+ priv->pending_failures = g_array_new (FALSE, FALSE, sizeof (guint32));
+}
+
+static void gibber_r_multicast_transport_dispose (GObject *object);
+static void gibber_r_multicast_transport_finalize (GObject *object);
+
+static gboolean gibber_r_multicast_transport_do_send (
+ GibberTransport *transport, const guint8 *data, gsize size,
+ GError **error);
+
+static void gibber_r_multicast_transport_disconnect (
+ GibberTransport *transport);
+
+static void
+gibber_r_multicast_transport_class_init (
+ GibberRMulticastTransportClass *gibber_r_multicast_transport_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (
+ gibber_r_multicast_transport_class);
+ GibberTransportClass *transport_class =
+ GIBBER_TRANSPORT_CLASS(gibber_r_multicast_transport_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (gibber_r_multicast_transport_class,
+ sizeof (GibberRMulticastTransportPrivate));
+
+ object_class->dispose = gibber_r_multicast_transport_dispose;
+ object_class->finalize = gibber_r_multicast_transport_finalize;
+
+ signals[NEW_SENDERS] = g_signal_new ("new-senders",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_transport_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ signals[LOST_SENDERS] = g_signal_new ("lost-senders",
+ G_OBJECT_CLASS_TYPE(gibber_r_multicast_transport_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ object_class->set_property = gibber_r_multicast_transport_set_property;
+ object_class->get_property = gibber_r_multicast_transport_get_property;
+
+ param_spec = g_param_spec_object ("transport",
+ "transport", "The underlying Transport",
+ GIBBER_TYPE_TRANSPORT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, PROP_TRANSPORT,
+ param_spec);
+
+ transport_class->send = gibber_r_multicast_transport_do_send;
+ transport_class->disconnect = gibber_r_multicast_transport_disconnect;
+}
+
+static void
+gibber_r_multicast_transport_flush_state (GibberRMulticastTransport *self)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ priv->repeating_join = FALSE;
+ priv->send_empty = FALSE;
+
+ if (priv->timeout != 0)
+ {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+
+ if (priv->joining_timeout != 0)
+ {
+ g_source_remove (priv->joining_timeout);
+ priv->joining_timeout = 0;
+ }
+
+ if (priv->send_join != NULL)
+ {
+ g_array_unref (priv->send_join);
+ priv->send_join = NULL;
+ }
+
+ if (priv->send_join_failures != NULL)
+ {
+ g_array_unref (priv->send_join_failures);
+ priv->send_join_failures = NULL;
+ }
+
+ if (priv->pending_failures->len > 0)
+ {
+ g_array_remove_range (priv->pending_failures, 0,
+ priv->pending_failures->len);
+ }
+
+ g_hash_table_remove_all (priv->members);
+}
+
+void
+gibber_r_multicast_transport_dispose (GObject *object)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (object);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ gibber_r_multicast_transport_flush_state (self);
+
+ if (priv->transport != NULL)
+ {
+ g_object_unref (priv->transport);
+ priv->transport = NULL;
+ }
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (gibber_r_multicast_transport_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_r_multicast_transport_parent_class)->dispose (
+ object);
+}
+
+void
+gibber_r_multicast_transport_finalize (GObject *object)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (object);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ g_hash_table_unref (priv->members);
+ if (priv->pending_failures != NULL)
+ {
+ g_array_unref (priv->pending_failures);
+ priv->pending_failures = NULL;
+ }
+
+ G_OBJECT_CLASS (
+ gibber_r_multicast_transport_parent_class)->finalize (object);
+}
+
+static void
+transport_reconnected (GibberTransport *transport, gpointer user_data)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ DEBUG ("Resetting done");
+ g_signal_handler_disconnect (priv->transport, priv->reconnect_handler);
+ priv->state = STATE_NORMAL;
+}
+
+static void
+gibber_r_multicast_transport_reset (GibberRMulticastTransport *self)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ priv->state = STATE_RESETTING;
+ DEBUG ("Resetting!");
+
+ gibber_r_multicast_transport_flush_state (self);
+
+
+ priv->reconnect_handler = g_signal_connect (priv->transport, "connected",
+ G_CALLBACK(transport_reconnected), self);
+ gibber_r_multicast_causal_transport_reset (priv->transport);
+}
+
+static void
+received_data (GibberTransport *transport, GibberBuffer *buffer,
+ gpointer user_data)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+ GibberRMulticastCausalBuffer *cbuffer =
+ (GibberRMulticastCausalBuffer *) buffer;
+ MemberState state;
+
+ state = member_get_state (self, cbuffer->sender_id);
+
+ g_assert (state == MEMBER_STATE_MEMBER ||
+ state == MEMBER_STATE_MEMBER_FAILING);
+
+ gibber_transport_received_data_custom (GIBBER_TRANSPORT (self), buffer);
+}
+
+GibberRMulticastTransport *
+gibber_r_multicast_transport_new (GibberRMulticastCausalTransport *transport)
+{
+ GibberRMulticastTransport *result;
+
+ result = g_object_new (GIBBER_TYPE_R_MULTICAST_TRANSPORT,
+ "transport", transport, NULL);
+
+ gibber_transport_set_handler (GIBBER_TRANSPORT (transport),
+ received_data, result);
+
+ return result;
+}
+
+static MemberInfo *
+new_member (GibberRMulticastTransport *self, guint32 id)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ MemberInfo *info;
+
+ g_assert (id != priv->transport->sender_id);
+
+ info = g_slice_new0 (MemberInfo);
+ info->id = id;
+ info->failures = g_array_new (FALSE, FALSE, sizeof (guint32));
+ info->transport = self;
+ g_hash_table_insert (priv->members, &info->id, info);
+
+ return info;
+}
+
+static void
+free_member_info (gpointer data)
+{
+ MemberInfo *info = (MemberInfo *) data;
+
+ if (info->fail_timeout != 0)
+ g_source_remove (info->fail_timeout);
+ g_array_unref (info->failures);
+ g_slice_free (MemberInfo, info);
+}
+
+static MemberState
+member_get_state (GibberRMulticastTransport *self, guint32 id) {
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ MemberInfo *info = g_hash_table_lookup (priv->members, &id);
+ if (info == NULL) {
+ return MEMBER_STATE_UNKNOWN;
+ }
+ return info->state;
+}
+
+static MemberInfo *
+member_get_info (GibberRMulticastTransport *self, guint32 id) {
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ MemberInfo *info = g_hash_table_lookup (priv->members, &id);
+
+ if (info == NULL) {
+ info = new_member (self, id);
+ }
+
+ return info;
+}
+
+static void
+member_set_state (GibberRMulticastTransport *self, guint32 id,
+ MemberState state) {
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ MemberInfo *info = g_hash_table_lookup (priv->members, &id);
+ if (info == NULL) {
+ info = new_member (self, id);
+ }
+
+ info->state = state;
+}
+
+static void
+str_add_member (gpointer key, MemberInfo *value, GString *str) {
+ switch (value->state)
+ {
+ case MEMBER_STATE_UNKNOWN:
+ case MEMBER_STATE_ATTEMPT_JOIN_STARTED:
+ g_string_append_printf (str, "%x U, ", value->id);
+ break;
+ case MEMBER_STATE_ATTEMPT_JOIN_REPEAT:
+ case MEMBER_STATE_ATTEMPT_JOIN_DONE:
+ case MEMBER_STATE_JOINING:
+ g_string_append_printf (str, "%x N, ", value->id);
+ break;
+ case MEMBER_STATE_MEMBER:
+ g_string_append_printf (str, "%x M, ", value->id);
+ break;
+ case MEMBER_STATE_FAILING:
+ g_string_append_printf (str, "%x F, ", value->id);
+ break;
+ case MEMBER_STATE_MEMBER_FAILING:
+ g_string_append_printf (str, "%x MF, ", value->id);
+ break;
+ case MEMBER_STATE_INSTANT_FAILURE:
+ g_string_append_printf (str, "%x IF, ", value->id);
+ break;
+ }
+}
+
+
+static gchar *
+member_list_to_str (GibberRMulticastTransport *self) {
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ GString *str = g_string_sized_new (
+ 3 + 9 * g_hash_table_size (priv->members));
+
+ g_string_append (str, "{ ");
+ g_hash_table_foreach (priv->members, (GHFunc) str_add_member, str);
+ g_string_insert (str, str->len - 1, " }");
+
+ return g_string_free (str, FALSE);
+}
+
+static void
+add_to_join (gpointer key, gpointer value, gpointer user_data) {
+ MemberInfo *info = (MemberInfo *) value;
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ g_assert (info->state >= MEMBER_STATE_ATTEMPT_JOIN_REPEAT);
+
+ if (info->state < MEMBER_STATE_FAILING) {
+ g_array_append_val (priv->send_join, info->id);
+ }
+
+ if (info->state >= MEMBER_STATE_FAILING)
+ {
+ g_array_append_val (priv->send_join_failures, info->id);
+ }
+
+ info->agreed_join = FALSE;
+}
+
+static void
+fail_duplicated (gpointer key, gpointer value, gpointer user_data)
+{
+ MemberInfo *info = (MemberInfo *) value;
+ MemberInfo *cinfo = (MemberInfo *) user_data;
+ GibberRMulticastSender *sender;
+ GibberRMulticastSender *csender;
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (cinfo->transport);
+
+ if (info == cinfo)
+ return;
+
+ /* No need to check members that will fail anyways */
+ if (info->state < MEMBER_STATE_ATTEMPT_JOIN_REPEAT
+ || info->state >= MEMBER_STATE_FAILING)
+ return;
+
+ sender = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ info->id);
+ csender = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ cinfo->id);
+
+ if (sender->name != NULL && strcmp (csender->name, sender->name) == 0)
+ {
+ info->state = MEMBER_STATE_FAILING;
+ g_array_append_val (priv->pending_failures, info->id);
+ }
+}
+
+static void
+check_join_state (gpointer key, MemberInfo *value, gpointer user_data)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastSender *sender;
+
+ if (value->state < MEMBER_STATE_ATTEMPT_JOIN_REPEAT)
+ {
+ value->state = MEMBER_STATE_INSTANT_FAILURE;
+ gibber_r_multicast_causal_transport_remove_sender (priv->transport,
+ value->id);
+ }
+
+ DEBUG ("Checking join state of %x", value->id);
+ if (value->state >= MEMBER_STATE_FAILING)
+ {
+ /* Already taken care off */
+ return;
+ }
+
+
+ sender = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ value->id);
+
+ if (sender->name == NULL)
+ {
+ value->state = MEMBER_STATE_FAILING;
+ g_array_append_val (priv->pending_failures, value->id);
+ }
+ else if (value->state < MEMBER_STATE_MEMBER)
+ {
+ /* Check if any other members has the same name, if so fail it.
+ * This will ensure that we prioritise new responsive members over current
+ * ones. Major usecase is where someone parts ungracefully and reconnects
+ * later on. */
+ g_hash_table_foreach (priv->members, fail_duplicated, value);
+ }
+}
+
+static void
+setup_joining_phase (GibberRMulticastTransport *self)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ gchar *members;
+
+ if (priv->state == STATE_GATHERING)
+ {
+ stop_send_attempt_join (self);
+ g_source_remove (priv->joining_timeout);
+ priv->joining_timeout = 0;
+ /* every member with state >= MEMBER_STATE_ATTEMPT_JOIN_REPEAT, will be
+ * in our join */
+ g_assert (priv->send_join == NULL);
+ g_assert (priv->send_join_failures == NULL);
+ priv->send_join = g_array_new (FALSE, FALSE, sizeof (guint32));
+ priv->send_join_failures = g_array_new (FALSE, FALSE, sizeof (guint32));
+
+ g_hash_table_foreach (priv->members, (GHFunc)check_join_state, self);
+ }
+ else
+ {
+ g_assert (priv->state == STATE_JOINING);
+ g_array_unref (priv->send_join);
+ g_array_unref (priv->send_join_failures);
+
+ priv->send_join = g_array_new (FALSE, FALSE, sizeof (guint32));
+ priv->send_join_failures = g_array_new (FALSE, FALSE, sizeof (guint32));
+ }
+
+ priv->state = STATE_JOINING;
+ g_hash_table_foreach (priv->members, (GHFunc) add_to_join, self);
+
+ members = member_list_to_str (self);
+ DEBUG ("New join state: %s", members);
+
+ g_free (members);
+}
+
+static void
+fail_unjoined_members (gpointer key, gpointer value, gpointer user_data)
+{
+ MemberInfo *info = (MemberInfo *) value;
+
+ if (!info->agreed_join)
+ {
+ fail_member (info->transport, NULL, info->id);
+ }
+}
+
+static gboolean
+join_timeout_cb (gpointer data)
+{
+ GibberRMulticastTransport *self =
+ GIBBER_R_MULTICAST_TRANSPORT (data);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ g_hash_table_foreach (priv->members, fail_unjoined_members, NULL);
+
+ return FALSE;
+}
+
+static void
+start_joining_phase (GibberRMulticastTransport *self)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ GibberRMulticastSender *sender;
+ guint i;
+
+ setup_joining_phase (self);
+
+ sender = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ priv->transport->sender_id);
+
+ gibber_r_multicast_sender_hold_data (sender,
+ sender->next_input_packet);
+ gibber_r_multicast_causal_transport_send_join (priv->transport,
+ priv->send_join_failures);
+
+ /* We'll send them as failures as part of this join instead of in a dedicated
+ * failure packet */
+ for (i = 0; i < priv->pending_failures->len; i++)
+ {
+ GibberRMulticastSender *sender_;
+ sender_ = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ g_array_index (priv->pending_failures, guint32, i));
+
+ gibber_r_multicast_sender_set_failed (sender_);
+ }
+
+ if (priv->pending_failures->len > 0)
+ {
+ g_array_remove_range (priv->pending_failures, 0,
+ priv->pending_failures->len);
+ }
+
+ if (priv->timeout != 0)
+ g_source_remove (priv->timeout);
+
+ priv->timeout = g_timeout_add (JOIN_TIMEOUT,
+ join_timeout_cb, self);
+}
+
+static gboolean
+do_start_joining_phase (gpointer user_data)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+
+ start_joining_phase (self);
+ check_join_agreement (self);
+
+ return FALSE;
+}
+
+static void
+continue_gathering_phase (GibberRMulticastTransport *self) {
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ g_assert (priv->state != STATE_JOINING);
+
+ if (priv->state != STATE_GATHERING) {
+ DEBUG ("Entering gathering state");
+ priv->state = STATE_GATHERING;
+ }
+
+ if (priv->joining_timeout != 0)
+ g_source_remove (priv->joining_timeout);
+
+ priv->joining_timeout = g_timeout_add (
+ g_random_int_range (MIN_JOINING_START_TIMEOUT, MAX_JOINING_START_TIMEOUT),
+ do_start_joining_phase, self);
+}
+
+static gboolean
+guint32_array_contains (GArray *array, guint32 id) {
+ guint i;
+ for (i = 0; i < array->len; i++) {
+ if (g_array_index (array, guint32, i) == id)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+guint32_array_remove (GArray *array, guint32 id)
+{
+ guint i;
+ for (i = 0; i < array->len; i++) {
+ if (g_array_index (array, guint32, i) == id)
+ {
+ g_array_remove_index_fast (array, i);
+ return;
+ }
+ }
+}
+
+static void
+add_to_senders (gpointer key, gpointer value, gpointer user_data) {
+ struct HTData *data = (struct HTData *)user_data;
+ MemberInfo *info = (MemberInfo *) value;
+
+ if (info->state == MEMBER_STATE_UNKNOWN) {
+ info->state = MEMBER_STATE_ATTEMPT_JOIN_STARTED;
+ }
+
+ if (info->state == MEMBER_STATE_ATTEMPT_JOIN_STARTED) {
+ g_array_append_val (data->senders, info->id);
+ data->need_repeat = TRUE;
+ }
+
+ data->need_repeat |= (info->state == MEMBER_STATE_ATTEMPT_JOIN_REPEAT);
+}
+
+static void
+stop_send_attempt_join (GibberRMulticastTransport *self) {
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(self);
+
+ if (priv->repeating_join) {
+ gibber_r_multicast_causal_transport_stop_attempt_join (priv->transport,
+ priv->attempt_join_id);
+ priv->repeating_join = FALSE;
+ }
+
+ if (priv->timeout != 0) {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+}
+
+static gboolean
+do_send_attempt_join (gpointer user_data) {
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ struct HTData data;
+
+ priv->timeout = 0;
+ stop_send_attempt_join (self);
+
+ /* Overestimate the number of senders.. Prefer a bit more memory usage for a
+ * short time over allocations */
+ data.senders = g_array_sized_new (FALSE, FALSE, sizeof (guint32),
+ g_hash_table_size (priv->members));
+ data.need_repeat = FALSE;
+
+ /* Collect all senders from which we still need startpoint */
+ g_hash_table_foreach (priv->members, add_to_senders, &data);
+
+ priv->repeating_join = data.need_repeat;
+
+ if (priv->send_empty || priv->repeating_join) {
+ priv->attempt_join_id =
+ gibber_r_multicast_causal_transport_send_attempt_join (priv->transport,
+ data.senders, priv->repeating_join);
+ priv->send_empty = FALSE;
+ }
+ g_array_unref (data.senders);
+
+ return FALSE;
+}
+
+static void
+send_attempt_join (GibberRMulticastTransport *self, gboolean send_empty) {
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(self);
+
+ g_assert (priv->state != STATE_JOINING);
+
+ continue_gathering_phase (self);
+
+ /* schedule a send attempt for later on. If there wasn't one scheduled just
+ * yet */
+ priv->send_empty |= send_empty;
+
+ if (priv->timeout == 0) {
+ /* No send attempt scheduled yet, schedule one now */
+ priv->timeout = g_timeout_add (
+ g_random_int_range (MIN_ATTEMPT_JOIN_TIMEOUT, MAX_ATTEMPT_JOIN_TIMEOUT),
+ do_send_attempt_join, self);
+ }
+
+}
+
+static gboolean
+depends_list_contains (GArray *depends, guint32 id) {
+ guint i;
+
+ for (i = 0; i < depends->len; i++) {
+ GibberRMulticastPacketSenderInfo *info =
+ g_array_index (depends, GibberRMulticastPacketSenderInfo *, i);
+
+ if (info->sender_id == id)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+update_member (GibberRMulticastTransport *self,
+ guint32 sender_id, MemberState newstate, guint32 packet_id) {
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(self);
+ MemberInfo *info;
+ gboolean changed;
+ GibberRMulticastSender *sender;
+
+ if (sender_id == priv->transport->sender_id)
+ return FALSE;
+
+ sender = gibber_r_multicast_causal_transport_add_sender (priv->transport,
+ sender_id);
+
+ info = member_get_info (self, sender_id);
+
+ changed = (info->state < newstate);
+
+ info->state = MAX(info->state, newstate);
+
+ if (changed && info->state >= MEMBER_STATE_ATTEMPT_JOIN_REPEAT
+ && info->state < MEMBER_STATE_JOINING) {
+ /* We are guaranteed that this member didn't send a join before this
+ * packet_id */
+ gibber_r_multicast_sender_update_start (sender, packet_id);
+ }
+
+ return changed;
+}
+
+static gboolean
+update_foreign_member_list (GibberRMulticastTransport *self,
+ GibberRMulticastPacket *packet, MemberState state) {
+ guint i;
+ gboolean changed = FALSE;
+
+ changed |= update_member (self, packet->sender, state,
+ packet->packet_id + 1);
+ for (i = 0 ; i < packet->depends->len; i++) {
+ GibberRMulticastPacketSenderInfo *info =
+ g_array_index (packet->depends, GibberRMulticastPacketSenderInfo *, i);
+ changed |= update_member (self, info->sender_id, state, info->packet_id);
+ }
+ return changed;
+}
+
+static void
+received_foreign_packet_cb (GibberRMulticastCausalTransport *ctransport,
+ GibberRMulticastPacket *packet, gpointer user_data) {
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->state == STATE_JOINING) {
+ /* Once we started joining, ignore all foreign traffic */
+ return;
+ }
+
+ if (packet->type == PACKET_TYPE_BYE)
+ {
+ /* Ignore foreign bye packets, we don't care about people that are
+ * leaving */
+ return;
+ }
+
+ /* Always add sender, regardless of the packet. So that the causal layer can
+ * start requesting id -> name mapping */
+ gibber_r_multicast_causal_transport_add_sender (priv->transport,
+ packet->sender);
+
+ if (GIBBER_R_MULTICAST_PACKET_IS_RELIABLE_PACKET (packet)
+ || packet->type == PACKET_TYPE_SESSION) {
+ if (packet->type == PACKET_TYPE_ATTEMPT_JOIN) {
+ /* Either the sender wants a start position from us or has replied to one
+ * or our AJ messages.. Or it's some completely unrelated packet and we
+ * can handle it just like any other reliable packet */
+ guint i;
+ gboolean self_in_senders = FALSE;
+
+ for (i = 0; i < packet->data.attempt_join.senders->len; i++) {
+ guint32 id = g_array_index (packet->data.attempt_join.senders,
+ guint32, i);
+ if (id == priv->transport->sender_id) {
+ self_in_senders = TRUE;
+ break;
+ }
+ }
+
+ if (self_in_senders) {
+ /* New depends want start numbers */
+ update_foreign_member_list (self, packet,
+ MEMBER_STATE_ATTEMPT_JOIN_REPEAT);
+ send_attempt_join (self, TRUE);
+ return;
+ } else if (depends_list_contains (packet->depends,
+ priv->transport->sender_id)) {
+ if (member_get_state (self, packet->sender) ==
+ MEMBER_STATE_ATTEMPT_JOIN_STARTED) {
+ update_foreign_member_list (self, packet,
+ MEMBER_STATE_ATTEMPT_JOIN_DONE);
+ send_attempt_join (self, TRUE);
+ } else {
+ /* We got startinfo, including our own, but we didn't know we
+ * requested it! Ignore for now, some other node in our group will
+ * handle it. Don't care if we're the only one to receive this
+ * packet, at some point we'll know we did ask for the information
+ * and the sender will repeat it anyway.. Thus we'll do better some
+ * next time :) */
+ }
+ return;
+ }
+ }
+
+ if (packet->type != PACKET_TYPE_DATA
+ || packet->data.data.flags & GIBBER_R_MULTICAST_DATA_PACKET_START)
+ {
+ /* Foreign packet, with no mention of us.. Mark them as unknown */
+ update_foreign_member_list (self, packet, MEMBER_STATE_UNKNOWN);
+ send_attempt_join (self, FALSE);
+ }
+ }
+}
+
+
+static gboolean
+cmp_member_attempt_join_state (gpointer key, gpointer value,
+ gpointer user_data)
+{
+ MemberInfo *member = (MemberInfo *) value;
+ GibberRMulticastPacket *packet = GIBBER_R_MULTICAST_PACKET (user_data);
+
+ if (guint32_array_contains (packet->data.attempt_join.senders, member->id)) {
+ return member->state > MEMBER_STATE_ATTEMPT_JOIN_STARTED;
+ } else if (depends_list_contains (packet->depends, member->id)) {
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+
+/* Return 1 if the packets join state is a superset of ours, 0 if the info is
+ * the same and -1 if we have info the packet doesn't */
+static gint cmp_attempt_join_state (GibberRMulticastTransport *transport,
+ GibberRMulticastPacket *packet)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (transport);
+
+ g_assert (packet->type == PACKET_TYPE_ATTEMPT_JOIN);
+
+ if (g_hash_table_find (priv->members, cmp_member_attempt_join_state,
+ packet) != NULL) {
+ return -1;
+ }
+
+ if (!guint32_array_contains (packet->data.attempt_join.senders,
+ priv->transport->sender_id) && !depends_list_contains (packet->depends,
+ priv->transport->sender_id))
+ {
+ return -1;
+ }
+
+ if (g_hash_table_size (priv->members) + 1 ==
+ packet->data.attempt_join.senders->len + packet->depends->len) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static void
+send_failure_packet (GibberRMulticastTransport *self)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ guint i;
+
+ g_assert (priv->pending_failures != NULL);
+ g_assert (priv->pending_failures->len != 0);
+
+ gibber_r_multicast_causal_transport_send_failure (priv->transport,
+ priv->pending_failures);
+
+ for (i = 0; i < priv->pending_failures->len; i++)
+ {
+ GibberRMulticastSender *sender;
+ sender = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ g_array_index (priv->pending_failures, guint32, i));
+
+ if (sender != NULL)
+ gibber_r_multicast_sender_set_failed (sender);
+ }
+
+ priv->pending_failures =
+ g_array_remove_range (priv->pending_failures, 0,
+ priv->pending_failures->len);
+}
+
+static gboolean
+find_unfailed_member (gpointer key, gpointer value, gpointer user_data)
+{
+ guint i;
+ guint32 *id = (guint32 *) user_data;
+ MemberInfo *info = (MemberInfo *) value;
+
+ if (info->state < MEMBER_STATE_ATTEMPT_JOIN_DONE ||
+ info->state > MEMBER_STATE_MEMBER)
+ return FALSE;
+
+ for (i = 0; i < info->failures->len; i++)
+ {
+ if (*id == g_array_index (info->failures, guint32, i))
+ return FALSE;
+ }
+
+ DEBUG ("At least %x didn't fail %x", info->id, *id);
+
+ return TRUE;
+}
+
+static void
+remove_failure (gpointer key, gpointer value, gpointer user_data)
+{
+ guint32 *id = (guint32 *) user_data;
+ MemberInfo *info = (MemberInfo *) value;
+
+ guint32_array_remove (info->failures, *id);
+}
+
+static void
+collect_failed_members (gpointer key, gpointer value, gpointer user_data)
+{
+ MemberInfo *info = (MemberInfo *) value;
+ GArray *array = (GArray *) user_data;
+
+ if (info->state >= MEMBER_STATE_FAILING &&
+ info->state < MEMBER_STATE_INSTANT_FAILURE)
+ g_array_append_val (array, info->id);
+}
+
+static void
+recheck_failures (GibberRMulticastTransport *self)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(self);
+ GArray *array;
+ guint i;
+
+ array = g_array_new (FALSE, TRUE, sizeof (guint32));
+ g_hash_table_foreach (priv->members, collect_failed_members, array);
+
+ for (i = 0; i < array->len ; i++)
+ {
+ check_failure_completion (self,
+ g_array_index (array, guint32, i));
+ }
+
+ g_array_unref (array);
+}
+
+static void
+check_failure_completion (GibberRMulticastTransport *self, guint32 id)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(self);
+ MemberInfo *info, *tinfo;
+
+ tinfo = g_hash_table_find (priv->members, find_unfailed_member, &id);
+ if (tinfo != NULL)
+ {
+ DEBUG ("At least %x didn't complete the failure process for %x",
+ tinfo->id, id);
+ return;
+ }
+
+ info = member_get_info (self, id);
+ g_hash_table_foreach (priv->members, remove_failure, &id);
+
+ DEBUG ("Failure process finished for %x", id);
+ if (info->state == MEMBER_STATE_MEMBER_FAILING)
+ {
+ GibberRMulticastSender *sender;
+ GArray *lost = g_array_new (FALSE, FALSE, sizeof (gchar *));
+
+ sender = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ id);
+ g_array_append_val (lost, sender->name);
+ g_signal_emit (self, signals[LOST_SENDERS], 0, lost);
+ g_array_unref (lost);
+ }
+
+ gibber_r_multicast_causal_transport_remove_sender (priv->transport, id);
+
+ /* During the joining process, don't remove members as they still need to be
+ * flagged as failed during the join */
+ if (priv->state == STATE_JOINING)
+ {
+ /* Make it an instant failure as we already cleaned up this node */
+ info->state = MEMBER_STATE_INSTANT_FAILURE;
+ if (info->fail_timeout != 0)
+ {
+ g_source_remove (info->fail_timeout);
+ info->fail_timeout = 0;
+ }
+ check_join_agreement (self);
+ }
+ else
+ g_hash_table_remove (priv->members, &id);
+
+ /* Recheck all pending failures */
+ recheck_failures (self);
+}
+
+static gboolean
+fail_member_timeout_cb (gpointer user_data)
+{
+ MemberInfo *info = (MemberInfo *) user_data;
+ GibberRMulticastTransport *self = info->transport;
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(self);
+ MemberInfo *tinfo;
+
+ info->fail_timeout = 0;
+
+ while ((tinfo = g_hash_table_find (priv->members, find_unfailed_member,
+ &info->id)) != NULL)
+ {
+ DEBUG ("Failing %x because it didn't fail %x in time", tinfo->id,
+ info->id);
+ fail_member (self, NULL, tinfo->id);
+ }
+
+ return FALSE;
+}
+
+static void
+fail_member (GibberRMulticastTransport *self, MemberInfo *info, guint32 id)
+{
+ MemberInfo *finfo;
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ if (id == priv->transport->sender_id)
+ {
+ DEBUG ("Someone regarded us as a failure :(");
+ gibber_r_multicast_transport_reset (self);
+ return;
+ }
+
+ finfo = g_hash_table_lookup (priv->members, &id);
+
+ if (finfo == NULL || finfo->state < MEMBER_STATE_ATTEMPT_JOIN_REPEAT)
+ {
+ /* We don't know starting or end points of these, makes no sense to send
+ * failure messages for them. Will be handles at time of JOIN.
+ */
+ gibber_r_multicast_causal_transport_remove_sender (priv->transport, id);
+ if (finfo != NULL)
+ finfo->state = MEMBER_STATE_INSTANT_FAILURE;
+ return;
+ }
+
+ if (finfo->state < MEMBER_STATE_FAILING)
+ {
+ g_array_append_val (priv->pending_failures, id);
+ if (finfo->state == MEMBER_STATE_MEMBER)
+ finfo->state = MEMBER_STATE_MEMBER_FAILING;
+ else
+ finfo->state = MEMBER_STATE_FAILING;
+
+ finfo->fail_timeout = g_timeout_add (FAILURE_TIMEOUT,
+ fail_member_timeout_cb, finfo);
+ send_failure_packet (self);
+ }
+
+ if (info != NULL)
+ g_array_append_val (info->failures, id);
+
+ check_failure_completion (self, id);
+}
+
+
+static void
+handle_failure_packet (GibberRMulticastTransport *self,
+ GibberRMulticastPacket *packet)
+{
+ MemberInfo *info;
+ guint i;
+
+ info = member_get_info (self, packet->sender);
+ for (i = 0; i < packet->data.failure.failures->len; i++)
+ {
+ guint32 id = g_array_index (packet->data.failure.failures, guint32, i);
+
+ DEBUG ("%x failed %x", packet->sender, id);
+ fail_member (self, info, id);
+ }
+}
+
+static CheckJoinReturn
+check_join (GibberRMulticastTransport *self, GibberRMulticastPacket *packet)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+ guint i;
+ guint seen;
+ CheckJoinReturn result = JOIN_EQUAL;
+
+ g_assert (packet->type == PACKET_TYPE_JOIN);
+
+ for (i = 0; i < packet->data.join.failures->len; i++)
+ {
+ MemberInfo *info;
+ guint32 id;
+
+ id = g_array_index (packet->data.join.failures, guint32 , i);
+
+ if (id == priv->transport->sender_id)
+ {
+ /* Uh, oh, we failed! */
+ DEBUG ("Join says we are a failure :(");
+ gibber_r_multicast_transport_reset (self);
+ return JOIN_FAILED;
+ }
+
+ info = member_get_info (self, id);
+ if (info->state < MEMBER_STATE_ATTEMPT_JOIN_REPEAT)
+ {
+ info->state = MEMBER_STATE_INSTANT_FAILURE;
+ result = JOIN_LESS_INFO;
+ }
+ else if (info->state < MEMBER_STATE_MEMBER)
+ {
+ g_array_append_val (priv->pending_failures, id);
+ info->state = MEMBER_STATE_FAILING;
+ result = JOIN_LESS_INFO;
+ }
+ else if (info->state == MEMBER_STATE_MEMBER)
+ {
+ g_array_append_val (priv->pending_failures, id);
+ info->state = MEMBER_STATE_MEMBER_FAILING;
+ result = JOIN_LESS_INFO;
+ }
+ }
+
+ if (result != JOIN_EQUAL)
+ return result;
+
+ /* We send some failures this node doesn't know about yet, so the info isn't
+ * the same. But we might have less info iff it has non-failed members we
+ * don't know about */
+ if (priv->send_join_failures->len != packet->data.join.failures->len)
+ result = JOIN_MORE_INFO;
+
+
+ if (!depends_list_contains (packet->depends, priv->transport->sender_id))
+ {
+ /* Hmm, a join without us.. Odd.. Should not happen unless we're one of
+ * the failures.. Should be handled earlier. but reset anyways */
+ DEBUG ("Join doesn't contain us, shouldn't happen, reset to be sure :(");
+ gibber_r_multicast_transport_reset (self);
+ return JOIN_FAILED;
+ }
+
+ seen = 0;
+ for (i = 0; i < packet->depends->len; i++)
+ {
+ GibberRMulticastPacketSenderInfo *sinfo =
+ g_array_index (packet->depends,
+ GibberRMulticastPacketSenderInfo *, i);
+
+ if (guint32_array_contains (priv->send_join, sinfo->sender_id)
+ || sinfo->sender_id == priv->transport->sender_id)
+ {
+ seen++;
+ continue;
+ }
+
+ /* Not in send_join or ourselves, then it must be a failure.. */
+ if (!guint32_array_contains (priv->send_join_failures, sinfo->sender_id))
+ {
+ DEBUG ("Node %x is unknown to us. Marking as instant failure",
+ sinfo->sender_id);
+ result = JOIN_LESS_INFO;
+ member_set_state (self, sinfo->sender_id,
+ MEMBER_STATE_INSTANT_FAILURE);
+ }
+ }
+
+ /* Node's join didn't contain all senders we know about */
+ if (result == 0 && seen != priv->send_join->len)
+ result = JOIN_MORE_INFO;
+
+ return result;
+}
+
+static void
+release_data (gpointer key, MemberInfo *value, GibberRMulticastTransport *self)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(self);
+ GibberRMulticastSender *sender;
+
+ DEBUG ("Releasing data %x", value->id);
+ sender = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ value->id);
+
+ gibber_r_multicast_sender_release_data (sender);
+}
+
+static void
+check_join_agreement (GibberRMulticastTransport *self)
+{
+ guint i;
+ MemberInfo *info;
+ GibberRMulticastSender *sender;
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE(self);
+ GArray *new;
+ GArray *lost;
+
+ for (i = 0 ; i < priv->send_join->len; i++)
+ {
+ info = g_hash_table_lookup (priv->members,
+ &g_array_index (priv->send_join, guint32, i));
+
+ if (info == NULL)
+ continue;
+
+ /* send_join only contains members we didn't consider as failures at the
+ * time of the join message. The failure process for those has to be
+ * completely done before we can discard them, as it might have send out
+ * a JOIN we just hadn't received just yet.. Only after their failing
+ * has finished the state gets set to MEMBER_STATE_INSTANT_FAILURE */
+ if (info->state >= MEMBER_STATE_INSTANT_FAILURE)
+ continue;
+
+ if (!info->agreed_join)
+ return;
+ }
+
+ DEBUG ("---Finished joining phase!!!!---");
+ new = g_array_new (FALSE, FALSE, sizeof (gchar *));
+ lost = g_array_new (FALSE, FALSE, sizeof (gchar *));
+
+ for (i = 0 ; i < priv->send_join_failures->len; i++)
+ {
+ info = member_get_info (self,
+ g_array_index (priv->send_join_failures, guint32, i));
+ switch (info->state)
+ {
+ case MEMBER_STATE_MEMBER_FAILING: {
+ GibberRMulticastSender *sender_ =
+ gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ info->id);
+ if (info->state == MEMBER_STATE_MEMBER_FAILING)
+ {
+ gchar *name = g_strdup (sender_->name);
+ g_array_append_val (lost, name);
+ }
+ }
+ /* fallthrough */
+ case MEMBER_STATE_FAILING:
+ g_hash_table_foreach (priv->members, remove_failure, &(info->id));
+ gibber_r_multicast_causal_transport_remove_sender (priv->transport,
+ info->id);
+ break;
+ case MEMBER_STATE_INSTANT_FAILURE:
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ g_hash_table_remove (priv->members, &(info->id));
+ }
+
+ for (i = 0 ; i < priv->send_join->len; i++)
+ {
+ info = g_hash_table_lookup (priv->members,
+ &g_array_index (priv->send_join, guint32, i));
+
+ if (info == NULL)
+ continue;
+
+ if (info->state != MEMBER_STATE_MEMBER &&
+ info->state < MEMBER_STATE_FAILING)
+ {
+ GibberRMulticastSender *sender_;
+
+ sender_ = gibber_r_multicast_causal_transport_get_sender (
+ priv->transport, info->id);
+
+ DEBUG ("New member: %s (%x)", sender_->name, info->id);
+
+ info->state = MEMBER_STATE_MEMBER;
+ gibber_r_multicast_sender_set_data_start (sender_,
+ info->join_packet_id);
+ g_array_append_val (new, sender_->name);
+ }
+
+ if (info->state >= MEMBER_STATE_FAILING)
+ {
+ g_hash_table_remove (priv->members, &(info->id));
+ }
+ }
+
+ if (lost->len > 0)
+ g_signal_emit (self, signals[LOST_SENDERS], 0, lost);
+
+ if (new->len > 0)
+ g_signal_emit (self, signals[NEW_SENDERS], 0, new);
+
+ for (i = 0 ; i < lost->len ; i++) {
+ g_free (g_array_index (lost, gchar *, i));
+ }
+
+ g_array_unref (lost);
+ g_array_unref (new);
+
+ g_array_unref (priv->send_join);
+ priv->send_join = NULL;
+ g_array_unref (priv->send_join_failures);
+ priv->send_join_failures = NULL;
+ priv->state = STATE_NORMAL;
+
+ if (priv->timeout != 0)
+ {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+ DEBUG ("--------------------------------");
+
+ sender = gibber_r_multicast_causal_transport_get_sender (priv->transport,
+ priv->transport->sender_id);
+ gibber_r_multicast_sender_release_data (sender);
+ g_hash_table_foreach (priv->members, (GHFunc) release_data, self);
+}
+
+static void
+received_control_packet_cb (GibberRMulticastCausalTransport *ctransport,
+ GibberRMulticastSender *sender, GibberRMulticastPacket *packet,
+ gpointer user_data)
+{
+ GibberRMulticastTransport *self =
+ GIBBER_R_MULTICAST_TRANSPORT (user_data);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ DEBUG ("Received control packet");
+ switch (packet->type) {
+ case PACKET_TYPE_ATTEMPT_JOIN: {
+ int info_compare;
+ guint i;
+ gboolean changed = FALSE;
+ gboolean self_in_senders = FALSE;
+
+ if (priv->state == STATE_JOINING) {
+ /* Already started the joining process, so don't send or process new
+ * joiners */
+ break;
+ }
+
+ /* We can prevent/stop our join attempts iff some other node in our
+ * current group is sending out some with more info */
+ info_compare = cmp_attempt_join_state (self, packet);
+
+ for (i = 0; i < packet->data.attempt_join.senders->len; i++)
+ {
+ guint32 id = g_array_index (packet->data.attempt_join.senders,
+ guint32, i);
+ if (id == priv->transport->sender_id)
+ self_in_senders = TRUE;
+ changed |=
+ update_member (self, id, MEMBER_STATE_ATTEMPT_JOIN_STARTED, 0);
+ }
+
+ changed |= update_foreign_member_list (self, packet,
+ self_in_senders ? MEMBER_STATE_ATTEMPT_JOIN_REPEAT
+ : MEMBER_STATE_ATTEMPT_JOIN_DONE);
+
+ continue_gathering_phase (self);
+
+ switch (info_compare) {
+ case 1:
+ g_assert (changed);
+ /* Another sender is sending a more usefull version, stop repeating
+ * ours */
+ stop_send_attempt_join (self);
+ break;
+ case 0:
+ if (priv->timeout != 0 ||
+ packet->sender > priv->transport->sender_id) {
+ /* We didn't send out the latest info just yet so dont..
+ * Or we're sending equavalent info, so use the sender_id to get a
+ * winner */
+ stop_send_attempt_join (self);
+ }
+ break;
+ case -1:
+ /* We have info the sender didn't have. Only start a new attempt if
+ * something in our state actually changed */
+ if (changed)
+ send_attempt_join (self, FALSE);
+ break;
+ }
+ break;
+ }
+
+ case PACKET_TYPE_JOIN: {
+ MemberInfo *info;
+
+ DEBUG ("Received join from %s", sender->name);
+
+ if (priv->state < STATE_GATHERING)
+ {
+ DEBUG ("Received a join while not gathering or joining!?");
+ break;
+ }
+
+ info = g_hash_table_lookup (priv->members, &packet->sender);
+ gibber_r_multicast_sender_hold_data (sender, packet->packet_id);
+
+ info->join_packet_id = packet->packet_id;
+
+ if (priv->state == STATE_GATHERING)
+ {
+ setup_joining_phase (self);
+ if (check_join (self, packet) == JOIN_FAILED)
+ break;
+ start_joining_phase (self);
+ }
+
+ switch (check_join (self, packet)) {
+ case JOIN_EQUAL:
+ DEBUG ("%s agreed with our join", sender->name);
+ info->agreed_join = TRUE;
+ check_join_agreement (self);
+ break;
+ case JOIN_MORE_INFO:
+ /* Our join had more info, so we don't need to resent it */
+ DEBUG ("%s disagreed with our join with less info", sender->name);
+ gibber_r_multicast_sender_release_data (sender);
+ break;
+ case JOIN_LESS_INFO:
+ DEBUG ("%s disagreed with our join", sender->name);
+ /* Start the joining phase again */
+ start_joining_phase (self);
+ /* Either we send the same join, or we send more info */
+ if (check_join (self, packet) == JOIN_EQUAL)
+ info->agreed_join = TRUE;
+ check_join_agreement (self);
+ break;
+ case JOIN_FAILED:
+ /* We failed */
+ break;
+ }
+ break;
+ }
+ case PACKET_TYPE_FAILURE:
+ handle_failure_packet (self, packet);
+ break;
+ case PACKET_TYPE_BYE:
+ fail_member (self, NULL, packet->sender);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+sender_failed_cb (GibberRMulticastCausalTransport *ctransport,
+ GibberRMulticastSender *sender, gpointer user_data)
+{
+
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+
+ DEBUG ("%s (%x) failed", sender->name, sender->id);
+
+ fail_member (self, NULL, sender->id);
+}
+
+gboolean
+gibber_r_multicast_transport_connect (GibberRMulticastTransport *transport,
+ GError **error)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (transport);
+
+ g_assert (gibber_transport_get_state (GIBBER_TRANSPORT (priv->transport)) ==
+ GIBBER_TRANSPORT_CONNECTED);
+ g_assert (gibber_transport_get_state (GIBBER_TRANSPORT (transport)) !=
+ GIBBER_TRANSPORT_CONNECTED);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT(transport),
+ GIBBER_TRANSPORT_CONNECTED);
+
+ priv->state = STATE_NORMAL;
+
+ g_signal_connect (priv->transport, "received-foreign-packet",
+ G_CALLBACK (received_foreign_packet_cb), transport);
+
+ g_signal_connect (priv->transport, "received-control-packet",
+ G_CALLBACK (received_control_packet_cb), transport);
+
+ g_signal_connect (priv->transport, "sender-failed",
+ G_CALLBACK (sender_failed_cb), transport);
+
+ return TRUE;
+}
+
+gboolean
+gibber_r_multicast_transport_send (GibberRMulticastTransport *transport,
+ guint16 stream_id, const guint8 *data, gsize size, GError **error)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (transport);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->state == STATE_RESETTING)
+ return TRUE;
+
+ return gibber_r_multicast_causal_transport_send (priv->transport,
+ stream_id, data, size, error);
+}
+
+static gboolean
+gibber_r_multicast_transport_do_send (GibberTransport *transport,
+ const guint8 *data, gsize size, GError **error)
+{
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (transport);
+
+ if (priv->state == STATE_RESETTING)
+ return TRUE;
+
+ return gibber_transport_send (GIBBER_TRANSPORT (priv->transport),
+ data, size, error);
+}
+
+static void
+transport_disconnected (GibberTransport *transport,
+ gpointer user_data)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (user_data);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (self),
+ GIBBER_TRANSPORT_DISCONNECTED);
+}
+
+static void
+gibber_r_multicast_transport_disconnect (GibberTransport *transport)
+{
+ GibberRMulticastTransport *self = GIBBER_R_MULTICAST_TRANSPORT (transport);
+ GibberRMulticastTransportPrivate *priv =
+ GIBBER_R_MULTICAST_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->state == STATE_RESETTING)
+ {
+ g_signal_handler_disconnect (priv->transport, priv->reconnect_handler);
+ }
+ priv->state = STATE_DISCONNECTED;
+
+ /* Remove all the state we had. This will also stop all running timers */
+ gibber_r_multicast_transport_flush_state (self);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (self),
+ GIBBER_TRANSPORT_DISCONNECTING);
+
+ g_signal_connect (priv->transport, "disconnected",
+ G_CALLBACK(transport_disconnected), self);
+ gibber_transport_disconnect (GIBBER_TRANSPORT (priv->transport));
+}
diff --git a/salut/lib/gibber/gibber-r-multicast-transport.h b/salut/lib/gibber/gibber-r-multicast-transport.h
new file mode 100644
index 000000000..7ebdc3e3e
--- /dev/null
+++ b/salut/lib/gibber/gibber-r-multicast-transport.h
@@ -0,0 +1,78 @@
+/*
+ * gibber-r-multicast-transport.h - Header for GibberRMulticastTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_R_MULTICAST_TRANSPORT_H__
+#define __GIBBER_R_MULTICAST_TRANSPORT_H__
+
+#include <glib-object.h>
+#include "gibber-transport.h"
+#include "gibber-r-multicast-causal-transport.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberRMulticastTransport GibberRMulticastTransport;
+typedef struct _GibberRMulticastTransportClass GibberRMulticastTransportClass;
+
+struct _GibberRMulticastTransportClass {
+ GibberTransportClass parent_class;
+};
+
+struct _GibberRMulticastTransport {
+ GibberTransport parent;
+};
+
+typedef struct {
+ GibberBuffer buffer;
+ const gchar *sender;
+ guint16 stream_id;
+} GibberRMulticastBuffer;
+
+GType gibber_r_multicast_transport_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_R_MULTICAST_TRANSPORT \
+ (gibber_r_multicast_transport_get_type ())
+#define GIBBER_R_MULTICAST_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_R_MULTICAST_TRANSPORT, \
+ GibberRMulticastTransport))
+#define GIBBER_R_MULTICAST_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_R_MULTICAST_TRANSPORT, \
+ GibberRMulticastTransportClass))
+#define GIBBER_IS_R_MULTICAST_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_R_MULTICAST_TRANSPORT))
+#define GIBBER_IS_R_MULTICAST_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_R_MULTICAST_TRANSPORT))
+#define GIBBER_R_MULTICAST_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_R_MULTICAST_TRANSPORT, \
+ GibberRMulticastTransportClass))
+
+GibberRMulticastTransport * gibber_r_multicast_transport_new (
+ GibberRMulticastCausalTransport *transport);
+
+gboolean gibber_r_multicast_transport_connect (
+ GibberRMulticastTransport *transport, GError **error);
+
+gboolean gibber_r_multicast_transport_send (
+ GibberRMulticastTransport *transport, guint16 stream_id,
+ const guint8 *data, gsize size, GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_R_MULTICAST_TRANSPORT_H__*/
diff --git a/salut/lib/gibber/gibber-sockets-unix.h b/salut/lib/gibber/gibber-sockets-unix.h
new file mode 100644
index 000000000..bf59331ff
--- /dev/null
+++ b/salut/lib/gibber/gibber-sockets-unix.h
@@ -0,0 +1,61 @@
+/* To be included by gibber-sockets.h only.
+ * Do not include this header directly. */
+
+/*
+ * gibber-sockets-unix.h - meta-header for assorted semi-portable socket code
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+/* prerequisite for stdlib.h on Darwin etc., according to autoconf.info */
+#include <stdio.h>
+/* prerequisite for sys/socket.h on Darwin, according to autoconf.info */
+#include <stdlib.h>
+/* prerequisite for all sorts of things */
+#include <sys/types.h>
+
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+#ifdef HAVE_ARPA_NAMESER_H
+# include <arpa/nameser.h>
+#endif
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#ifdef HAVE_RESOLV_H
+ /* autoconf.info recommends putting sys/types.h, netinet/in.h,
+ * arpa/nameser.h, netdb.h before this one; if you re-order this header,
+ * please keep that true */
+# include <resolv.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
diff --git a/salut/lib/gibber/gibber-sockets-win32.h b/salut/lib/gibber/gibber-sockets-win32.h
new file mode 100644
index 000000000..d35e3cbdf
--- /dev/null
+++ b/salut/lib/gibber/gibber-sockets-win32.h
@@ -0,0 +1,29 @@
+/* To be included by gibber-sockets.h only.
+ * Do not include this header directly. */
+
+/*
+ * gibber-sockets-win32.h - meta-header for assorted semi-portable socket code
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <Windows.h>
+#include <Winbase.h>
+/* Winsock makes some inappropriately-namespaced definitions */
+#undef ERROR
+#undef interface
diff --git a/salut/lib/gibber/gibber-sockets.c b/salut/lib/gibber/gibber-sockets.c
new file mode 100644
index 000000000..bcedeb53b
--- /dev/null
+++ b/salut/lib/gibber/gibber-sockets.c
@@ -0,0 +1,112 @@
+/*
+ * gibber-sockets.c - basic portability wrappers for BSD/Winsock differences
+ *
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+#include "gibber-sockets.h"
+
+#include <errno.h>
+
+#define DEBUG_FLAG DEBUG_NET
+#include "gibber-debug.h"
+
+gboolean
+gibber_connect_errno_requires_retry (void)
+{
+#ifdef G_OS_WIN32
+ int err = WSAGetLastError ();
+
+ return (err == WSAEINPROGRESS || err == WSAEALREADY);
+#else
+ return (errno == EINPROGRESS || errno == EALREADY);
+#endif
+}
+
+gint
+gibber_socket_errno (void)
+{
+#ifdef G_OS_WIN32
+ return WSAGetLastError ();
+#else
+ return errno;
+#endif
+}
+
+const gchar *
+gibber_socket_strerror (void)
+{
+#ifdef G_OS_WIN32
+ return "[no strerror() in winsock :-(]";
+#else
+ return g_strerror (errno);
+#endif
+}
+
+gboolean
+gibber_socket_errno_is_eafnosupport (void)
+{
+#ifdef G_OS_WIN32
+ return (WSAGetLastError () == WSAEAFNOSUPPORT);
+#else
+ return (errno == EAFNOSUPPORT);
+#endif
+}
+
+gboolean
+gibber_socket_errno_is_eaddrinuse (void)
+{
+#ifdef G_OS_WIN32
+ return (WSAGetLastError () == WSAEADDRINUSE);
+#else
+ return (errno == EADDRINUSE);
+#endif
+}
+
+void
+gibber_socket_set_error (GError **error, const gchar *context,
+ GQuark domain, gint code)
+{
+ gint err = gibber_socket_errno ();
+ const gchar *str = gibber_socket_strerror ();
+
+ DEBUG ("%s: #%d %s", context, err, str);
+ g_set_error (error, domain, code, "%s: #%d %s", context, err, str);
+}
+
+GIOChannel *
+gibber_io_channel_new_from_socket (gint sockfd)
+{
+#ifdef G_OS_WIN32
+ return g_io_channel_win32_new_socket (sockfd);
+#else
+ return g_io_channel_unix_new (sockfd);
+#endif
+}
+
+void
+gibber_socket_set_nonblocking (gint sockfd)
+{
+#ifdef G_OS_WIN32
+ u_long please_dont_block = 1;
+
+ ioctlsocket (sockfd, FIONBIO, &please_dont_block);
+#else
+ fcntl (sockfd, F_SETFL, O_NONBLOCK);
+#endif
+}
diff --git a/salut/lib/gibber/gibber-sockets.h b/salut/lib/gibber/gibber-sockets.h
new file mode 100644
index 000000000..5cfb3c8b3
--- /dev/null
+++ b/salut/lib/gibber/gibber-sockets.h
@@ -0,0 +1,49 @@
+/*
+ * gibber-sockets.h - meta-header for assorted semi-portable socket code
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#ifndef GIBBER_SOCKETS_H
+#define GIBBER_SOCKETS_H
+
+#include <glib.h>
+
+#ifdef G_OS_WIN32
+# include "gibber-sockets-win32.h"
+#else
+# include "gibber-sockets-unix.h"
+#endif
+
+G_BEGIN_DECLS
+
+gboolean gibber_connect_errno_requires_retry (void);
+gboolean gibber_socket_errno_is_eafnosupport (void);
+gboolean gibber_socket_errno_is_eaddrinuse (void);
+void gibber_socket_set_error (GError **error, const gchar *context,
+ GQuark domain, gint code);
+gint gibber_socket_errno (void);
+const gchar *gibber_socket_strerror (void);
+
+GIOChannel *gibber_io_channel_new_from_socket (gint sockfd);
+
+void gibber_socket_set_nonblocking (gint sockfd);
+
+G_END_DECLS
+
+#endif
diff --git a/salut/lib/gibber/gibber-tcp-transport.c b/salut/lib/gibber/gibber-tcp-transport.c
new file mode 100644
index 000000000..efe2fc8a8
--- /dev/null
+++ b/salut/lib/gibber/gibber-tcp-transport.c
@@ -0,0 +1,290 @@
+/*
+ * gibber-tcp-transport.c - Source for GibberTCPTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <errno.h>
+#include <string.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "gibber-sockets.h"
+#include "gibber-tcp-transport.h"
+
+#define DEBUG_FLAG DEBUG_NET
+#include "gibber-debug.h"
+
+#include "errno.h"
+
+G_DEFINE_TYPE(GibberTCPTransport, gibber_tcp_transport,
+ GIBBER_TYPE_FD_TRANSPORT)
+
+/* private structure */
+typedef struct _GibberTCPTransportPrivate GibberTCPTransportPrivate;
+
+struct _GibberTCPTransportPrivate
+{
+ GIOChannel *channel;
+ struct addrinfo *ans;
+ struct addrinfo *tmpaddr;
+ guint watch_in;
+
+ gboolean dispose_has_run;
+};
+
+#define GIBBER_TCP_TRANSPORT_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_TCP_TRANSPORT, \
+ GibberTCPTransportPrivate))
+
+static void
+gibber_tcp_transport_init (GibberTCPTransport *obj)
+{
+ /* GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (obj);
+ */
+
+ /* allocate any data required by the object here */
+}
+
+static void gibber_tcp_transport_dispose (GObject *object);
+static void gibber_tcp_transport_finalize (GObject *object);
+
+static void
+gibber_tcp_transport_class_init (
+ GibberTCPTransportClass *gibber_tcp_transport_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_tcp_transport_class);
+
+ g_type_class_add_private (gibber_tcp_transport_class,
+ sizeof (GibberTCPTransportPrivate));
+
+ object_class->dispose = gibber_tcp_transport_dispose;
+ object_class->finalize = gibber_tcp_transport_finalize;
+}
+
+static void
+clean_connect_attempt (GibberTCPTransport *self)
+{
+ GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (
+ self);
+
+ if (priv->watch_in != 0)
+ {
+ g_source_remove (priv->watch_in);
+ priv->watch_in = 0;
+ }
+
+ if (priv->channel != NULL)
+ {
+ g_io_channel_unref (priv->channel);
+ priv->channel = NULL;
+ }
+}
+
+static void
+clean_all_connect_attempts (GibberTCPTransport *self)
+{
+ GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (
+ self);
+
+ clean_connect_attempt (self);
+
+ if (priv->ans != NULL)
+ {
+ freeaddrinfo (priv->ans);
+ priv->ans = NULL;
+ }
+
+ priv->tmpaddr = NULL;
+}
+
+void
+gibber_tcp_transport_dispose (GObject *object)
+{
+ GibberTCPTransport *self = GIBBER_TCP_TRANSPORT (object);
+ GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ clean_all_connect_attempts (self);
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (gibber_tcp_transport_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_tcp_transport_parent_class)->dispose (object);
+}
+
+void
+gibber_tcp_transport_finalize (GObject *object)
+{
+ /*
+ GibberTCPTransport *self = GIBBER_TCP_TRANSPORT (object);
+ GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (self);
+ */
+
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (gibber_tcp_transport_parent_class)->finalize (object);
+}
+
+GibberTCPTransport *
+gibber_tcp_transport_new ()
+{
+ return g_object_new (GIBBER_TYPE_TCP_TRANSPORT, NULL);
+}
+
+static void new_connect_attempt (GibberTCPTransport *self);
+
+static gboolean
+try_to_connect (GibberTCPTransport *self)
+{
+ GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (
+ self);
+ gint fd;
+ int ret;
+
+ g_assert (priv->channel != NULL);
+
+ fd = g_io_channel_unix_get_fd (priv->channel);
+ ret = connect (fd, priv->tmpaddr->ai_addr, priv->tmpaddr->ai_addrlen);
+
+ if (ret == 0)
+ {
+ DEBUG ("connect succeeded");
+
+ clean_all_connect_attempts (self);
+ gibber_fd_transport_set_fd (GIBBER_FD_TRANSPORT (self), fd, TRUE);
+ return FALSE;
+ }
+
+ if (gibber_connect_errno_requires_retry ())
+ {
+ /* We have to wait longer */
+ return TRUE;
+ }
+
+ clean_connect_attempt (self);
+ priv->tmpaddr = priv->tmpaddr->ai_next;
+ new_connect_attempt (self);
+ return FALSE;
+}
+
+static gboolean
+_channel_io (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ GibberTCPTransport *self = GIBBER_TCP_TRANSPORT (data);
+
+ return try_to_connect (self);
+}
+
+static void
+new_connect_attempt (GibberTCPTransport *self)
+{
+ GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (
+ self);
+ int fd;
+ char name[NI_MAXHOST], portname[NI_MAXSERV];
+
+ if (priv->tmpaddr == NULL)
+ {
+ /* no more candidate to try */
+ DEBUG ("connection failed");
+ goto failed;
+ }
+
+ getnameinfo (priv->tmpaddr->ai_addr, priv->tmpaddr->ai_addrlen,
+ name, sizeof (name), portname, sizeof (portname),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ DEBUG ("Trying %s port %s...", name, portname);
+
+ fd = socket (priv->tmpaddr->ai_family, priv->tmpaddr->ai_socktype,
+ priv->tmpaddr->ai_protocol);
+
+ if (fd < 0)
+ {
+ DEBUG("socket failed: #%d %s", gibber_socket_errno (),
+ gibber_socket_strerror ());
+ goto failed;
+ }
+
+ gibber_socket_set_nonblocking (fd);
+ priv->channel = gibber_io_channel_new_from_socket (fd);
+ g_io_channel_set_close_on_unref (priv->channel, FALSE);
+ g_io_channel_set_encoding (priv->channel, NULL, NULL);
+ g_io_channel_set_buffered (priv->channel, FALSE);
+
+ priv->watch_in = g_io_add_watch (priv->channel, G_IO_IN | G_IO_PRI | G_IO_OUT,
+ _channel_io, self);
+
+ try_to_connect (self);
+ return;
+
+failed:
+ clean_all_connect_attempts (self);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (self),
+ GIBBER_TRANSPORT_DISCONNECTED);
+}
+
+void
+gibber_tcp_transport_connect (GibberTCPTransport *tcp_transport,
+ const gchar *host, const gchar *port)
+{
+ GibberTCPTransportPrivate *priv = GIBBER_TCP_TRANSPORT_GET_PRIVATE (
+ tcp_transport);
+ int ret = -1;
+ struct addrinfo req;
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (tcp_transport),
+ GIBBER_TRANSPORT_CONNECTING);
+
+ memset (&req, 0, sizeof (req));
+ req.ai_flags = 0;
+ req.ai_family = AF_UNSPEC;
+ req.ai_socktype = SOCK_STREAM;
+ req.ai_protocol = IPPROTO_TCP;
+
+ g_assert (priv->ans == NULL);
+ g_assert (priv->tmpaddr == NULL);
+ g_assert (priv->channel == NULL);
+
+ ret = getaddrinfo (host, port, &req, &priv->ans);
+ if (ret != 0)
+ {
+ DEBUG("getaddrinfo failed: %s", gai_strerror (ret));
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (tcp_transport),
+ GIBBER_TRANSPORT_DISCONNECTED);
+ return;
+ }
+
+ priv->tmpaddr = priv->ans;
+
+ new_connect_attempt (tcp_transport);
+}
diff --git a/salut/lib/gibber/gibber-tcp-transport.h b/salut/lib/gibber/gibber-tcp-transport.h
new file mode 100644
index 000000000..642fdcdcd
--- /dev/null
+++ b/salut/lib/gibber/gibber-tcp-transport.h
@@ -0,0 +1,67 @@
+/*
+ * gibber-tcp-transport.h - Header for GibberTCPTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_TCP_TRANSPORT_H__
+#define __GIBBER_TCP_TRANSPORT_H__
+
+#include <glib-object.h>
+#include "gibber-fd-transport.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberTCPTransport GibberTCPTransport;
+typedef struct _GibberTCPTransportClass GibberTCPTransportClass;
+
+struct _GibberTCPTransportClass {
+ GibberFdTransportClass parent_class;
+};
+
+struct _GibberTCPTransport {
+ GibberFdTransport parent;
+};
+
+GType gibber_tcp_transport_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_TCP_TRANSPORT \
+ (gibber_tcp_transport_get_type ())
+#define GIBBER_TCP_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_TCP_TRANSPORT, \
+ GibberTCPTransport))
+#define GIBBER_TCP_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_TCP_TRANSPORT, \
+ GibberTCPTransportClass))
+#define GIBBER_IS_TCP_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_TCP_TRANSPORT))
+#define GIBBER_IS_TCP_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_TCP_TRANSPORT))
+#define GIBBER_TCP_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_TCP_TRANSPORT, \
+ GibberTCPTransportClass))
+
+GibberTCPTransport *
+gibber_tcp_transport_new (void);
+
+void gibber_tcp_transport_connect (GibberTCPTransport *tcp_transport,
+ const gchar *host, const gchar *port);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_TCP_TRANSPORT_H__*/
diff --git a/salut/lib/gibber/gibber-transport.c b/salut/lib/gibber/gibber-transport.c
new file mode 100644
index 000000000..82ad73b33
--- /dev/null
+++ b/salut/lib/gibber/gibber-transport.c
@@ -0,0 +1,296 @@
+/*
+ * gibber-transport.c - Source for GibberTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "gibber-transport.h"
+#include "gibber-signals-marshal.h"
+
+#define DEBUG_FLAG DEBUG_TRANSPORT
+#include "gibber-debug.h"
+
+G_DEFINE_TYPE(GibberTransport, gibber_transport, G_TYPE_OBJECT)
+
+/* signal enum */
+enum
+{
+ CONNECTED,
+ CONNECTING,
+ DISCONNECTED,
+ DISCONNECTING,
+ ERROR,
+ BUFFER_EMPTY,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+typedef struct _GibberTransportPrivate GibberTransportPrivate;
+
+struct _GibberTransportPrivate
+{
+ gboolean dispose_has_run;
+};
+
+#define GIBBER_TRANSPORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_TRANSPORT, GibberTransportPrivate))
+
+static void
+gibber_transport_init (GibberTransport *obj)
+{
+ obj->state = GIBBER_TRANSPORT_DISCONNECTED;
+ obj->handler = NULL;
+}
+
+static void gibber_transport_dispose (GObject *object);
+static void gibber_transport_finalize (GObject *object);
+
+static void
+gibber_transport_class_init (GibberTransportClass *gibber_transport_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_transport_class);
+
+ g_type_class_add_private (gibber_transport_class, sizeof (GibberTransportPrivate));
+
+ object_class->dispose = gibber_transport_dispose;
+ object_class->finalize = gibber_transport_finalize;
+
+ signals[BUFFER_EMPTY] =
+ g_signal_new ("buffer-empty",
+ G_OBJECT_CLASS_TYPE (gibber_transport_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CONNECTED] =
+ g_signal_new ("connected",
+ G_OBJECT_CLASS_TYPE (gibber_transport_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CONNECTING] =
+ g_signal_new ("connecting",
+ G_OBJECT_CLASS_TYPE (gibber_transport_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[DISCONNECTING] =
+ g_signal_new ("disconnecting",
+ G_OBJECT_CLASS_TYPE (gibber_transport_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[DISCONNECTED] =
+ g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (gibber_transport_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[ERROR] =
+ g_signal_new ("error",
+ G_OBJECT_CLASS_TYPE (gibber_transport_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _gibber_signals_marshal_VOID__UINT_INT_STRING,
+ G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING);
+}
+
+void
+gibber_transport_dispose (GObject *object)
+{
+ GibberTransport *self = GIBBER_TRANSPORT (object);
+ GibberTransportPrivate *priv = GIBBER_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (gibber_transport_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_transport_parent_class)->dispose (object);
+}
+
+void
+gibber_transport_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (gibber_transport_parent_class)->finalize (object);
+}
+
+void
+gibber_transport_received_data (GibberTransport *transport,
+ const guint8 *data, gsize length)
+{
+ GibberBuffer buffer;
+ buffer.length = length;
+ buffer.data = data;
+
+ gibber_transport_received_data_custom (transport, &buffer);
+}
+
+void
+gibber_transport_received_data_custom (GibberTransport *transport,
+ GibberBuffer *buffer)
+{
+ if (G_UNLIKELY (transport->handler == NULL))
+ {
+ DEBUG("No handler for transport, dropping data!");
+ }
+ else
+ {
+ transport->handler (transport, buffer, transport->user_data);
+ }
+}
+
+void
+gibber_transport_set_state (GibberTransport *transport,
+ GibberTransportState state)
+{
+ if (state != transport->state)
+ {
+ transport->state = state;
+
+ switch (state)
+ {
+ case GIBBER_TRANSPORT_DISCONNECTED:
+ g_signal_emit (transport, signals[DISCONNECTED], 0);
+ break;
+ case GIBBER_TRANSPORT_CONNECTING:
+ g_signal_emit (transport, signals[CONNECTING], 0);
+ break;
+ case GIBBER_TRANSPORT_CONNECTED:
+ g_signal_emit (transport, signals[CONNECTED], 0);
+ break;
+ case GIBBER_TRANSPORT_DISCONNECTING:
+ g_signal_emit (transport, signals[DISCONNECTING], 0);
+ break;
+ }
+ }
+}
+
+GibberTransportState
+gibber_transport_get_state (GibberTransport *transport)
+{
+ return transport->state;
+}
+
+void
+gibber_transport_emit_error (GibberTransport *transport, GError *error)
+{
+ DEBUG ("Transport error: %s", error->message);
+ g_signal_emit (transport, signals[ERROR], 0,
+ error->domain, error->code, error->message);
+}
+
+gboolean
+gibber_transport_send (GibberTransport *transport, const guint8 *data,
+ gsize size, GError **error)
+{
+ GibberTransportClass *cls = GIBBER_TRANSPORT_GET_CLASS (transport);
+
+ g_assert (transport->state == GIBBER_TRANSPORT_CONNECTED);
+
+ return cls->send (transport, data, size, error);
+}
+
+void
+gibber_transport_disconnect (GibberTransport *transport)
+{
+ GibberTransportClass *cls = GIBBER_TRANSPORT_GET_CLASS (transport);
+ return cls->disconnect (transport);
+}
+
+void
+gibber_transport_set_handler (GibberTransport *transport,
+ GibberHandlerFunc func, gpointer user_data)
+{
+ g_assert (transport->handler == NULL || func == NULL);
+
+ transport->handler = func;
+ transport->user_data = user_data;
+}
+
+gboolean
+gibber_transport_get_peeraddr (GibberTransport *transport,
+ struct sockaddr_storage *addr, socklen_t *len)
+{
+ GibberTransportClass *cls = GIBBER_TRANSPORT_GET_CLASS (transport);
+
+ if (cls->get_peeraddr != NULL)
+ return cls->get_peeraddr (transport, addr, len);
+
+ return FALSE;
+}
+
+gboolean
+gibber_transport_get_sockaddr (GibberTransport *transport,
+ struct sockaddr_storage *addr, socklen_t *len)
+{
+ GibberTransportClass *cls = GIBBER_TRANSPORT_GET_CLASS (transport);
+
+ if (cls->get_sockaddr != NULL)
+ return cls->get_sockaddr (transport, addr, len);
+
+ return FALSE;
+}
+
+gboolean
+gibber_transport_buffer_is_empty (GibberTransport *transport)
+{
+ GibberTransportClass *cls = GIBBER_TRANSPORT_GET_CLASS (transport);
+
+ g_assert (cls->buffer_is_empty != NULL);
+ return cls->buffer_is_empty (transport);
+}
+
+void
+gibber_transport_emit_buffer_empty (GibberTransport *transport)
+{
+ g_signal_emit (transport, signals[BUFFER_EMPTY], 0);
+}
+
+void
+gibber_transport_block_receiving (GibberTransport *transport,
+ gboolean block)
+{
+ GibberTransportClass *cls = GIBBER_TRANSPORT_GET_CLASS (transport);
+
+ g_assert (cls->block_receiving != NULL);
+ cls->block_receiving (transport, block);
+}
+
diff --git a/salut/lib/gibber/gibber-transport.h b/salut/lib/gibber/gibber-transport.h
new file mode 100644
index 000000000..2e79cdd89
--- /dev/null
+++ b/salut/lib/gibber/gibber-transport.h
@@ -0,0 +1,131 @@
+/*
+ * gibber-transport.h - Header for GibberTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_TRANSPORT_H__
+#define __GIBBER_TRANSPORT_H__
+
+#include <glib-object.h>
+
+#include "gibber-sockets.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GIBBER_TRANSPORT_DISCONNECTED = 0,
+ GIBBER_TRANSPORT_CONNECTING,
+ GIBBER_TRANSPORT_CONNECTED,
+ GIBBER_TRANSPORT_DISCONNECTING,
+} GibberTransportState;
+
+
+typedef struct _GibberTransport GibberTransport;
+typedef struct _GibberTransportClass GibberTransportClass;
+typedef struct _GibberBuffer GibberBuffer;
+typedef void (*GibberHandlerFunc) (GibberTransport *transport,
+ GibberBuffer *buffer,
+ gpointer user_data);
+
+struct _GibberBuffer {
+ const guint8 *data;
+ gsize length;
+};
+
+struct _GibberTransportClass {
+ GObjectClass parent_class;
+ gboolean (*send) (GibberTransport *transport,
+ const guint8 *data, gsize length, GError **error);
+ void (*disconnect) (GibberTransport *transport);
+ gboolean (*get_peeraddr) (GibberTransport *transport,
+ struct sockaddr_storage *addr, socklen_t *len);
+ gboolean (*get_sockaddr) (GibberTransport *transport,
+ struct sockaddr_storage *addr, socklen_t *len);
+ gboolean (*buffer_is_empty) (GibberTransport *transport);
+ void (*block_receiving) (GibberTransport *transport, gboolean block);
+};
+
+struct _GibberTransport {
+ GObject parent;
+ GibberTransportState state;
+
+ /* Maximum packet size for transports where it matters, 0 otherwise */
+ gsize max_packet_size;
+
+ /* FIXME Should be private... */
+ GibberHandlerFunc handler;
+ gpointer user_data;
+};
+
+GType gibber_transport_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_TRANSPORT (gibber_transport_get_type ())
+#define GIBBER_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_TRANSPORT, GibberTransport))
+#define GIBBER_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_TRANSPORT, \
+ GibberTransportClass))
+#define GIBBER_IS_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_TRANSPORT))
+#define GIBBER_IS_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_TRANSPORT))
+#define GIBBER_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_TRANSPORT, \
+ GibberTransportClass))
+
+/* Utility functions for the classes based on GibberTransport */
+void gibber_transport_received_data (GibberTransport *transport,
+ const guint8 *data, gsize length);
+
+void gibber_transport_received_data_custom (GibberTransport *transport,
+ GibberBuffer *buffer);
+
+void gibber_transport_set_state (GibberTransport *transport,
+ GibberTransportState state);
+
+void gibber_transport_emit_error (GibberTransport *transport, GError *error);
+
+/* Public api */
+GibberTransportState gibber_transport_get_state (GibberTransport *transport);
+
+gboolean gibber_transport_send (GibberTransport *transport, const guint8 *data,
+ gsize size, GError **error);
+
+void gibber_transport_disconnect (GibberTransport *transport);
+
+void gibber_transport_set_handler (GibberTransport *transport,
+ GibberHandlerFunc func, gpointer user_data);
+
+gboolean gibber_transport_get_peeraddr (GibberTransport *transport,
+ struct sockaddr_storage *addr, socklen_t *len);
+
+gboolean gibber_transport_get_sockaddr (GibberTransport *transport,
+ struct sockaddr_storage *addr,
+ socklen_t *len);
+
+gboolean gibber_transport_buffer_is_empty (GibberTransport *transport);
+
+void gibber_transport_emit_buffer_empty (GibberTransport *transport);
+
+void gibber_transport_block_receiving (GibberTransport *transport,
+ gboolean block);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_TRANSPORT_H__*/
diff --git a/salut/lib/gibber/gibber-unix-transport.c b/salut/lib/gibber/gibber-unix-transport.c
new file mode 100644
index 000000000..884bac6d1
--- /dev/null
+++ b/salut/lib/gibber/gibber-unix-transport.c
@@ -0,0 +1,414 @@
+/*
+ * gibber-linklocal-transport.c - Source for GibberLLTransport
+ * Copyright (C) 2006, 2008 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ * @author: Alban Crequy <alban.crequy@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* needed for struct ucred */
+#define _GNU_SOURCE
+
+#include <glib.h>
+
+#ifdef G_OS_UNIX
+
+/* If you claim to be Unix but you don't have these headers, you may have
+ * already lost. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "gibber-unix-transport.h"
+#include "gibber-util.h"
+
+#define DEBUG_FLAG DEBUG_NET
+#include "gibber-debug.h"
+
+G_DEFINE_TYPE(GibberUnixTransport, gibber_unix_transport, \
+ GIBBER_TYPE_FD_TRANSPORT)
+
+GQuark
+gibber_unix_transport_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string ("gibber_unix_transport_error");
+
+ return quark;
+}
+
+/* private structure */
+typedef struct _GibberUnixTransportPrivate GibberUnixTransportPrivate;
+
+struct _GibberUnixTransportPrivate
+{
+ gboolean incoming;
+
+ GibberUnixTransportRecvCredentialsCb recv_creds_cb;
+ gpointer recv_creds_data;
+
+ gboolean dispose_has_run;
+};
+
+#define GIBBER_UNIX_TRANSPORT_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_UNIX_TRANSPORT, \
+ GibberUnixTransportPrivate))
+
+static void gibber_unix_transport_finalize (GObject *object);
+
+static void
+gibber_unix_transport_init (GibberUnixTransport *self)
+{
+ GibberUnixTransportPrivate *priv = GIBBER_UNIX_TRANSPORT_GET_PRIVATE (self);
+ priv->incoming = FALSE;
+}
+
+static void gibber_unix_transport_dispose (GObject *object);
+static GibberFdIOResult gibber_unix_transport_read (
+ GibberFdTransport *transport,
+ GIOChannel *channel,
+ GError **error);
+
+static void
+gibber_unix_transport_class_init (
+ GibberUnixTransportClass *gibber_unix_transport_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gibber_unix_transport_class);
+ GibberFdTransportClass *fd_class = GIBBER_FD_TRANSPORT_CLASS (
+ gibber_unix_transport_class);
+
+ g_type_class_add_private (gibber_unix_transport_class,
+ sizeof (GibberUnixTransportPrivate));
+
+ object_class->dispose = gibber_unix_transport_dispose;
+ object_class->finalize = gibber_unix_transport_finalize;
+
+ /* override GibberFdTransport's read */
+ fd_class->read = gibber_unix_transport_read;
+}
+
+void
+gibber_unix_transport_dispose (GObject *object)
+{
+ GibberUnixTransport *self = GIBBER_UNIX_TRANSPORT (object);
+ GibberUnixTransportPrivate *priv = GIBBER_UNIX_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ priv->recv_creds_cb = NULL;
+ priv->recv_creds_data = NULL;
+
+ if (G_OBJECT_CLASS (gibber_unix_transport_parent_class)->dispose)
+ G_OBJECT_CLASS (gibber_unix_transport_parent_class)->dispose (object);
+}
+
+void
+gibber_unix_transport_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (gibber_unix_transport_parent_class)->finalize (object);
+}
+
+GibberUnixTransport *
+gibber_unix_transport_new (void)
+{
+ return g_object_new (GIBBER_TYPE_UNIX_TRANSPORT, NULL);
+}
+
+gboolean
+gibber_unix_transport_connect (GibberUnixTransport *transport,
+ const gchar *path,
+ GError **error)
+{
+ union {
+ struct sockaddr_un un;
+ struct sockaddr addr;
+ } addr;
+ int fd;
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (transport),
+ GIBBER_TRANSPORT_CONNECTING);
+
+ memset (&addr, 0, sizeof (addr));
+
+ fd = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ DEBUG ("Error creating socket: %s", g_strerror (errno));
+ g_set_error (error, GIBBER_UNIX_TRANSPORT_ERROR,
+ GIBBER_UNIX_TRANSPORT_ERROR_CONNECT_FAILED,
+ "Error creating socket: %s", g_strerror (errno));
+ goto failed;
+ }
+
+ addr.un.sun_family = PF_UNIX;
+ g_strlcpy (addr.un.sun_path, path, sizeof (addr.un.sun_path));
+
+ if (connect (fd, &addr.addr, sizeof (addr.un)) == -1)
+ {
+ g_set_error (error, GIBBER_UNIX_TRANSPORT_ERROR,
+ GIBBER_UNIX_TRANSPORT_ERROR_CONNECT_FAILED,
+ "Error connecting socket: %s", g_strerror (errno));
+ DEBUG ("Error connecting socket: %s", g_strerror (errno));
+ goto failed;
+ }
+ DEBUG ("Connected to socket");
+
+ gibber_fd_transport_set_fd (GIBBER_FD_TRANSPORT (transport), fd, TRUE);
+
+ return TRUE;
+
+failed:
+ g_assert (error != NULL);
+ gibber_transport_emit_error (GIBBER_TRANSPORT(transport), *error);
+
+ gibber_transport_set_state (GIBBER_TRANSPORT (transport),
+ GIBBER_TRANSPORT_DISCONNECTED);
+ return FALSE;
+}
+
+GibberUnixTransport *
+gibber_unix_transport_new_from_fd (int fd)
+{
+ GibberUnixTransport *transport;
+
+ transport = gibber_unix_transport_new ();
+ gibber_fd_transport_set_fd (GIBBER_FD_TRANSPORT (transport), fd, TRUE);
+ return transport;
+}
+
+/* Patches that reimplement these functions for non-Linux would be welcome
+ * (please file a bug) */
+
+#if defined(__linux__)
+
+gboolean
+gibber_unix_transport_supports_credentials (void)
+{
+ return TRUE;
+}
+
+gboolean
+gibber_unix_transport_send_credentials (GibberUnixTransport *transport,
+ const guint8 *data,
+ gsize size)
+{
+ int fd, ret;
+ struct ucred *cred;
+ struct msghdr msg;
+ struct cmsghdr *ch;
+ struct iovec iov;
+ char buffer[CMSG_SPACE (sizeof (struct ucred))];
+
+ DEBUG ("send credentials");
+ fd = GIBBER_FD_TRANSPORT (transport)->fd;
+
+ /* Set the message payload */
+ memset (&iov, 0, sizeof (iov));
+ iov.iov_base = (void *) data;
+ iov.iov_len = size;
+
+ memset (&msg, 0, sizeof (msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = buffer;
+ msg.msg_controllen = sizeof (buffer);
+ memset (buffer, 0, sizeof (buffer));
+
+ /* Set the credentials */
+ ch = CMSG_FIRSTHDR (&msg);
+ ch->cmsg_len = CMSG_LEN (sizeof (struct ucred));
+ ch->cmsg_level = SOL_SOCKET;
+ ch->cmsg_type = SCM_CREDENTIALS;
+
+ cred = (struct ucred *) CMSG_DATA (ch);
+ cred->pid = getpid ();
+ cred->uid = getuid ();
+ cred->gid = getgid ();
+
+ ret = sendmsg (fd, &msg, 0);
+ if (ret == -1)
+ {
+ DEBUG ("sendmsg failed: %s", g_strerror (errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#define BUFSIZE 1024
+
+static GibberFdIOResult
+gibber_unix_transport_read (GibberFdTransport *transport,
+ GIOChannel *channel,
+ GError **error)
+{
+ GibberUnixTransport *self = GIBBER_UNIX_TRANSPORT (transport);
+ GibberUnixTransportPrivate *priv = GIBBER_UNIX_TRANSPORT_GET_PRIVATE (self);
+ int fd;
+ guint8 buffer[BUFSIZE];
+ ssize_t bytes_read;
+ GibberBuffer buf;
+ struct iovec iov;
+ struct msghdr msg;
+ char control[CMSG_SPACE (sizeof (struct ucred))];
+ struct cmsghdr *ch;
+ struct ucred *cred;
+ int opt;
+
+ if (priv->recv_creds_cb == NULL)
+ return gibber_fd_transport_read (transport, channel, error);
+
+ /* We are waiting for credentials */
+ fd = transport->fd;
+
+ /* set SO_PASSCRED flag */
+ opt = 1;
+ setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &opt, sizeof (opt));
+
+ memset (buffer, 0, sizeof (buffer));
+ memset (&iov, 0, sizeof (iov));
+ iov.iov_base = buffer;
+ iov.iov_len = sizeof (buffer);
+
+ memset (&msg, 0, sizeof (msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof (control);
+
+ bytes_read = recvmsg (fd, &msg, 0);
+
+ if (bytes_read == -1)
+ {
+ GError *err = NULL;
+
+ g_set_error_literal (&err, G_IO_CHANNEL_ERROR,
+ g_io_channel_error_from_errno (errno), "recvmsg failed");
+
+ priv->recv_creds_cb (self, NULL, NULL, err, priv->recv_creds_data);
+ g_propagate_error (error, err);
+
+ priv->recv_creds_cb = NULL;
+ priv->recv_creds_data = NULL;
+ return GIBBER_FD_IO_RESULT_ERROR;
+ }
+
+ /* unset SO_PASSCRED flag */
+ opt = 0;
+ setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &opt, sizeof (opt));
+
+ buf.data = buffer;
+ buf.length = bytes_read;
+
+ /* extract the credentials */
+ ch = CMSG_FIRSTHDR (&msg);
+ if (ch == NULL)
+ {
+ GError *err = NULL;
+
+ DEBUG ("Message doesn't contain credentials");
+
+ g_set_error_literal (&err, GIBBER_UNIX_TRANSPORT_ERROR,
+ GIBBER_UNIX_TRANSPORT_ERROR_NO_CREDENTIALS,
+ "no credentials received");
+
+ priv->recv_creds_cb (self, &buf, NULL, err, priv->recv_creds_data);
+ g_error_free (err);
+ }
+ else
+ {
+ GibberCredentials credentials;
+
+ cred = (struct ucred *) CMSG_DATA (ch);
+ credentials.pid = cred->pid;
+ credentials.uid = cred->uid;
+ credentials.gid = cred->gid;
+
+ priv->recv_creds_cb (self, &buf, &credentials, NULL,
+ priv->recv_creds_data);
+ }
+
+ priv->recv_creds_cb = NULL;
+ priv->recv_creds_data = NULL;
+ return GIBBER_FD_IO_RESULT_SUCCESS;
+}
+
+gboolean
+gibber_unix_transport_recv_credentials (GibberUnixTransport *self,
+ GibberUnixTransportRecvCredentialsCb callback,
+ gpointer user_data)
+{
+ GibberUnixTransportPrivate *priv = GIBBER_UNIX_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->recv_creds_cb != NULL)
+ {
+ DEBUG ("already waiting for credentials");
+ return FALSE;
+ }
+
+ priv->recv_creds_cb = callback;
+ priv->recv_creds_data = user_data;
+ return TRUE;
+}
+
+#else /* OSs where we have no implementation */
+
+gboolean
+gibber_unix_transport_supports_credentials (void)
+{
+ return FALSE;
+}
+
+gboolean
+gibber_unix_transport_recv_credentials (GibberUnixTransport *self,
+ GibberUnixTransportRecvCredentialsCb callback,
+ gpointer user_data)
+{
+ DEBUG ("stub implementation, failing");
+ return FALSE;
+}
+
+gboolean
+gibber_unix_transport_send_credentials (GibberUnixTransport *transport,
+ const guint8 *data,
+ gsize size)
+{
+ DEBUG ("stub implementation, failing");
+ return FALSE;
+}
+
+static GibberFdIOResult
+gibber_unix_transport_read (GibberFdTransport *transport,
+ GIOChannel *channel,
+ GError **error)
+{
+ return gibber_fd_transport_read (transport, channel, error);
+}
+
+#endif /* OSs where we have no implementation of credentials */
+
+#endif /* G_OS_UNIX */
diff --git a/salut/lib/gibber/gibber-unix-transport.h b/salut/lib/gibber/gibber-unix-transport.h
new file mode 100644
index 000000000..5f8f553d2
--- /dev/null
+++ b/salut/lib/gibber/gibber-unix-transport.h
@@ -0,0 +1,117 @@
+/*
+ * gibber-unix-transport.h - Header for GibberUnixTransport
+ * Copyright (C) 2006, 2008 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ * @author: Alban Crequy <alban.crequy@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_UNIX_TRANSPORT_H__
+#define __GIBBER_UNIX_TRANSPORT_H__
+
+#include <glib-object.h>
+
+#ifdef G_OS_UNIX
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <sys/un.h>
+
+#include "gibber-fd-transport.h"
+
+G_BEGIN_DECLS
+
+GQuark gibber_unix_transport_error_quark (void);
+#define GIBBER_UNIX_TRANSPORT_ERROR gibber_unix_transport_error_quark()
+
+typedef enum
+{
+ GIBBER_UNIX_TRANSPORT_ERROR_CONNECT_FAILED,
+ GIBBER_UNIX_TRANSPORT_ERROR_FAILED,
+ GIBBER_UNIX_TRANSPORT_ERROR_NO_CREDENTIALS,
+} GibberUnixTransportError;
+
+typedef struct _GibberUnixTransport GibberUnixTransport;
+typedef struct _GibberUnixTransportClass GibberUnixTransportClass;
+
+
+struct _GibberUnixTransportClass {
+ GibberFdTransportClass parent_class;
+};
+
+struct _GibberUnixTransport {
+ GibberFdTransport parent;
+};
+
+GType gibber_unix_transport_get_type (void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_UNIX_TRANSPORT \
+ (gibber_unix_transport_get_type ())
+#define GIBBER_UNIX_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GIBBER_TYPE_UNIX_TRANSPORT, \
+ GibberUnixTransport))
+#define GIBBER_UNIX_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GIBBER_TYPE_UNIX_TRANSPORT, \
+ GibberUnixTransportClass))
+#define GIBBER_IS_UNIX_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIBBER_TYPE_UNIX_TRANSPORT))
+#define GIBBER_IS_UNIX_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GIBBER_TYPE_UNIX_TRANSPORT))
+#define GIBBER_UNIX_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_UNIX_TRANSPORT, \
+ GibberUnixTransportClass))
+
+gboolean gibber_unix_transport_supports_credentials (void);
+
+GibberUnixTransport * gibber_unix_transport_new (void);
+
+GibberUnixTransport * gibber_unix_transport_new_from_fd (int fd);
+
+gboolean gibber_unix_transport_connect (GibberUnixTransport *transport,
+ const gchar *path, GError **error);
+
+gboolean gibber_unix_transport_send_credentials (GibberUnixTransport *transport,
+ const guint8 *data, gsize size);
+
+typedef struct {
+ pid_t pid;
+ uid_t uid;
+ gid_t gid;
+} GibberCredentials;
+
+typedef void (*GibberUnixTransportRecvCredentialsCb) (
+ GibberUnixTransport *transport,
+ GibberBuffer *buffer,
+ GibberCredentials *credentials,
+ GError *error,
+ gpointer user_data);
+
+gboolean gibber_unix_transport_recv_credentials (GibberUnixTransport *transport,
+ GibberUnixTransportRecvCredentialsCb callback,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* G_OS_UNIX */
+
+#endif /* #ifndef __GIBBER_UNIX_TRANSPORT_H__*/
diff --git a/salut/lib/gibber/gibber-util.c b/salut/lib/gibber/gibber-util.c
new file mode 100644
index 000000000..b1cf93aa1
--- /dev/null
+++ b/salut/lib/gibber/gibber-util.c
@@ -0,0 +1,96 @@
+/*
+ * gibber-util.c - Code for Gibber utility functions
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "gibber-util.h"
+
+void
+gibber_normalize_address (struct sockaddr_storage *addr)
+{
+ struct sockaddr_in *s4 = (struct sockaddr_in *) addr;
+ struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) addr;
+
+ if (s6->sin6_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED (&(s6->sin6_addr)))
+ {
+ /* Normalize to ipv4 address */
+ u_int32_t addr_big_endian;
+ u_int16_t port;
+
+ memcpy (&addr_big_endian, s6->sin6_addr.s6_addr + 12, 4);
+ port = s6->sin6_port;
+
+ s4->sin_family = AF_INET;
+ s4->sin_addr.s_addr = addr_big_endian;
+ s4->sin_port = port;
+ }
+}
+
+/* FIXME: this is a hack until we get the normalization of v6-in-v4
+ * addresses in GLib. See bgo#646082 */
+GSocketAddress *
+gibber_normalize_socket_address (GSocketAddress *addr)
+{
+ struct sockaddr_storage ss;
+
+ if (g_socket_address_get_family (addr) != G_SOCKET_FAMILY_IPV6)
+ return addr;
+
+ if (!g_socket_address_to_native (addr, &ss, sizeof (ss), NULL))
+ return addr;
+
+ g_object_unref (addr);
+
+ gibber_normalize_address (&ss);
+
+ return g_socket_address_new_from_native (&ss, sizeof (ss));
+}
+
+/**
+ * gibber_strdiff:
+ * @left: The first string to compare (may be NULL)
+ * @right: The second string to compare (may be NULL)
+ *
+ * Return %TRUE if the given strings are different. Unlike #strcmp this
+ * function will handle null pointers, treating them as distinct from any
+ * string.
+ *
+ * Returns: %FALSE if @left and @right are both %NULL, or if
+ * neither is %NULL and both have the same contents; %TRUE otherwise
+ */
+gboolean
+gibber_strdiff (const gchar *left, const gchar *right)
+{
+ if ((NULL == left) != (NULL == right))
+ return TRUE;
+
+ else if (left == right)
+ return FALSE;
+
+ else
+ return (0 != strcmp (left, right));
+}
diff --git a/salut/lib/gibber/gibber-util.h b/salut/lib/gibber/gibber-util.h
new file mode 100644
index 000000000..bbd9032a7
--- /dev/null
+++ b/salut/lib/gibber/gibber-util.h
@@ -0,0 +1,36 @@
+/*
+ * gibber-util.h - Header for Gibber utility functions
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GIBBER_UTIL_H__
+#define __GIBBER_UTIL_H__
+
+#include "gibber-sockets.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+void gibber_normalize_address (struct sockaddr_storage *addr);
+GSocketAddress * gibber_normalize_socket_address (GSocketAddress *addr);
+gboolean gibber_strdiff (const gchar *left, const gchar *right);
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_UTIL_H__ */
diff --git a/salut/lib/gibber/tests/Makefile.am b/salut/lib/gibber/tests/Makefile.am
new file mode 100644
index 000000000..e60256683
--- /dev/null
+++ b/salut/lib/gibber/tests/Makefile.am
@@ -0,0 +1,80 @@
+CLEANFILES=
+
+include $(top_srcdir)/rules/check.mak
+
+SUPPRESSIONS=valgrind.supp dlopen.supp
+
+AM_CFLAGS = $(ERROR_CFLAGS) @GLIB_CFLAGS@ @LIBXML2_CFLAGS@ @WOCKY_CFLAGS@ \
+ @DBUS_CFLAGS@ \
+ -I $(top_srcdir) -I $(top_builddir) \
+ -I $(top_srcdir)/lib -I $(top_builddir)/lib
+
+AM_LDFLAGS = @GLIB_LIBS@ \
+ $(top_builddir)/lib/gibber/libgibber.la
+
+clean-local:
+ -rm -rf outputs
+
+EXTRA_DIST = \
+ test-transport.h \
+ test-transport.c
+
+$(check_SCRIPTS): always-run
+ chmod +x $(srcdir)/$@
+
+.PHONY: always-run
+
+# ------------------------------------------------------------------------------
+# Test programs
+
+# Teach it how to make libgibber.la
+$(top_builddir)/lib/gibber/libgibber.la:
+ ${MAKE} -C $(top_builddir)/lib/gibber libgibber.la
+
+.PHONY: $(top_builddir)/lib/gibber/libgibber.la
+
+TESTS =
+
+noinst_PROGRAMS = \
+ test-r-multicast-transport-io
+
+check_SCRIPTS =
+
+EXTRA_DIST += \
+ simplemeshtest.py mesh.py $(check_SCRIPTS)
+
+test_r_multicast_transport_io_SOURCES = \
+ test-r-multicast-transport-io.c \
+ test-transport.c \
+ test-transport.h
+
+test_r_multicast_transport_io_LDADD = \
+ $(top_builddir)/lib/gibber/libgibber.la \
+ $(AM_LDFLAGS)
+
+test_r_multicast_transport_io_CFLAGS = \
+ $(AM_CFLAGS)
+
+# ------------------------------------------------------------------------------
+# Checks
+
+check_PROGRAMS = \
+ check-gibber-r-multicast-causal-transport \
+ check-gibber-r-multicast-packet \
+ check-gibber-r-multicast-sender \
+ check-gibber-listener \
+ check-gibber-unix-transport
+
+test: ${TEST_PROGS}
+ gtester -k --verbose $(check_PROGRAMS)
+
+# ------------------------------------------------------------------------------
+# Code Style
+
+# Coding style checks
+check_c_sources = \
+ $(test_r_multicast_transport_io_SOURCES)
+
+include $(top_srcdir)/tools/check-coding-style.mk
+
+check-local: check-coding-style test
diff --git a/salut/lib/gibber/tests/check-gibber-listener.c b/salut/lib/gibber/tests/check-gibber-listener.c
new file mode 100644
index 000000000..3ca014075
--- /dev/null
+++ b/salut/lib/gibber/tests/check-gibber-listener.c
@@ -0,0 +1,214 @@
+/*
+ * check-gibber-listener.c - Test for GibberListener
+ * Copyright (C) 2007, 2008 Collabora Ltd.
+ * @author Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include <gibber/gibber-tcp-transport.h>
+#include <gibber/gibber-unix-transport.h>
+#include <gibber/gibber-listener.h>
+
+gboolean got_connection;
+gboolean signalled;
+
+static void
+new_connection_cb (GibberListener *listener,
+ GibberTransport *connection,
+ struct sockaddr *addr,
+ guint size,
+ GMainLoop *loop)
+{
+ signalled = TRUE;
+ got_connection = TRUE;
+ g_main_loop_quit (loop);
+}
+
+static void
+disconnected_cb (GibberTransport *transport, GMainLoop *loop)
+{
+ signalled = TRUE;
+ got_connection = FALSE;
+ g_main_loop_quit (loop);
+}
+
+static GibberTransport *
+connect_to_port (int port, GMainLoop *loop)
+{
+ GibberTCPTransport *transport;
+ gchar sport[16];
+
+ g_snprintf (sport, 16, "%d", port);
+
+ transport = gibber_tcp_transport_new ();
+ g_signal_connect (transport, "disconnected",
+ G_CALLBACK (disconnected_cb), loop);
+
+ gibber_tcp_transport_connect (transport, "127.0.0.1", sport);
+
+ return GIBBER_TRANSPORT (transport);
+}
+
+static void
+test_unix_listen (void)
+{
+ GibberListener *listener_unix;
+ GibberUnixTransport *unix_transport;
+ int ret;
+ GMainLoop *mainloop;
+ GError *error = NULL;
+ gchar *path = "/tmp/check-gibber-listener-socket";
+
+ ret = unlink (path);
+ g_assert (!(ret == -1 && errno != ENOENT));
+
+ mainloop = g_main_loop_new (NULL, FALSE);
+
+ listener_unix = gibber_listener_new ();
+ g_assert (listener_unix != NULL);
+
+ g_signal_connect (listener_unix, "new-connection",
+ G_CALLBACK (new_connection_cb), mainloop);
+
+ ret = gibber_listener_listen_socket (listener_unix, path, FALSE, &error);
+ g_assert (ret == TRUE);
+
+ unix_transport = gibber_unix_transport_new ();
+ ret = gibber_unix_transport_connect (unix_transport, path, &error);
+ g_assert (ret == TRUE);
+
+ g_main_loop_run (mainloop);
+
+ g_assert (got_connection && "Failed to connect");
+
+ g_object_unref (listener_unix);
+ g_object_unref (unix_transport);
+ g_main_loop_unref (mainloop);
+}
+
+static void
+test_tcp_listen (void)
+{
+ GibberListener *listener;
+ GibberListener *listener_without_port;
+ GibberListener *listener2;
+ int ret, port;
+ GMainLoop *mainloop;
+ GibberTransport *transport;
+ GError *error = NULL;
+
+ mainloop = g_main_loop_new (NULL, FALSE);
+
+ /* tcp socket tests without a specified port */
+ listener_without_port = gibber_listener_new ();
+ g_assert (listener_without_port != NULL);
+
+ g_signal_connect (listener_without_port, "new-connection",
+ G_CALLBACK (new_connection_cb), mainloop);
+
+ ret = gibber_listener_listen_tcp (listener_without_port, 0, &error);
+ g_assert (ret == TRUE);
+ port = gibber_listener_get_port (listener_without_port);
+
+ signalled = FALSE;
+ transport = connect_to_port (port, mainloop);
+ if (!signalled)
+ g_main_loop_run (mainloop);
+
+ g_assert (got_connection);
+
+ g_object_unref (listener_without_port);
+ g_object_unref (transport);
+
+ /* tcp socket tests with a specified port */
+ listener = gibber_listener_new ();
+ g_assert (listener != NULL);
+
+ g_signal_connect (listener, "new-connection", G_CALLBACK (new_connection_cb),
+ mainloop);
+
+ for (port = 5298; port < 5400; port++)
+ {
+ if (gibber_listener_listen_tcp (listener, port, &error))
+ break;
+
+ g_assert_error (error, GIBBER_LISTENER_ERROR,
+ GIBBER_LISTENER_ERROR_ADDRESS_IN_USE);
+ g_error_free (error);
+ error = NULL;
+ }
+ g_assert (port < 5400);
+ g_assert (port == gibber_listener_get_port (listener));
+
+ /* try a second listener on the same port */
+ listener2 = gibber_listener_new ();
+ g_assert (listener2 != NULL);
+ g_assert (!gibber_listener_listen_tcp (listener2, port, &error));
+ g_assert_error (error, GIBBER_LISTENER_ERROR,
+ GIBBER_LISTENER_ERROR_ADDRESS_IN_USE);
+ g_object_unref (listener2);
+ g_error_free (error);
+ error = NULL;
+
+ signalled = FALSE;
+ transport = connect_to_port (port, mainloop);
+ if (!signalled)
+ g_main_loop_run (mainloop);
+
+ g_assert (got_connection);
+
+ g_object_unref (listener);
+ g_object_unref (transport);
+
+ /* listener is destroyed, connection should be refused now */
+ signalled = FALSE;
+ transport = connect_to_port (port, mainloop);
+
+ if (!signalled)
+ g_main_loop_run (mainloop);
+
+ /* Connected while listening should have stopped */
+ g_assert (!got_connection);
+
+ g_object_unref (transport);
+ g_main_loop_unref (mainloop);
+
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_type_init ();
+
+ g_test_add_func ("/gibber/listener/tcp-listen", test_tcp_listen);
+ g_test_add_func ("/gibber/listener/unix-listen", test_unix_listen);
+
+ return g_test_run ();
+}
diff --git a/salut/lib/gibber/tests/check-gibber-r-multicast-causal-transport.c b/salut/lib/gibber/tests/check-gibber-r-multicast-causal-transport.c
new file mode 100644
index 000000000..0d9f7427c
--- /dev/null
+++ b/salut/lib/gibber/tests/check-gibber-r-multicast-causal-transport.c
@@ -0,0 +1,567 @@
+/*
+ * check-gibber-r-multicast-causal-transport.c
+ * - R Multicast CausalTransport test
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <gibber/gibber-r-multicast-causal-transport.h>
+#include <gibber/gibber-r-multicast-packet.h>
+#include "test-transport.h"
+
+/* Numer of polls we expect the id generation to do */
+#define ID_GENERATION_EXPECTED_POLLS 3
+
+/* Assume mtu is 1500, we want at least 3 packets */
+#define TEST_DATA_SIZE 3300
+GMainLoop *loop;
+
+static GibberRMulticastCausalTransport *
+create_rmulticast_transport (TestTransport **testtransport,
+ const gchar *name,
+ test_transport_send_hook test_send_hook,
+ gpointer user_data)
+{
+ TestTransport *t;
+ GibberRMulticastCausalTransport *rmctransport;
+
+ t = test_transport_new (test_send_hook, user_data);
+ g_assert (t != NULL);
+ GIBBER_TRANSPORT (t)->max_packet_size = 150;
+
+ rmctransport = gibber_r_multicast_causal_transport_new
+ (GIBBER_TRANSPORT(t), "test123");
+ g_object_unref (t);
+
+ if (testtransport != NULL)
+ {
+ *testtransport = t;
+ }
+
+ test_transport_set_echoing (t, TRUE);
+
+ return rmctransport;
+}
+
+static void
+rmulticast_connect (GibberRMulticastCausalTransport *transport)
+{
+
+ g_assert (transport != NULL);
+
+ g_assert (gibber_r_multicast_causal_transport_connect (transport,
+ FALSE, NULL));
+}
+
+
+/* test depends test */
+struct {
+ gchar *name;
+ guint32 sender_id;
+ guint32 packet_id;
+ gboolean seen;
+} senders[] = {
+ { "test0", 1, 0xff, FALSE },
+ { "test1", 2, 0xffff, FALSE },
+ { "test2", 3, 0xffffff, FALSE },
+ { "test3", 4, 0xaaaaaa, FALSE },
+ { "test4", 5, 0xabcdab, FALSE },
+ { NULL, 0, 0, FALSE }
+};
+
+static gboolean
+depends_send_hook (GibberTransport *transport,
+ const guint8 *data,
+ gsize length,
+ GError **error,
+ gpointer user_data)
+{
+ GibberRMulticastPacket *packet;
+ guint i, n;
+
+ packet = gibber_r_multicast_packet_parse (data, length, NULL);
+ g_assert (packet != NULL);
+
+ if (packet->type == PACKET_TYPE_WHOIS_REQUEST)
+ {
+ GibberRMulticastPacket *reply;
+ guint8 *pdata;
+ gsize psize;
+
+ for (i = 0; senders[i].name != NULL; i++)
+ {
+ if (senders[i].sender_id == packet->data.whois_request.sender_id)
+ {
+ break;
+ }
+ }
+
+ if (senders[i].name == NULL && packet->sender == 0)
+ {
+ /* unique id polling */
+ goto out;
+ }
+
+ g_assert (senders[i].name != NULL);
+
+ reply = gibber_r_multicast_packet_new (PACKET_TYPE_WHOIS_REPLY,
+ senders[i].sender_id, transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_whois_reply_info (reply,
+ senders[i].name);
+
+ pdata = gibber_r_multicast_packet_get_raw_data (reply, &psize);
+ test_transport_write (TEST_TRANSPORT(transport), pdata, psize);
+ g_object_unref (reply);
+ }
+
+ if (packet->type != PACKET_TYPE_DATA)
+ {
+ goto out;
+ }
+
+ g_assert (packet->depends->len > 0);
+
+ for (n = 0; n < packet->depends->len; n++)
+ {
+ for (i = 0; senders[i].name != NULL ; i++)
+ {
+ GibberRMulticastPacketSenderInfo *sender_info =
+ g_array_index (packet->depends,
+ GibberRMulticastPacketSenderInfo *, n);
+ if (senders[i].sender_id == sender_info->sender_id)
+ {
+ g_assert (senders[i].seen == FALSE);
+ g_assert (senders[i].packet_id + 1 == sender_info->packet_id);
+ senders[i].seen = TRUE;
+ break;
+ }
+ }
+ g_assert (senders[i].name != NULL);
+ }
+
+ for (i = 0; senders[i].name != NULL ; i++)
+ {
+ g_assert (senders[i].seen && "Not all senders in depends");
+ }
+
+ g_main_loop_quit (loop);
+out:
+ g_object_unref (packet);
+ return TRUE;
+}
+
+static gboolean
+depends_send_test_data (gpointer data)
+{
+ GibberRMulticastCausalTransport *t =
+ GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (data);
+ guint8 testdata[] = { 1, 2, 3 };
+
+ g_assert (gibber_transport_send (GIBBER_TRANSPORT (t), testdata,
+ 3, NULL));
+
+ return FALSE;
+}
+
+static void
+depends_connected (GibberTransport *transport,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *rmctransport
+ = GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (transport);
+ TestTransport *testtransport = TEST_TRANSPORT (user_data);
+ int i;
+
+ /* First input some data packets, so the transport is forced to generate
+ * dependency info */
+ for (i = 0 ; senders[i].name != NULL; i++)
+ {
+ GibberRMulticastPacket *packet;
+ guint8 *data;
+ gsize size;
+
+ packet = gibber_r_multicast_packet_new (PACKET_TYPE_DATA,
+ senders[i].sender_id,
+ GIBBER_TRANSPORT (testtransport)->max_packet_size);
+
+ gibber_r_multicast_causal_transport_add_sender (rmctransport,
+ senders[i].sender_id);
+ gibber_r_multicast_causal_transport_update_sender_start (rmctransport,
+ senders[i].sender_id, senders[i].packet_id);
+
+ gibber_r_multicast_packet_set_packet_id (packet, senders[i].packet_id);
+ gibber_r_multicast_packet_set_data_info (packet, 0, 0, 1);
+
+ data = gibber_r_multicast_packet_get_raw_data (packet, &size);
+ test_transport_write (testtransport, data, size);
+ g_object_unref (packet);
+ }
+
+ /* Wait more then 200 ms, so all senders can get go to running */
+ g_timeout_add (300, depends_send_test_data, rmctransport);
+}
+
+static void
+test_depends (void)
+{
+ GibberRMulticastCausalTransport *rmctransport;
+ TestTransport *testtransport;
+ int i;
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ rmctransport = create_rmulticast_transport (&testtransport, "test123",
+ depends_send_hook, NULL);
+
+ g_signal_connect (rmctransport, "connected",
+ G_CALLBACK (depends_connected), testtransport);
+
+ rmulticast_connect (rmctransport);
+
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+
+ for (i = 0 ; senders[i].name != NULL; i++)
+ {
+ g_assert (senders[i].seen);
+ }
+
+ g_object_unref (rmctransport);
+}
+
+
+/* test fragmentation testing */
+static gboolean
+fragmentation_send_hook (GibberTransport *transport,
+ const guint8 *data,
+ gsize length,
+ GError **error,
+ gpointer user_data)
+{
+ GibberRMulticastPacket *packet;
+ static gsize bytes = 0;
+ static guint8 next_byte = 0;
+ gsize i;
+ gsize size;
+ guint8 *payload;
+
+ packet = gibber_r_multicast_packet_parse (data, length, NULL);
+ g_assert (packet != NULL);
+
+ if (packet->type != PACKET_TYPE_DATA)
+ {
+ goto out;
+ }
+
+ payload = gibber_r_multicast_packet_get_payload (packet, &size);
+
+ if (bytes == 0)
+ g_assert
+ (packet->data.data.flags == GIBBER_R_MULTICAST_DATA_PACKET_START);
+ else if (bytes + size < TEST_DATA_SIZE)
+ g_assert (packet->data.data.flags == 0);
+
+ bytes += size;
+ g_assert (bytes <= TEST_DATA_SIZE);
+
+ /* check our bytes */
+ for (i = 0; i < size; i++)
+ {
+ g_assert (payload[i] == next_byte);
+ next_byte++;
+ }
+
+ if (bytes == TEST_DATA_SIZE)
+ {
+ g_assert
+ (packet->data.data.flags == GIBBER_R_MULTICAST_DATA_PACKET_END);
+ g_object_unref (packet);
+ g_main_loop_quit (loop);
+ return FALSE;
+ }
+
+out:
+ g_object_unref (packet);
+ return TRUE;
+}
+
+static void
+fragmentation_connected (GibberTransport *transport,
+ gpointer user_data)
+{
+ GibberRMulticastCausalTransport *rmctransport
+ = GIBBER_R_MULTICAST_CAUSAL_TRANSPORT (transport);
+ guint8 testdata[TEST_DATA_SIZE];
+ int i;
+
+ for (i = 0; i < TEST_DATA_SIZE; i++)
+ {
+ testdata[i] = (guint8) (i & 0xff);
+ }
+
+ g_assert (gibber_transport_send (GIBBER_TRANSPORT (rmctransport),
+ (guint8 *) testdata, TEST_DATA_SIZE, NULL));
+}
+
+static void
+test_fragmentation (void)
+{
+ GibberRMulticastCausalTransport *rmctransport;
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ rmctransport = create_rmulticast_transport (NULL, "test123",
+ fragmentation_send_hook, NULL);
+
+ g_signal_connect (rmctransport, "connected",
+ G_CALLBACK (fragmentation_connected), NULL);
+
+ rmulticast_connect (rmctransport);
+
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+
+ g_object_unref (rmctransport);
+}
+
+
+/* test unique id */
+static gboolean
+unique_id_send_hook (GibberTransport *transport,
+ const guint8 *data,
+ gsize length,
+ GError **error,
+ gpointer user_data)
+{
+ GibberRMulticastPacket *packet;
+ guint32 *test_id = (guint32 *) user_data;
+
+
+ packet = gibber_r_multicast_packet_parse (data, length, NULL);
+ g_assert (packet != NULL);
+
+ if (*test_id == 0)
+ {
+ /* force collision */
+ GibberRMulticastPacket *reply;
+ guint8 *pdata;
+ gsize psize;
+
+ /* First packet must be a whois request to see if the id is taken */
+ g_assert (packet->type == PACKET_TYPE_WHOIS_REQUEST);
+ /* Sender must be 0 as it couldn't choose a id just yet */
+ g_assert (packet->sender == 0);
+
+ *test_id = packet->data.whois_request.sender_id;
+
+ reply = gibber_r_multicast_packet_new (PACKET_TYPE_WHOIS_REPLY,
+ *test_id, transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_whois_reply_info (reply, "romeo");
+
+ pdata = gibber_r_multicast_packet_get_raw_data (reply, &psize);
+ test_transport_write (TEST_TRANSPORT(transport), pdata, psize);
+ g_object_unref (reply);
+ }
+ else
+ {
+ g_assert (*test_id != packet->sender);
+ switch (packet->type)
+ {
+ case PACKET_TYPE_WHOIS_REQUEST:
+ g_assert (*test_id != packet->data.whois_request.sender_id);
+ break;
+ case PACKET_TYPE_WHOIS_REPLY:
+ /* transport sends a unsolicited whois reply after choosing a
+ * identifier */
+ g_main_loop_quit (loop);
+ break;
+ default:
+ g_warning ("Unexpected packet type: %x", packet->type);
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ g_object_unref (packet);
+ return TRUE;
+}
+
+static void
+test_unique_id (void)
+{
+ /* Test if the multicast transport correctly handles the case that it gets a
+ * WHOIS_REPLY on one of it's WHOIS_REQUESTS when it's determining a unique
+ * id for itself */
+ GibberRMulticastCausalTransport *rmctransport;
+ guint32 test_id;
+
+ test_id = 0;
+ loop = g_main_loop_new (NULL, FALSE);
+
+ rmctransport = create_rmulticast_transport (NULL, "test123",
+ unique_id_send_hook, &test_id);
+
+ rmulticast_connect (rmctransport);
+
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+
+ g_object_unref (rmctransport);
+}
+
+/* test id generation conflict */
+typedef struct {
+ guint32 id;
+ gint count;
+ gint wait;
+} unique_id_conflict_test_t;
+
+static gboolean
+id_generation_conflict_send_hook (GibberTransport *transport,
+ const guint8 *data,
+ gsize length,
+ GError **error,
+ gpointer user_data)
+{
+ GibberRMulticastPacket *packet;
+ unique_id_conflict_test_t *test = (unique_id_conflict_test_t *) user_data;
+
+ packet = gibber_r_multicast_packet_parse (data, length, NULL);
+ g_assert (packet != NULL);
+
+ if (test->id == 0)
+ {
+ /* First packet must be a whois request to see if the id is taken */
+ g_assert (packet->type == PACKET_TYPE_WHOIS_REQUEST);
+ /* Sender must be 0 as it couldn't choose a id just yet */
+ g_assert (packet->sender == 0);
+
+ test->id = packet->data.whois_request.sender_id;
+ }
+
+ switch (packet->type)
+ {
+ case PACKET_TYPE_WHOIS_REQUEST:
+ test->count++;
+
+ if (test->count < test->wait)
+ {
+ g_assert (test->id == packet->data.whois_request.sender_id);
+ }
+ else if (test->count == test->wait)
+ {
+ /* force collision */
+ GibberRMulticastPacket *reply;
+ guint8 *pdata;
+ gsize psize;
+
+ g_assert (test->id == packet->data.whois_request.sender_id);
+
+ reply = gibber_r_multicast_packet_new (PACKET_TYPE_WHOIS_REQUEST,
+ 0, transport->max_packet_size);
+
+ gibber_r_multicast_packet_set_whois_request_info (reply, test->id);
+
+ pdata = gibber_r_multicast_packet_get_raw_data (reply, &psize);
+ test_transport_write (TEST_TRANSPORT(transport), pdata, psize);
+ g_object_unref (reply);
+ }
+ else if (test->count > test->wait)
+ {
+ g_assert (test->id != packet->data.whois_request.sender_id);
+ }
+
+ break;
+
+ case PACKET_TYPE_WHOIS_REPLY:
+ /* transport sends a unsolicited whois reply after choosing a
+ * identifier */
+ g_assert (packet->sender != test->id);
+ g_assert_cmpuint (test->count, ==,
+ ID_GENERATION_EXPECTED_POLLS + test->wait);
+ g_main_loop_quit (loop);
+ break;
+
+ default:
+ g_warning ("Unexpected packet type: %x", packet->type);
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_object_unref (packet);
+ return TRUE;
+}
+
+static void
+test_id_generation_conflict (gint _i)
+{
+ /* Test if the multicast transport correctly handles the case that it sees
+ * another WHOIS_REQUEST on one of its WHOIS_REQUESTS when it's determining
+ * a unique id for itself */
+ GibberRMulticastCausalTransport *rmtransport;
+ unique_id_conflict_test_t test;
+
+ test.id = 0;
+ test.count = 0;
+ test.wait = _i + 1;
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ rmtransport = create_rmulticast_transport (NULL, "test123",
+ id_generation_conflict_send_hook, &test);
+
+ rmulticast_connect (rmtransport);
+
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+
+ g_object_unref (rmtransport);
+}
+
+static void
+test_id_generation_conflict_loop (void)
+{
+ gint i;
+ for (i = 0; i < ID_GENERATION_EXPECTED_POLLS; ++i)
+ test_id_generation_conflict (i);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_type_init ();
+
+ g_test_add_func ("/gibber/r-multicast-casual-transport/unique-id",
+ test_unique_id);
+ g_test_add_func (
+ "/gibber/r-multicast-casual-transport/id-generation-conflict",
+ test_id_generation_conflict_loop);
+ g_test_add_func ("/gibber/r-multicast-casual-transport/fragmentation",
+ test_fragmentation);
+ g_test_add_func ("/gibber/r-multicast-casual-transport/depends",
+ test_depends);
+
+ return g_test_run ();
+}
+
+#include "test-transport.c"
diff --git a/salut/lib/gibber/tests/check-gibber-r-multicast-packet.c b/salut/lib/gibber/tests/check-gibber-r-multicast-packet.c
new file mode 100644
index 000000000..5d5fcfd8d
--- /dev/null
+++ b/salut/lib/gibber/tests/check-gibber-r-multicast-packet.c
@@ -0,0 +1,239 @@
+#include <stdio.h>
+#include <string.h>
+
+#include <gibber/gibber-r-multicast-packet.h>
+
+#define COMPARE(x) G_STMT_START { \
+ g_assert (a->x == b->x); \
+} G_STMT_END
+
+typedef struct {
+ guint32 sender_id;
+ guint32 packet_id;
+ gboolean seen;
+} sender_t;
+
+typedef struct {
+ guint32 a;
+ guint32 b;
+ gint32 result;
+} diff_testcase;
+
+#define NUMBER_OF_DIFF_TESTS 15
+
+static void
+test_r_multicast_packet_diff (gint _i)
+{
+ diff_testcase cases[NUMBER_OF_DIFF_TESTS] =
+ { { 0, 0, 0 },
+ { 10, 10, 0 },
+ { 5, 10, 5 },
+ { 10, 5, -5 },
+ { G_MAXUINT32 - 10, 10, 21 },
+ { G_MAXUINT32, 0, 1 },
+ { 0 , G_MAXUINT32, -1 },
+ { G_MAXUINT32, 10, 11 },
+ { 10, G_MAXUINT32, -11 },
+ { G_MAXUINT32/2, G_MAXUINT32, G_MAXINT32 },
+ { G_MAXUINT32, G_MAXUINT32/2, -G_MAXINT32 },
+ { G_MAXUINT32/2 - 1, G_MAXUINT32, G_MAXINT32 },
+ { G_MAXUINT32, G_MAXUINT32/2 - 1, -G_MAXINT32 },
+ { G_MAXUINT32 - 5, 5, 11 },
+ { 5, G_MAXUINT32 - 5, -11 },
+
+ };
+
+ diff_testcase *c = cases + _i;
+ gint32 result = gibber_r_multicast_packet_diff (c->a, c->b);
+ g_assert (c->result == result);
+}
+
+static void
+test_r_multicast_packet_diff_loop (void)
+{
+ gint i;
+ for (i = 0; i < NUMBER_OF_DIFF_TESTS; ++i)
+ test_r_multicast_packet_diff (i);
+}
+
+static void
+test_data_packet (void)
+{
+ GibberRMulticastPacket *a;
+ GibberRMulticastPacket *b;
+ guint32 sender_id = 1234;
+ guint32 packet_id = 1200;
+ guint8 flags = GIBBER_R_MULTICAST_DATA_PACKET_START;
+ guint32 total_size = 800;
+ guint16 stream_id = 56;
+ guint8 *data;
+ gsize len;
+ guint8 *pdata;
+ gsize plen;
+ guint i,n;
+ sender_t senders[] =
+ { { 0x300, 500, FALSE }, { 0x400, 600, FALSE }, { 0, 0, FALSE } };
+ gchar *payload = "1234567890";
+
+ g_type_init ();
+
+ a = gibber_r_multicast_packet_new (PACKET_TYPE_DATA, sender_id, 1500);
+ gibber_r_multicast_packet_set_packet_id (a, packet_id);
+ gibber_r_multicast_packet_set_data_info (a, stream_id, flags, total_size);
+
+ for (i = 0 ; senders[i].sender_id != 0; i++)
+ {
+ gibber_r_multicast_packet_add_sender_info (a,
+ senders[i].sender_id, senders[i].packet_id, NULL);
+ }
+
+ gibber_r_multicast_packet_add_payload (a, (guint8 *) payload,
+ strlen (payload));
+
+ data = gibber_r_multicast_packet_get_raw_data (a, &len);
+
+ b = gibber_r_multicast_packet_parse (data, len, NULL);
+ g_assert (b != NULL);
+
+ COMPARE (type);
+ COMPARE (version);
+ COMPARE (data.data.flags);
+ COMPARE (data.data.total_size);
+ COMPARE (packet_id);
+ COMPARE (data.data.stream_id);
+
+ g_assert (a->sender == b->sender);
+
+ for (n = 0 ; n < b->depends->len; n++)
+ {
+ for (i = 0; senders[i].sender_id != 0 ; i++)
+ {
+ GibberRMulticastPacketSenderInfo *s = g_array_index (b->depends,
+ GibberRMulticastPacketSenderInfo *, n);
+ if (senders[i].sender_id == s->sender_id)
+ {
+ g_assert (senders[i].packet_id == s->packet_id);
+ g_assert (senders[i].seen == FALSE);
+ senders[i].seen = TRUE;
+ break;
+ }
+ }
+
+ g_assert (senders[i].sender_id != 0);
+ }
+
+ for (i = 0; senders[i].sender_id != 0 ; i++)
+ {
+ g_assert (senders[i].seen == TRUE);
+ }
+
+
+ pdata = gibber_r_multicast_packet_get_payload (b, &plen);
+ g_assert (plen == strlen (payload));
+
+ g_assert (memcmp (payload, pdata, plen) == 0);
+
+ g_object_unref (a);
+ g_object_unref (b);
+}
+
+static void
+test_attempt_join_packet (void)
+{
+ GibberRMulticastPacket *a;
+ GibberRMulticastPacket *b;
+ guint32 sender_id = 1234;
+ guint32 packet_id = 1200;
+ guint8 *data;
+ gsize len;
+ guint i, n;
+ sender_t senders[] =
+ { { 0x300, 500, FALSE }, { 0x400, 600, FALSE }, { 0, 0, FALSE } };
+ sender_t new_senders[] =
+ { { 0x500, 0, FALSE }, { 0x600, 0, FALSE }, { 0, 0, FALSE } };
+
+ g_type_init ();
+
+ a = gibber_r_multicast_packet_new (PACKET_TYPE_ATTEMPT_JOIN,
+ sender_id, 1500);
+ gibber_r_multicast_packet_set_packet_id (a, packet_id);
+
+ for (i = 0; senders[i].sender_id != 0; i++)
+ {
+ gibber_r_multicast_packet_add_sender_info (a,
+ senders[i].sender_id, senders[i].packet_id, NULL);
+ }
+ for (i = 0; new_senders[i].sender_id != 0; i++)
+ {
+ gibber_r_multicast_packet_attempt_join_add_sender (a,
+ new_senders[i].sender_id, NULL);
+ }
+
+ data = gibber_r_multicast_packet_get_raw_data (a, &len);
+
+ b = gibber_r_multicast_packet_parse (data, len, NULL);
+
+ g_assert (b != NULL);
+
+ COMPARE (type);
+ COMPARE (version);
+ COMPARE (packet_id);
+ COMPARE (data.attempt_join.senders->len);
+
+ g_assert (a->sender == b->sender);
+
+ for (n = 0; n < b->depends->len; n++)
+ {
+ for (i = 0; senders[i].sender_id != 0 ; i++)
+ {
+ GibberRMulticastPacketSenderInfo *s = g_array_index (b->depends,
+ GibberRMulticastPacketSenderInfo *, n);
+ if (senders[i].sender_id == s->sender_id)
+ {
+ g_assert (senders[i].packet_id == s->packet_id);
+ g_assert (senders[i].seen == FALSE);
+ senders[i].seen = TRUE;
+ break;
+ }
+ }
+
+ g_assert (senders[i].sender_id != 0);
+ }
+
+ for (i = 0; senders[i].sender_id != 0; i++)
+ {
+ g_assert (senders[i].seen == TRUE);
+ }
+
+ for (i = 0; new_senders[i].sender_id != 0; i++)
+ {
+ g_assert (new_senders[i].sender_id ==
+ g_array_index (b->data.attempt_join.senders, guint32, i));
+ new_senders[i].seen = TRUE;
+ break;
+ }
+
+ for (i = 0; new_senders[i].sender_id != 0; i++)
+ {
+ g_assert (senders[i].seen == TRUE);
+ }
+
+ g_object_unref (a);
+ g_object_unref (b);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_type_init ();
+
+ g_test_add_func ("/gibber/r-multicast-packet/data-packet", test_data_packet);
+ g_test_add_func ("/gibber/r-multicast-packet/attempt-join-packet",
+ test_attempt_join_packet);
+ g_test_add_func ("/gibber/r-multicast-packet/diff",
+ test_r_multicast_packet_diff_loop);
+
+ return g_test_run ();
+}
diff --git a/salut/lib/gibber/tests/check-gibber-r-multicast-sender.c b/salut/lib/gibber/tests/check-gibber-r-multicast-sender.c
new file mode 100644
index 000000000..7eb419436
--- /dev/null
+++ b/salut/lib/gibber/tests/check-gibber-r-multicast-sender.c
@@ -0,0 +1,693 @@
+/*
+ * check-gibber-r-multicast-sender.c
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <gibber/gibber-r-multicast-sender.h>
+
+#define SENDER 4321
+#define SENDER_NAME "testsender"
+
+#define REPAIR_PACKET ((guint32)15)
+
+#define EXTRA_SEEN ((guint32)11)
+#define NR_PACKETS ((guint32)40)
+
+guint32 serial_offset;
+guint32 expected;
+
+typedef struct {
+ guint32 serial_offset;
+ gboolean test_seen;
+} test_t;
+
+typedef struct {
+ guint32 receiver_id;
+ const gchar *name;
+ guint32 packet_id;
+} recv_t;
+
+GMainLoop *loop;
+recv_t receivers[] = {
+ { 0x500, "sender1", 500 },
+ { 0x600, "sender2", 600 },
+ { 0, NULL, 0 }
+};
+
+static GibberRMulticastPacket *
+generate_packet (guint32 serial)
+{
+ GibberRMulticastPacket *p;
+ guint8 flags = 0;
+ gint total = 1;
+ gchar *payload;
+ guint8 stream_id = 0;
+ int i;
+
+ switch (serial % 3)
+ {
+ case 0:
+ flags = GIBBER_R_MULTICAST_DATA_PACKET_START
+ | GIBBER_R_MULTICAST_DATA_PACKET_END;
+ stream_id = serial % G_MAXUINT8;
+ break;
+ case 1:
+ flags = GIBBER_R_MULTICAST_DATA_PACKET_START;
+ stream_id = serial % G_MAXUINT8;
+ total = 2;
+ break;
+ case 2:
+ flags = GIBBER_R_MULTICAST_DATA_PACKET_END;
+ stream_id = (serial - 1) % G_MAXUINT8;
+ total = 2;
+ break;
+ }
+
+ p = gibber_r_multicast_packet_new (PACKET_TYPE_DATA, SENDER, 1500);
+
+ gibber_r_multicast_packet_set_packet_id (p, serial);
+ gibber_r_multicast_packet_set_data_info (p, stream_id, flags, total * 11);
+
+ for (i = 0 ; receivers[i].receiver_id != 0; i++)
+ {
+ gibber_r_multicast_packet_add_sender_info (p,
+ receivers[i].receiver_id, receivers[i].packet_id, NULL);
+ }
+
+ payload = g_strdup_printf ("%010d\n", serial);
+ gibber_r_multicast_packet_add_payload (p, (guint8 *) payload,
+ strlen (payload));
+
+ g_free (payload);
+ return p;
+}
+
+static void
+data_received_cb (GibberRMulticastSender *sender, guint8 stream_id,
+ guint8 *data, gsize size, gpointer user_data)
+{
+ gchar *str;
+ gchar **lines;
+ int i;
+
+ str = g_strndup ((const gchar *)data, size);
+
+ lines = g_strsplit (str, "\n", 0);
+ for (i = 0 ; lines[i] != NULL && *lines[i] != '\0'; i++) {
+ guint32 v = atoi (lines[i]);
+
+ g_assert (v == expected);
+ g_assert ((v % G_MAXUINT8) - i == stream_id);
+ expected++;
+ }
+ /* serial % 3 is send out in a single packet the other two together.
+ * So expected can't be % 3 == 2 here */
+ g_assert_cmpuint (expected % 3, !=, 2);
+
+ if (expected == serial_offset + NR_PACKETS
+ || expected == serial_offset + NR_PACKETS + EXTRA_SEEN) {
+ g_main_loop_quit ((GMainLoop *) user_data);
+ }
+
+ g_strfreev (lines);
+ g_free (str);
+
+}
+
+static void
+repair_request_cb (GibberRMulticastSender *sender, guint id, gpointer data)
+{
+ GibberRMulticastPacket *p;
+
+ g_assert (gibber_r_multicast_packet_diff (serial_offset, id) >= 0
+ || gibber_r_multicast_packet_diff (id,
+ serial_offset + NR_PACKETS + EXTRA_SEEN) < 0);
+
+ p = generate_packet (id);
+ gibber_r_multicast_sender_push (sender, p);
+ g_object_unref (p);
+}
+
+static void
+repair_message_cb (GibberRMulticastSender *sender,
+ GibberRMulticastPacket *packet, gpointer user_data)
+{
+
+ g_assert (packet->type == PACKET_TYPE_DATA);
+ g_assert (packet->packet_id == REPAIR_PACKET + serial_offset);
+
+ g_main_loop_quit ((GMainLoop *) user_data);
+}
+
+static gboolean
+add_packet (gpointer data)
+{
+ static guint32 i = 0;
+ GibberRMulticastSender *sender = GIBBER_R_MULTICAST_SENDER (data);
+ GibberRMulticastPacket *p;
+
+ if (i == NR_PACKETS)
+ {
+ i = 0;
+ return FALSE;
+ }
+
+ if (i % 5 != 3)
+ {
+ p = generate_packet (i + serial_offset);
+ gibber_r_multicast_sender_push (sender, p);
+ g_object_unref (p);
+ }
+
+ i++;
+ return TRUE;
+}
+
+
+#define NUMBER_OF_TESTS 3
+
+static void
+test_sender (gint _i)
+{
+ GibberRMulticastSender *s;
+ GibberRMulticastSenderGroup *group;
+ test_t tests[NUMBER_OF_TESTS] = {
+ { (guint32)(~0 - NR_PACKETS/2), TRUE },
+ { 0xff, TRUE },
+ { 0xff, FALSE },
+ };
+ int i;
+
+ g_type_init ();
+ group = gibber_r_multicast_sender_group_new ();
+ loop = g_main_loop_new (NULL, FALSE);
+
+ serial_offset = tests[_i].serial_offset;
+ expected = serial_offset;
+
+ for (i = 0 ; receivers[i].receiver_id != 0; i++)
+ {
+ s = gibber_r_multicast_sender_new (receivers[i].receiver_id,
+ receivers[i].name, group);
+ gibber_r_multicast_sender_update_start (s, receivers[i].packet_id);
+ gibber_r_multicast_sender_seen (s, receivers[i].packet_id + 1);
+ gibber_r_multicast_sender_group_add (group, s);
+ }
+
+ s = gibber_r_multicast_sender_new (SENDER, SENDER_NAME, group);
+ g_signal_connect (s, "received-data", G_CALLBACK(data_received_cb), loop);
+ g_signal_connect (s, "repair-request", G_CALLBACK(repair_request_cb), loop);
+
+ gibber_r_multicast_sender_update_start (s, serial_offset);
+ gibber_r_multicast_sender_set_data_start (s, serial_offset);
+
+ if (tests[_i].test_seen)
+ {
+ gibber_r_multicast_sender_seen (s, serial_offset);
+ }
+ else
+ {
+ gibber_r_multicast_sender_repair_request (s, serial_offset);
+ }
+
+ g_timeout_add (10, add_packet, s);
+
+ g_main_loop_run (loop);
+
+ /* tell the sender we've seen some extra pakcets */
+ gibber_r_multicast_sender_seen (s, serial_offset + NR_PACKETS + EXTRA_SEEN);
+ g_main_loop_run (loop);
+
+ /* Ask for a repair */
+ g_signal_connect (s, "repair-message", G_CALLBACK (repair_message_cb), loop);
+
+ gibber_r_multicast_sender_repair_request (s, serial_offset + REPAIR_PACKET);
+
+ g_main_loop_run (loop);
+
+ gibber_r_multicast_sender_group_free (group);
+}
+
+static void
+test_sender_loop (void)
+{
+ gint i;
+ for (i = 0; i < NUMBER_OF_TESTS; ++i)
+ test_sender (i);
+}
+
+/* Holding test */
+guint32 idle_timer = 0;
+
+typedef struct {
+ gchar *name;
+ guint32 packet_id;
+ GibberRMulticastPacketType packet_type;
+ gchar *data;
+ gchar *depend_node;
+ guint32 depend_packet_id;
+ guint16 data_stream_id;
+ guint8 flags;
+ guint32 total_size;
+} h_setup_t;
+
+typedef enum {
+ EXPECT = 0,
+ START_DATA,
+ FAIL,
+ HOLD,
+ UNHOLD,
+ UNHOLD_IMMEDIATE,
+ DONE
+} h_expect_type_t;
+
+typedef struct {
+ h_expect_type_t type;
+ gchar *expected_node;
+ GibberRMulticastPacketType packet_type;
+ guint32 hold_id;
+ guint32 data_stream_id;
+} h_expect_t;
+
+typedef struct {
+ int test_step;
+ GibberRMulticastSenderGroup *group;
+ h_expect_t *expectation;
+} h_data_t;
+
+typedef struct {
+ h_setup_t *setup;
+ h_expect_t *expectation;
+} h_test_t;
+
+static void h_next_test_step (h_data_t *d);
+
+static gboolean
+h_find_sender (gpointer key, gpointer value, gpointer user_data)
+{
+ GibberRMulticastSender *s = GIBBER_R_MULTICAST_SENDER (value);
+
+ return strcmp (s->name, (gchar *) user_data) == 0;
+}
+
+static gboolean
+h_idle_next_step (gpointer user_data)
+{
+ h_data_t *d = (h_data_t *) user_data;
+ h_expect_t *e = &(d->expectation[d->test_step]);
+ GibberRMulticastSender *s;
+
+ idle_timer = 0;
+
+ switch (e->type) {
+ case UNHOLD_IMMEDIATE:
+ case START_DATA:
+ case FAIL:
+ case EXPECT:
+ g_assert_not_reached ();
+ break;
+ case HOLD:
+ s = g_hash_table_find (d->group->senders,
+ h_find_sender, e->expected_node);
+ g_assert (s != NULL);
+ d->test_step++;
+ gibber_r_multicast_sender_hold_data (s, e->hold_id);
+ h_next_test_step (d);
+ break;
+ case UNHOLD:
+ s = g_hash_table_find (d->group->senders,
+ h_find_sender, e->expected_node);
+ g_assert (s != NULL);
+ d->test_step++;
+ gibber_r_multicast_sender_release_data (s);
+ h_next_test_step (d);
+ break;
+ case DONE:
+ /* And there was great rejoice */
+ g_main_loop_quit (loop);
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+h_next_test_step (h_data_t *d)
+{
+ GibberRMulticastSender *s;
+ h_expect_t *e = &(d->expectation[d->test_step]);
+
+ switch (d->expectation[d->test_step].type) {
+ case EXPECT:
+ break;
+ case UNHOLD_IMMEDIATE:
+ s = g_hash_table_find (d->group->senders,
+ h_find_sender, e->expected_node);
+ g_assert (s != NULL);
+ d->test_step++;
+ gibber_r_multicast_sender_release_data (s);
+ h_next_test_step (d);
+ break;
+ case START_DATA:
+ s = g_hash_table_find (d->group->senders,
+ h_find_sender, e->expected_node);
+ g_assert (s != NULL);
+ d->test_step++;
+ gibber_r_multicast_sender_set_data_start (s, e->hold_id);
+ h_next_test_step (d);
+ break;
+ case FAIL:
+ s = g_hash_table_find (d->group->senders,
+ h_find_sender, e->expected_node);
+ g_assert (s != NULL);
+ d->test_step++;
+ gibber_r_multicast_sender_set_failed (s);
+ h_next_test_step (d);
+ break;
+ case HOLD:
+ case UNHOLD:
+ case DONE:
+ if (idle_timer == 0)
+ {
+ idle_timer = g_idle_add (h_idle_next_step, d);
+ }
+ }
+}
+
+static void
+h_received_data_cb (GibberRMulticastSender *sender, guint16 stream_id,
+ guint8 *data, gsize size, gpointer user_data)
+{
+ h_data_t *d = (h_data_t *) user_data;
+
+ g_assert (d->expectation[d->test_step].type == EXPECT);
+ g_assert (d->expectation[d->test_step].packet_type == PACKET_TYPE_DATA);
+ g_assert_cmpstr (d->expectation[d->test_step].expected_node, ==,
+ sender->name);
+ g_assert (d->expectation[d->test_step].data_stream_id == stream_id);
+
+ d->test_step++;
+ h_next_test_step (d);
+}
+
+static void
+h_received_control_packet_cb (GibberRMulticastSender *sender,
+ GibberRMulticastPacket *packet, gpointer user_data)
+{
+ h_data_t *d = (h_data_t *) user_data;
+
+ g_assert (d->expectation[d->test_step].type == EXPECT);
+ g_assert (d->expectation[d->test_step].packet_type == packet->type);
+ g_assert_cmpstr (d->expectation[d->test_step].expected_node, ==,
+ sender->name);
+
+ d->test_step++;
+ h_next_test_step (d);
+}
+
+h_setup_t h_setup0[] = {
+ { "node0", 0x1, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 3, 3 },
+ { "node1", 0x1, PACKET_TYPE_DATA, "001", "node0", 0x2, 0, 3, 3 },
+ { "node0", 0x2, PACKET_TYPE_DATA, "002", "node1", 0x2, 0, 3, 3 },
+ { "node1", 0x2, PACKET_TYPE_DATA, "002", "node0", 0x3, 0, 3, 3 },
+ { "node0", 0x3, PACKET_TYPE_ATTEMPT_JOIN, NULL, "node1", 0x3 },
+ { "node1", 0x3, PACKET_TYPE_ATTEMPT_JOIN, NULL, "node0", 0x4 },
+ { "node0", 0x4, PACKET_TYPE_DATA, "003", "node1", 0x4, 0, 3, 3 },
+ { "node1", 0x4, PACKET_TYPE_DATA, "003", "node0", 0x5, 0, 3, 3 },
+ { "node0", 0x5, PACKET_TYPE_JOIN, NULL, "node1", 0x5 },
+ { "node1", 0x5, PACKET_TYPE_JOIN, NULL, "node0", 0x6 },
+ { NULL },
+ };
+
+h_expect_t h_expectation0[] = {
+ { EXPECT, "node0", PACKET_TYPE_ATTEMPT_JOIN },
+ { EXPECT, "node1", PACKET_TYPE_ATTEMPT_JOIN },
+ { EXPECT, "node0", PACKET_TYPE_JOIN },
+ { EXPECT, "node1", PACKET_TYPE_JOIN },
+ /* Set the data start of node1 to 0x1, which means all the data should still
+ * be popped off */
+ { START_DATA, "node1", PACKET_TYPE_INVALID, 0x1 },
+ { START_DATA, "node0", PACKET_TYPE_INVALID, 0x1 },
+ /* only unhold node1, nothing should happen as they depend on those of
+ * node0 */
+ { HOLD, "node1", PACKET_TYPE_INVALID, 0x3 },
+ /* unhold node0 too, packets should start flowing */
+ { HOLD, "node0", PACKET_TYPE_INVALID, 0x3 },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 0 },
+ { EXPECT, "node1", PACKET_TYPE_DATA, 0, 0 },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 0 },
+ { EXPECT, "node1", PACKET_TYPE_DATA, 0, 0 },
+ { UNHOLD, "node1" },
+ { UNHOLD, "node0" },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 0 },
+ { EXPECT, "node1", PACKET_TYPE_DATA, 0, 0 },
+ { DONE },
+};
+
+h_setup_t h_setup1[] = {
+ { "node0", 0x1, PACKET_TYPE_ATTEMPT_JOIN, "001", "node1", 0x2 },
+ { "node1", 0x1, PACKET_TYPE_ATTEMPT_JOIN, "001", NULL },
+ { NULL },
+};
+
+h_expect_t h_expectation1[] = {
+ { EXPECT, "node1", PACKET_TYPE_ATTEMPT_JOIN },
+ { EXPECT, "node0", PACKET_TYPE_ATTEMPT_JOIN },
+ { DONE }
+};
+
+h_setup_t h_setup2[] = {
+ { "node0", 0x1, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 1, 9 },
+ { "node0", 0x3, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 0, 9 },
+ { "node0", 0x6, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 2, 9 },
+ { "node0", 0x2, PACKET_TYPE_DATA, "001", NULL, 0x0, 1, 1, 6 },
+ { "node0", 0x4, PACKET_TYPE_DATA, "001", NULL, 0x0, 2, 3, 3 },
+ { "node0", 0x5, PACKET_TYPE_DATA, "001", NULL, 0x0, 1, 2, 6 },
+ { NULL },
+};
+
+h_expect_t h_expectation2[] = {
+ { START_DATA, "node0", PACKET_TYPE_INVALID, 0x1 },
+ { UNHOLD, "node0" },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 2 },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 1 },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 0 },
+ { DONE }
+};
+
+h_setup_t h_setup3[] = {
+ { "node0", 0x1, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 1, 9 },
+ { "node0", 0x2, PACKET_TYPE_DATA, "001", NULL, 0x0, 1, 1, 6 },
+ { "node0", 0x3, PACKET_TYPE_ATTEMPT_JOIN, NULL, NULL },
+ { "node0", 0x4, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 0, 9 },
+ { "node0", 0x5, PACKET_TYPE_DATA, "001", NULL, 0x0, 2, 3, 3 },
+ { "node0", 0x6, PACKET_TYPE_DATA, "001", NULL, 0x0, 1, 2, 6 },
+ { "node0", 0x7, PACKET_TYPE_ATTEMPT_JOIN, NULL, NULL },
+ { "node0", 0x8, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 2, 9 },
+ { NULL },
+};
+
+h_expect_t h_expectation3[] = {
+ { EXPECT, "node0", PACKET_TYPE_ATTEMPT_JOIN },
+ { START_DATA, "node0", PACKET_TYPE_INVALID, 0x1 },
+ { UNHOLD_IMMEDIATE, "node0" },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 2 },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 1 },
+ { EXPECT, "node0", PACKET_TYPE_ATTEMPT_JOIN },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 0 },
+ { DONE }
+};
+
+h_setup_t h_setup4[] = {
+ { "node0", 0x1, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 1, 9 },
+ { "node0", 0x2, PACKET_TYPE_DATA, "001", NULL, 0x0, 1, 1, 6 },
+ { "node0", 0x3, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 0, 9 },
+ { "node0", 0x4, PACKET_TYPE_DATA, "001", NULL, 0x0, 2, 3, 3 },
+ { "node0", 0x5, PACKET_TYPE_DATA, "001", NULL, 0x0, 1, 2, 6 },
+ { "node0", 0x6, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 2, 9 },
+ { NULL },
+};
+
+h_expect_t h_expectation4[] = {
+ { START_DATA, "node0", PACKET_TYPE_INVALID, 0x2 },
+ { UNHOLD, "node0" },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 2 },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 1 },
+ { DONE }
+};
+
+/* Test if failing a node correctly pops the minimum amount of packets needed
+ * to fulfill all dependencies */
+h_setup_t h_setup5[] = {
+ { "node1", 0x1, PACKET_TYPE_DATA, "001", NULL, 0x0, 1, 3, 3 },
+ { "node1", 0x2, PACKET_TYPE_DATA, "001", "node0", 0x3, 2, 3, 3 },
+
+ /* As the very first thing do a Control packet, which isn't hold back. To
+ * force the setting of FAIL (as the setup instructions are run as soon as
+ * the join packet is received) */
+ { "node0", 0x1, PACKET_TYPE_JOIN, "001", NULL },
+ { "node0", 0x2, PACKET_TYPE_DATA, "001", "node1", 0x2, 2, 3, 3 },
+ { "node0", 0x3, PACKET_TYPE_DATA, "001", NULL, 0x0, 0, 3, 3 },
+ { NULL },
+};
+
+h_expect_t h_expectation5[] = {
+ { EXPECT, "node0", PACKET_TYPE_JOIN, 0, 1 },
+ { START_DATA, "node0", PACKET_TYPE_INVALID, 0x1 },
+ { START_DATA, "node1", PACKET_TYPE_INVALID, 0x1 },
+ { FAIL, "node0", PACKET_TYPE_INVALID, 0x2 },
+ { UNHOLD, "node0" },
+ { UNHOLD, "node1" },
+ { EXPECT, "node1", PACKET_TYPE_DATA, 0, 1 },
+ { EXPECT, "node0", PACKET_TYPE_DATA, 0, 2 },
+ { EXPECT, "node1", PACKET_TYPE_DATA, 0, 2 },
+ { DONE }
+};
+
+#define NUMBER_OF_H_TESTS 6
+h_test_t h_tests[NUMBER_OF_H_TESTS] = {
+ { h_setup0, h_expectation0 },
+ { h_setup1, h_expectation1 },
+ { h_setup2, h_expectation2 },
+ { h_setup3, h_expectation3 },
+ { h_setup4, h_expectation4 },
+ { h_setup5, h_expectation5 },
+ };
+
+
+static void
+add_h_sender (guint32 sender, gchar *name, GibberRMulticastSenderGroup *group,
+ guint32 packet_id, h_data_t *data)
+{
+ GibberRMulticastSender *s;
+
+ s = gibber_r_multicast_sender_new (sender, name, group);
+ gibber_r_multicast_sender_update_start (s, packet_id);
+ gibber_r_multicast_sender_hold_data (s, packet_id);
+ gibber_r_multicast_sender_group_add (group, s);
+
+ g_signal_connect (s, "received-data",
+ G_CALLBACK (h_received_data_cb), data);
+ g_signal_connect (s, "received-control-packet",
+ G_CALLBACK (h_received_control_packet_cb), data);
+}
+
+static void
+test_holding (gint _i)
+{
+ GibberRMulticastSenderGroup *group;
+ guint32 sender_offset = 0xf00;
+ /* control packets aren't hold back, thus we get them interleaved at first
+ */
+ h_test_t *test = h_tests + _i;
+ h_data_t data = { 0, NULL, test->expectation };
+ int i;
+
+ g_type_init ();
+ loop = g_main_loop_new (NULL, FALSE);
+
+ group = gibber_r_multicast_sender_group_new ();
+ data.group = group;
+
+ for (i = 0; test->setup[i].name != NULL; i++)
+ {
+ GibberRMulticastSender *s;
+ s = g_hash_table_find (group->senders,
+ h_find_sender, test->setup[i].name);
+ if (s == NULL)
+ {
+ add_h_sender (sender_offset++, test->setup[i].name, group,
+ test->setup[i].packet_id, &data);
+ }
+ }
+
+ for (i = 0; test->setup[i].name != NULL; i++)
+ {
+ GibberRMulticastSender *s0, *s1 = NULL;
+ GibberRMulticastPacket *p;
+
+ s0 = g_hash_table_find (group->senders, h_find_sender,
+ test->setup[i].name);
+ g_assert (s0 != NULL);
+
+ p = gibber_r_multicast_packet_new (test->setup[i].packet_type, s0->id,
+ 1500);
+ gibber_r_multicast_packet_set_packet_id (p, test->setup[i].packet_id);
+
+ if (test->setup[i].depend_node != NULL)
+ {
+ s1 = g_hash_table_find (group->senders, h_find_sender,
+ test->setup[i].depend_node);
+ g_assert (s1 != NULL);
+ g_assert (gibber_r_multicast_packet_add_sender_info (p, s1->id,
+ test->setup[i].depend_packet_id, NULL));
+ }
+ if (test->setup[i].packet_type == PACKET_TYPE_DATA)
+ {
+ g_assert (test->setup[i].data != NULL);
+
+ gibber_r_multicast_packet_set_data_info (p,
+ test->setup[i].data_stream_id,
+ test->setup[i].flags,
+ test->setup[i].total_size);
+ gibber_r_multicast_packet_add_payload (p,
+ (guint8 *) test->setup[i].data, strlen (test->setup[i].data));
+ }
+ gibber_r_multicast_sender_push (s0, p);
+
+ g_object_unref (p);
+ }
+
+ h_next_test_step (&data);
+
+ do
+ {
+ g_main_loop_run (loop);
+ }
+ while (data.expectation[data.test_step].type != DONE);
+
+ g_assert (idle_timer == 0);
+}
+
+static void
+test_holding_loop (void)
+{
+ gint i;
+ for (i = 0; i < NUMBER_OF_H_TESTS; ++i)
+ test_holding (i);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_type_init ();
+
+ /* Kill this process after 20 seconds */
+ alarm (20);
+
+ g_test_add_func ("/gibber/r-multicast-sender/sender", test_sender_loop);
+ g_test_add_func ("/gibber/r-multicast-sender/holding", test_holding_loop);
+
+ return g_test_run ();
+}
diff --git a/salut/lib/gibber/tests/check-gibber-unix-transport.c b/salut/lib/gibber/tests/check-gibber-unix-transport.c
new file mode 100644
index 000000000..c3cfbed8b
--- /dev/null
+++ b/salut/lib/gibber/tests/check-gibber-unix-transport.c
@@ -0,0 +1,242 @@
+/*
+ * check-unix-transport.c - Test for GibberUnixTransport
+ * Copyright (C) 2009 Collabora Ltd.
+ * @author Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <sys/un.h>
+
+#include <gibber/gibber-unix-transport.h>
+#include <gibber/gibber-listener.h>
+
+gboolean got_connection;
+gboolean received_credentials;
+GibberUnixTransport *unix_transport;
+
+#define DATA "What a nice data"
+
+static void
+new_connection_cb (GibberListener *listener,
+ GibberTransport *connection,
+ struct sockaddr *addr,
+ guint size,
+ GMainLoop *loop)
+{
+#if defined(__linux__)
+ int fd, opt, ret;
+ struct iovec iov;
+ struct msghdr msg;
+ ssize_t received;
+ char control[CMSG_SPACE (sizeof (struct ucred))];
+ struct cmsghdr *ch;
+ struct ucred *cred;
+ gchar buffer[128];
+#endif
+
+ got_connection = TRUE;
+
+ /* Block receiving so the data won't be consummed by transport's GIOSource */
+ gibber_transport_block_receiving (connection, TRUE);
+
+#if defined(__linux__)
+ g_assert (gibber_unix_transport_send_credentials (unix_transport,
+ (guint8 *) DATA, strlen (DATA) + 1));
+
+ fd = GIBBER_FD_TRANSPORT (connection)->fd;
+
+ opt = 1;
+ ret = setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &opt, sizeof (opt));
+ g_assert (ret != -1);
+
+ memset (buffer, 0, sizeof (buffer));
+ memset (&iov, 0, sizeof (iov));
+ iov.iov_base = buffer;
+ iov.iov_len = sizeof (buffer);
+
+ memset (&msg, 0, sizeof (msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof (control);
+
+ received = recvmsg (fd, &msg, 0);
+ /* check the received data */
+ g_assert (received != -1);
+ g_assert (strcmp (DATA, buffer) == 0);
+
+ /* check the credentials */
+ ch = CMSG_FIRSTHDR (&msg);
+ g_assert (ch != NULL);
+ cred = (struct ucred *) CMSG_DATA (ch);
+ g_assert (cred->pid == getpid ());
+ g_assert (cred->uid == getuid ());
+ g_assert (cred->gid == getgid ());
+#else /* not Linux */
+ g_assert (!gibber_unix_transport_send_credentials (unix_transport,
+ (guint8 *) DATA, strlen (DATA) + 1));
+#endif
+
+ g_main_loop_quit (loop);
+}
+
+static void
+test_send_credentials (void)
+{
+ GibberListener *listener_unix;
+ int ret;
+ GMainLoop *mainloop;
+ GError *error = NULL;
+ gchar *path = "/tmp/check-gibber-unix-transport-socket";
+
+ ret = unlink (path);
+ g_assert (!(ret == -1 && errno != ENOENT));
+
+ got_connection = FALSE;
+ mainloop = g_main_loop_new (NULL, FALSE);
+
+ listener_unix = gibber_listener_new ();
+ g_assert (listener_unix != NULL);
+
+ g_signal_connect (listener_unix, "new-connection",
+ G_CALLBACK (new_connection_cb), mainloop);
+
+ ret = gibber_listener_listen_socket (listener_unix, path, FALSE, &error);
+ g_assert (ret == TRUE);
+
+ unix_transport = gibber_unix_transport_new ();
+ ret = gibber_unix_transport_connect (unix_transport, path, &error);
+ g_assert (ret == TRUE);
+
+ if (!got_connection)
+ g_main_loop_run (mainloop);
+
+ /* "Failed to connect" */
+ g_assert (got_connection);
+
+ g_object_unref (listener_unix);
+ g_object_unref (unix_transport);
+ g_main_loop_unref (mainloop);
+}
+
+static void
+get_credentials_cb (GibberUnixTransport *transport,
+ GibberBuffer *buffer,
+ GibberCredentials *credentials,
+ GError *error,
+ gpointer user_data)
+{
+ GMainLoop *loop = (GMainLoop *) user_data;
+
+ received_credentials = TRUE;
+
+ g_assert (error == NULL);
+ g_assert (strcmp ((gchar *) buffer->data, DATA) == 0);
+ g_assert (credentials->pid == getpid ());
+ g_assert (credentials->uid == getuid ());
+ g_assert (credentials->gid == getgid ());
+
+ g_main_loop_quit (loop);
+}
+
+static void
+receive_new_connection_cb (GibberListener *listener,
+ GibberTransport *connection,
+ struct sockaddr *addr,
+ guint size,
+ GMainLoop *loop)
+{
+ gboolean ok;
+
+ ok = gibber_unix_transport_recv_credentials (unix_transport,
+ get_credentials_cb, loop);
+
+ g_assert (ok == gibber_unix_transport_supports_credentials ());
+
+ ok = gibber_unix_transport_send_credentials (GIBBER_UNIX_TRANSPORT (connection),
+ (guint8 *) DATA, strlen (DATA));
+
+ g_assert (ok == gibber_unix_transport_supports_credentials ());
+}
+
+static void
+test_receive_credentials (void)
+{
+ GibberListener *listener_unix;
+ int ret;
+ GMainLoop *mainloop;
+ GError *error = NULL;
+ gchar *path = "/tmp/check-gibber-unix-transport-socket";
+
+ ret = unlink (path);
+ g_assert (!(ret == -1 && errno != ENOENT));
+
+ received_credentials = FALSE;
+ mainloop = g_main_loop_new (NULL, FALSE);
+
+ listener_unix = gibber_listener_new ();
+ g_assert (listener_unix != NULL);
+
+ g_signal_connect (listener_unix, "new-connection",
+ G_CALLBACK (receive_new_connection_cb), mainloop);
+
+ ret = gibber_listener_listen_socket (listener_unix, path, FALSE, &error);
+ g_assert (ret == TRUE);
+
+ unix_transport = gibber_unix_transport_new ();
+ ret = gibber_unix_transport_connect (unix_transport, path, &error);
+ g_assert (ret == TRUE);
+
+#if defined(__linux__)
+ if (!received_credentials)
+ g_main_loop_run (mainloop);
+
+ /* Failed to receive credentials */
+ g_assert (received_credentials);
+#endif
+
+ g_object_unref (listener_unix);
+ g_object_unref (unix_transport);
+ g_main_loop_unref (mainloop);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_type_init ();
+
+ alarm (20);
+
+ g_test_add_func ("/gibber/unix-transport/send-credentials",
+ test_send_credentials);
+ g_test_add_func ("/gibber/unix-transport/receive-credentials",
+ test_receive_credentials);
+
+ return g_test_run ();
+}
diff --git a/salut/lib/gibber/tests/mesh.py b/salut/lib/gibber/tests/mesh.py
new file mode 100644
index 000000000..e0a7fef82
--- /dev/null
+++ b/salut/lib/gibber/tests/mesh.py
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+
+from twisted.internet import reactor, protocol
+from base64 import b64encode, b64decode
+from struct import unpack
+import random
+
+packettypes = { 0: "Whois request",
+ 1: "Whois reply",
+ 2: "Repair request",
+ 3: "Session",
+ 0xf: "Data",
+ 0x10: "No data",
+ 0x11: "Failure",
+ 0x12: "Attempt join",
+ 0x13: "Join",
+ 0x14: "Bye"
+}
+
+WHOIS_REQUEST = 0x0
+WHOIS_REPLY = 0x1
+REPAIR_REQUEST = 0x2
+SESSION = 0x3
+DATA = 0xf
+NO_DATA = 0x10
+FAILURE = 0x11
+ATTEMPT_JOIN = 0x12
+JOIN = 0x13
+BYE = 0x14
+
+def packet_sender(data):
+ return unpack ("!I", data[8:12])[0]
+
+def packet_type(data):
+ return unpack("B", data[7])[0]
+
+def dump_depends(data):
+ sender = packet_sender(data)
+ num_senders = unpack("B", data[16])[0]
+ print "sender: %x (%d)" % (sender, num_senders)
+ for x in xrange (0, num_senders):
+ (sender, depend) = unpack("!II", data[17 + x:17 + x + 8])
+ print "%x:\t%x" % (sender, depend)
+
+
+class BaseMeshNode(protocol.ProcessProtocol):
+ delimiter = '\n'
+ __buffer = ''
+
+ def __init__(self, name):
+ self.name = name
+ self.process = reactor.spawnProcess(self,
+ "./test-r-multicast-transport-io",
+ ("test-r-multicast-transport-io", name),
+ None)
+ self.peers = []
+ self.packets = {}
+
+ def sendPacket(self, data):
+ "Should be overridden"
+ print "Should send: " + data
+
+ def __sendPacket(self, data):
+ binary = b64decode(data)
+ type = packet_type(binary)
+ self.packets[type] = self.packets.get(type, 0) + 1
+ return self.sendPacket(binary)
+
+ def stats(self):
+ print "-------" + self.name + "-------"
+ for (a,b) in self.packets.iteritems():
+ print packettypes[a] + ":\t" + str(b)
+
+ def gotOutput(self, sender, data):
+ "Should be overridden"
+ print "Output: " + data
+
+ def node_connected(self):
+ "Should be overridden"
+ print "Connected!!"
+
+ def node_disconnected(self):
+ "Should be overridden"
+ print "Disconnected!!"
+
+ def __connected(self, data):
+ self.node_connected()
+
+ def __disconnected(self, data):
+ self.node_disconnected()
+
+ def __gotOutput(self, data):
+ (sender, rawdata) = data.split(":", 1)
+ self.gotOutput(sender, b64decode(rawdata))
+
+
+ def newNode(self, data):
+ if not data in self.peers:
+ self.peers.append(data)
+
+ def __newNodes(self, data):
+ for x in data.split():
+ self.newNode(x)
+
+ def leftNode(self, node):
+ self.peers.remove(node)
+
+ def __leftNodes(self, data):
+ for x in data.split():
+ self.leftNode (x)
+
+ def recvPacket(self, data):
+ self.process.write("RECV:" + b64encode(data) + "\n")
+
+ def pushInput(self, data):
+ self.process.write("INPUT:" + b64encode(data) + "\n")
+
+ def fail(self, name):
+ self.process.write("FAIL:" + name + "\n")
+
+ def disconnect(self):
+ self.process.write("DISCONNECT\n")
+
+ def lineReceived(self, line):
+ commands = { "SEND" : self.__sendPacket,
+ "OUTPUT" : self.__gotOutput,
+ "CONNECTED" : self.__connected,
+ "DISCONNECTED" : self.__disconnected,
+ "NEWNODES" : self.__newNodes,
+ "LOSTNODES" : self.__leftNodes
+ }
+
+ parts = line.rstrip().split(":", 1)
+ if len(parts) == 2:
+ command = parts[0]
+ rawdata = parts[1]
+ if command in commands:
+ commands[command](rawdata)
+ return
+ self.unknownOutput(line)
+
+ def unknownOutput(self, line):
+ print self.name + " (U) " + line.rstrip()
+
+ def outReceived(self, data):
+ lines = (self.__buffer + data).split(self.delimiter)
+ self.__buffer = lines.pop(-1)
+ for line in lines:
+ self.lineReceived(line)
+
+ def errReceived(self, data):
+ print self.name + " Error: " + data.rstrip()
+
+ def processEnded(self, reason):
+ if self.process.status != 0:
+ print "process ended: " + str(reason)
+
+class MeshNode(BaseMeshNode):
+ def __init__(self, name, mesh):
+ BaseMeshNode.__init__(self, name)
+ self.mesh = mesh
+
+ def sendPacket(self, data):
+ self.mesh.sendPacket(self, data)
+
+ def gotOutput(self, sender, data):
+ self.mesh.gotOutput(self, sender, data)
+
+ def node_connected(self):
+ self.mesh.connected(self)
+
+class Link:
+ def __init__(self, target, bandwidth, latency, dropchance):
+ self.target = target
+ self.bandwidth = bandwidth
+ self.latency = latency
+ self.dropchance = dropchance
+
+ def send(self, data):
+ if random.random() > self.dropchance:
+ self.target.recvPacket(data)
+
+class Mesh:
+ def __init__ (self):
+ self.nodes = []
+ self.connections = {}
+
+ def connect(self, node):
+ print node.name + " got connected"
+
+ def gotOutput(self, node, sender, data):
+ print "Got " + data + " from " + node.name + " send by " + sender
+
+ def connect(self, node0, node1, bandwidth, latency, dropchance):
+ self.connections.setdefault(node0, []).append(
+ Link(node1, bandwidth, latency, dropchance))
+
+ def connect_duplex(self, node0, node1, bandwidth, latency, dropchance):
+ self.connect(node0, node1, bandwidth, latency, dropchance)
+ self.connect(node1, node0, bandwidth, latency, dropchance)
+
+ def connect_full(self, bandwidth, latency, dropchance):
+ self.connections = {}
+ for x in self.nodes:
+ for y in self.nodes:
+ if x != y:
+ self.connect(x, y, bandwidth, latency, dropchance)
+
+ def sendPacket(self, node, data):
+ conn = self.connections.get(node, [])
+ for link in conn:
+ link.send(data)
+
+ def addNode(self, name):
+ node = MeshNode(name, self)
+ self.nodes.append(node)
+ return node
+
+ def addMeshNode(self, node):
+ self.nodes.append(node)
+ return node
+
+ def removeMeshNode (self, node):
+ self.nodes.remove(node)
+ del self.connections[node]
+ for (n, links) in self.connections.iteritems():
+ for link in links:
+ if link.target == node:
+ self.connections[n].remove(link)
+
+
+ def connected (self, node):
+ "To be overwritten"
+ pass
+
diff --git a/salut/lib/gibber/tests/simplemeshtest.py b/salut/lib/gibber/tests/simplemeshtest.py
new file mode 100644
index 000000000..575eeb946
--- /dev/null
+++ b/salut/lib/gibber/tests/simplemeshtest.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+
+from twisted.internet import reactor
+from mesh import Mesh, MeshNode
+import sys
+
+NUMNODES = 5
+NUMPACKETS = 10
+DELAY = 0.1
+
+
+nodes = []
+# We're optimists
+success = True
+
+class TestMeshNode(MeshNode):
+ nodes = 1
+
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def node_connected(self):
+ MeshNode.node_connected(self)
+ print "Connected"
+
+ def newNode (self, data):
+ MeshNode.newNode (self, data)
+ print "node0 - Added " + data
+ self.nodes += 1
+ if self.nodes == NUMNODES:
+ print "Everybody joined"
+ for x in xrange(0, NUMPACKETS):
+ reactor.callLater(0.1 * x, (lambda y: self.pushInput(str(y) + "\n")), x)
+
+ def leftNode (self, data):
+ MeshNode.leftNode (self, data)
+ print data.rstrip() + " left"
+ reactor.stop()
+
+class TestMesh(Mesh):
+ expected = {}
+ done = 0
+
+ def gotOutput(self, node, sender, data):
+ global success
+
+ if self.expected.get(node) == None:
+ self.expected[node] = 0
+
+ if (self.expected.get(node, int(data)) != int(data)):
+ print "Got " + data.rstrip() + " instead of " + \
+ str(self.expected[node]) + " from " + node.name
+
+ success = False
+ reactor.crash()
+
+ if not sender in node.peers:
+ print "Sender " + sender + " not in node peers"
+ success = False
+ reactor.crash()
+
+ self.expected[node] = int(data) + 1
+
+ if self.expected[node] == 10:
+ self.done += 1
+
+ if self.done == NUMNODES - 1:
+ for x in self.nodes:
+ x.stats()
+ self.nodes[-1].disconnect()
+
+m = TestMesh()
+
+
+n = TestMeshNode("node0", m)
+nodes.append(n)
+m.addMeshNode(n)
+
+for x in xrange(1, NUMNODES):
+ nodes.append(m.addNode("node" + str(x)))
+
+# Connect all nodes to all others. 1024 bytes/s bandwidth, 50ms delay and 0%
+# packet loss.. (bandwidth and delay aren't implemented just yet)
+m.connect_full(1024, 50, 0.30)
+
+def timeout():
+ global success
+ print "TIMEOUT!"
+ success = False
+ reactor.crash()
+
+reactor.callLater(60, timeout)
+
+reactor.run()
+
+
+if not success:
+ print "FAILED"
+ sys.exit(-1)
+
+print "SUCCESS"
diff --git a/salut/lib/gibber/tests/test-r-multicast-transport-io.c b/salut/lib/gibber/tests/test-r-multicast-transport-io.c
new file mode 100644
index 000000000..62a4e96dc
--- /dev/null
+++ b/salut/lib/gibber/tests/test-r-multicast-transport-io.c
@@ -0,0 +1,226 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include <gibber/gibber-r-multicast-transport.h>
+#include <gibber/gibber-debug.h>
+#include "test-transport.h"
+
+GMainLoop *loop;
+
+TestTransport *t;
+GibberRMulticastTransport *rm;
+GibberRMulticastCausalTransport *rmc;
+gulong rmc_connected_handler = 0;
+
+static void
+received_data (GibberTransport *transport, GibberBuffer *buffer,
+ gpointer user_data)
+{
+ GibberRMulticastBuffer *rmbuffer = (GibberRMulticastBuffer *) buffer;
+ gchar *b64;
+
+ b64 = g_base64_encode ((guchar *) buffer->data, buffer->length);
+ printf ("OUTPUT:%s:%s\n", rmbuffer->sender, b64);
+ fflush (stdout);
+ g_free (b64);
+}
+
+static gboolean
+send_hook (GibberTransport *transport, const guint8 *data, gsize length,
+ GError **error, gpointer user_data)
+{
+ gchar *b64;
+
+ b64 = g_base64_encode ((guchar *) data, length);
+ printf ("SEND:%s\n", b64);
+ fflush (stdout);
+ g_free (b64);
+
+ return TRUE;
+}
+
+static void
+fail_node (gchar *name)
+{
+ GibberRMulticastSender *sender;
+
+ name = g_strstrip (name);
+
+ sender = gibber_r_multicast_causal_transport_get_sender_by_name (rmc, name);
+ g_assert (sender != NULL);
+
+ _gibber_r_multicast_TEST_sender_fail (sender);
+}
+
+static gboolean
+got_input (GIOChannel *source, GIOCondition condition, gpointer user_data)
+{
+ GIOStatus s;
+ gchar *buffer;
+ gsize len;
+ gchar *p;
+ gboolean packet = FALSE;
+ guchar *b64;
+ gsize size;
+
+ s = g_io_channel_read_line (source, &buffer, &len, NULL, NULL);
+ g_assert (s == G_IO_STATUS_NORMAL);
+
+ if (g_str_has_prefix (buffer, "INPUT:"))
+ {
+ packet = FALSE;
+ p = buffer + strlen ("INPUT:");
+ }
+ else if (g_str_has_prefix (buffer, "RECV:"))
+ {
+ packet = TRUE;
+ p = buffer + strlen ("RECV:");
+ }
+ else if (strcmp (buffer, "DISCONNECT\n") == 0)
+ {
+ gibber_transport_disconnect (GIBBER_TRANSPORT(rm));
+ goto out;
+ }
+ else if (g_str_has_prefix (buffer, "FAIL:"))
+ {
+ /* this will modify our buffer */
+ fail_node (buffer + strlen ("FAIL:"));
+ goto out;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ b64 = g_base64_decode (p, &size);
+
+ if (packet)
+ {
+ test_transport_write (t, b64, size);
+ }
+ else
+ {
+ g_assert (gibber_transport_send (GIBBER_TRANSPORT (rm),
+ b64, size, NULL));
+ }
+
+ g_free (b64);
+out:
+ g_free (buffer);
+
+ return TRUE;
+}
+
+static gboolean
+got_error (GIOChannel *source, GIOCondition condition, gpointer user_data)
+{
+ g_main_loop_quit (loop);
+ fprintf (stderr, "error");
+ fflush (stderr);
+ return TRUE;
+}
+
+static void
+new_senders_cb (GibberRMulticastTransport *transport,
+ GArray *names, gpointer user_data)
+{
+ guint i;
+ GString *str = g_string_new ("NEWNODES:");
+
+ for (i = 0; i < names->len; i++)
+ {
+ g_string_append_printf (str, "%s ", g_array_index (names, gchar *, i));
+ }
+ printf ("%s\n", str->str);
+ g_string_free (str, TRUE);
+ fflush (stdout);
+}
+
+static void
+lost_senders_cb (GibberRMulticastTransport *transport,
+ GArray *names, gpointer user_data)
+{
+ guint i;
+ GString *str = g_string_new ("LOSTNODES:");
+
+ for (i = 0; i < names->len; i++)
+ {
+ g_string_append_printf (str, "%s ", g_array_index (names, gchar *, i));
+ }
+ printf ("%s\n", str->str);
+ g_string_free (str, TRUE);
+ fflush (stdout);
+}
+
+static void
+rm_connected (GibberRMulticastTransport *transport, gpointer user_data)
+{
+ printf ("CONNECTED:\n");
+ fflush (stdout);
+}
+
+static void
+rm_disconnected (GibberRMulticastTransport *transport, gpointer user_data)
+{
+ printf ("DISCONNECTED:\n");
+ fflush (stdout);
+ g_main_loop_quit (loop);
+}
+
+static void
+rmc_connected (GibberRMulticastTransport *transport, gpointer user_data)
+{
+ g_assert (gibber_r_multicast_transport_connect (rm, NULL));
+ g_signal_handler_disconnect (transport, rmc_connected_handler);
+}
+
+int
+main (int argc, char **argv)
+{
+ GIOChannel *io;
+
+ g_assert (argc == 2);
+
+ g_type_init ();
+
+ printf ("Starting process %d for %s\n", getpid (), argv[1]);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ t = test_transport_new (send_hook, argv[1]);
+ GIBBER_TRANSPORT (t)->max_packet_size = 1500;
+ test_transport_set_echoing (t, TRUE);
+
+ rmc = gibber_r_multicast_causal_transport_new (GIBBER_TRANSPORT (t),
+ argv[1]);
+ g_object_unref (t);
+
+ rm = gibber_r_multicast_transport_new (rmc);
+ gibber_transport_set_handler (GIBBER_TRANSPORT (rm), received_data, argv[1]);
+ g_object_unref (rmc);
+
+ g_signal_connect (rm, "new-senders", G_CALLBACK (new_senders_cb), NULL);
+
+ g_signal_connect (rm, "lost-senders", G_CALLBACK (lost_senders_cb), NULL);
+
+ rmc_connected_handler = g_signal_connect (rmc, "connected",
+ G_CALLBACK (rmc_connected), NULL);
+
+ g_signal_connect (rm, "connected", G_CALLBACK (rm_connected), NULL);
+
+ g_signal_connect (rm, "disconnected", G_CALLBACK (rm_disconnected), NULL);
+
+ /* test transport starts out connected */
+ g_assert (gibber_r_multicast_causal_transport_connect (rmc, FALSE, NULL));
+
+ io = g_io_channel_unix_new (STDIN_FILENO);
+ g_io_add_watch (io, G_IO_IN, got_input, NULL);
+ g_io_add_watch (io, G_IO_HUP|G_IO_ERR, got_error, NULL);
+
+ g_main_loop_run (loop);
+
+ return 0;
+}
diff --git a/salut/lib/gibber/tests/test-transport.c b/salut/lib/gibber/tests/test-transport.c
new file mode 100644
index 000000000..b035a5345
--- /dev/null
+++ b/salut/lib/gibber/tests/test-transport.c
@@ -0,0 +1,209 @@
+/*
+ * test-transport.c - Source for TestTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "test-transport.h"
+
+static gboolean
+test_transport_send (GibberTransport *transport, const guint8 *data,
+ gsize size, GError **error);
+
+void test_transport_disconnect (GibberTransport *transport);
+
+G_DEFINE_TYPE (TestTransport, test_transport, GIBBER_TYPE_TRANSPORT)
+
+/* private structure */
+typedef struct _TestTransportPrivate TestTransportPrivate;
+
+struct _TestTransportPrivate
+{
+ gboolean dispose_has_run;
+ test_transport_send_hook send;
+ GQueue *buffers;
+ guint send_id;
+ gpointer user_data;
+
+ gboolean echoing;
+};
+
+#define TEST_TRANSPORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
+ TEST_TYPE_TRANSPORT, TestTransportPrivate))
+
+static void
+test_transport_init (TestTransport *obj)
+{
+ TestTransportPrivate *priv = TEST_TRANSPORT_GET_PRIVATE (obj);
+
+ /* allocate any data required by the object here */
+ priv->send = NULL;
+ priv->buffers = g_queue_new ();
+ priv->send_id = 0;
+}
+
+static void test_transport_dispose (GObject *object);
+static void test_transport_finalize (GObject *object);
+
+static void
+test_transport_class_init (TestTransportClass *test_transport_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (test_transport_class);
+ GibberTransportClass *transport_class =
+ GIBBER_TRANSPORT_CLASS(test_transport_class);
+
+ g_type_class_add_private (test_transport_class, sizeof (TestTransportPrivate));
+
+ object_class->dispose = test_transport_dispose;
+ object_class->finalize = test_transport_finalize;
+
+ transport_class->send = test_transport_send;
+ transport_class->disconnect = test_transport_disconnect;
+}
+
+void
+test_transport_dispose (GObject *object)
+{
+ TestTransport *self = TEST_TRANSPORT (object);
+ TestTransportPrivate *priv = TEST_TRANSPORT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ if (priv->send_id != 0)
+ g_source_remove (priv->send_id);
+
+ priv->dispose_has_run = TRUE;
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (test_transport_parent_class)->dispose)
+ G_OBJECT_CLASS (test_transport_parent_class)->dispose (object);
+}
+
+static void
+free_array (gpointer data, gpointer user_data)
+{
+ g_array_free ((GArray *) data, TRUE);
+}
+
+void
+test_transport_finalize (GObject *object)
+{
+ TestTransport *self = TEST_TRANSPORT (object);
+ TestTransportPrivate *priv = TEST_TRANSPORT_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+ g_queue_foreach (priv->buffers, free_array, NULL);
+ g_queue_free (priv->buffers);
+ G_OBJECT_CLASS (test_transport_parent_class)->finalize (object);
+}
+
+static gboolean
+send_data (gpointer data)
+{
+ TestTransport *self = TEST_TRANSPORT (data);
+ TestTransportPrivate *priv = TEST_TRANSPORT_GET_PRIVATE (self);
+ GArray *arr;
+
+ g_assert (priv->send != NULL);
+
+ arr = (GArray *) g_queue_pop_head (priv->buffers);
+
+ priv->send (GIBBER_TRANSPORT (self),
+ (guint8 *) arr->data, arr->len, NULL, priv->user_data);
+
+ g_array_unref (arr);
+
+ if (g_queue_is_empty (priv->buffers))
+ {
+ priv->send_id = 0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+test_transport_send (GibberTransport *transport,
+ const guint8 *data, gsize size, GError **error)
+{
+ TestTransport *self = TEST_TRANSPORT (transport);
+ TestTransportPrivate *priv = TEST_TRANSPORT_GET_PRIVATE (self);
+
+ GArray *arr;
+
+ if (priv->echoing)
+ {
+ test_transport_write (self, data, size);
+ }
+
+ arr = g_array_sized_new (FALSE, TRUE, sizeof (guint8), size);
+ g_array_append_vals (arr, data, size);
+
+ g_queue_push_tail (priv->buffers, arr);
+
+ if (priv->send_id == 0)
+ {
+ priv->send_id = g_idle_add (send_data, transport);
+ }
+
+ return TRUE;
+}
+
+void
+test_transport_disconnect (GibberTransport *transport)
+{
+ gibber_transport_set_state (GIBBER_TRANSPORT(transport),
+ GIBBER_TRANSPORT_DISCONNECTED);
+}
+
+
+TestTransport *
+test_transport_new (test_transport_send_hook send_, gpointer user_data)
+{
+ TestTransport *self;
+ TestTransportPrivate *priv;
+
+ self = g_object_new (TEST_TYPE_TRANSPORT, NULL);
+ priv = TEST_TRANSPORT_GET_PRIVATE (self);
+ priv->send = send_;
+ priv->user_data = user_data;
+
+ gibber_transport_set_state (GIBBER_TRANSPORT(self),
+ GIBBER_TRANSPORT_CONNECTED);
+
+ return self;
+}
+
+void
+test_transport_set_echoing (TestTransport *transport, gboolean echo)
+{
+ TestTransportPrivate *priv = TEST_TRANSPORT_GET_PRIVATE (transport);
+ priv->echoing = echo;
+}
+
+
+void
+test_transport_write (TestTransport *transport, const guint8 *buf, gsize size)
+{
+ gibber_transport_received_data (GIBBER_TRANSPORT (transport), buf, size);
+}
diff --git a/salut/lib/gibber/tests/test-transport.h b/salut/lib/gibber/tests/test-transport.h
new file mode 100644
index 000000000..1e76d1339
--- /dev/null
+++ b/salut/lib/gibber/tests/test-transport.h
@@ -0,0 +1,73 @@
+/*
+ * test-transport.h - Header for TestTransport
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TEST_TRANSPORT_H__
+#define __TEST_TRANSPORT_H__
+
+#include <glib-object.h>
+
+#include <gibber/gibber-transport.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TestTransport TestTransport;
+typedef struct _TestTransportClass TestTransportClass;
+
+struct _TestTransportClass {
+ GibberTransportClass parent_class;
+};
+
+struct _TestTransport {
+ GibberTransport parent;
+};
+
+GType test_transport_get_type (void);
+
+/* TYPE MACROS */
+#define TEST_TYPE_TRANSPORT \
+ (test_transport_get_type ())
+#define TEST_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TEST_TYPE_TRANSPORT, TestTransport))
+#define TEST_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TEST_TYPE_TRANSPORT, TestTransportClass))
+#define TEST_IS_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TEST_TYPE_TRANSPORT))
+#define TEST_IS_TRANSPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TEST_TYPE_TRANSPORT))
+#define TEST_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_TRANSPORT, TestTransportClass))
+
+typedef gboolean (*test_transport_send_hook)(GibberTransport *transport,
+ const guint8 *data,
+ gsize length,
+ GError **error,
+ gpointer user_data);
+TestTransport *test_transport_new (test_transport_send_hook send,
+ gpointer user_data);
+
+void test_transport_set_echoing (TestTransport *transport,
+ gboolean echo);
+
+void test_transport_write (TestTransport *transport,
+ const guint8 *buf, gsize size);
+
+G_END_DECLS
+
+#endif /* #ifndef __TEST_TRANSPORT_H__*/
diff --git a/salut/m4/Makefile.am b/salut/m4/Makefile.am
new file mode 100644
index 000000000..5e1bb502b
--- /dev/null
+++ b/salut/m4/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = \
+as-compiler-flag.m4
diff --git a/salut/m4/as-compiler-flag.m4 b/salut/m4/as-compiler-flag.m4
new file mode 100644
index 000000000..605708a5a
--- /dev/null
+++ b/salut/m4/as-compiler-flag.m4
@@ -0,0 +1,33 @@
+dnl as-compiler-flag.m4 0.1.0
+
+dnl autostars m4 macro for detection of compiler flags
+
+dnl David Schleef <ds@schleef.org>
+
+dnl $Id: as-compiler-flag.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $
+
+dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED])
+dnl Tries to compile with the given CFLAGS.
+dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags,
+dnl and ACTION-IF-NOT-ACCEPTED otherwise.
+
+AC_DEFUN([AS_COMPILER_FLAG],
+[
+ AC_MSG_CHECKING([to see if compiler understands $1])
+
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $1"
+
+ AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ CFLAGS="$save_CFLAGS"
+
+ if test "X$flag_ok" = Xyes ; then
+ $2
+ true
+ else
+ $3
+ true
+ fi
+ AC_MSG_RESULT([$flag_ok])
+])
+
diff --git a/salut/m4/ax_config_dir.m4 b/salut/m4/ax_config_dir.m4
new file mode 100644
index 000000000..0ba313faa
--- /dev/null
+++ b/salut/m4/ax_config_dir.m4
@@ -0,0 +1,109 @@
+dnl Copied from Audacity 1.3.10 which itself is licensed under the GPL v2 or
+dnl any later version
+
+dnl Function to configure a sub-library now, because we need to know the result
+dnl of the configuration now in order to take decisions.
+dnl We don't worry about whether the configuration worked or not - it is
+dnl assumed that the next thing after this will be a package-specific check to
+dnl see if the package is actually available. (Hint: use pkg-config and
+dnl -uninstalled.pc files if available).
+dnl code based on a simplification of _AC_OUTPUT_SUBDIRS in
+dnl /usr/share/autoconf/autoconf/status.m4 which implements part of
+dnl AC_CONFIG_SUBDIRS
+
+AC_DEFUN([AX_CONFIG_DIR],
+[AC_REQUIRE([AC_DISABLE_OPTION_CHECKING])]
+[m4_append([_AC_LIST_SUBDIRS], [$1], [])]
+[
+ # Remove --cache-file and --srcdir arguments so they do not pile up.
+ ax_sub_configure_args=
+ ax_prev=
+ eval "set x $ac_configure_args"
+ shift
+ for ax_arg
+ do
+ if test -n "$ax_prev"; then
+ ax_prev=
+ continue
+ fi
+ case $ax_arg in
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ax_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* \
+ | --c=*)
+ ;;
+ --config-cache | -C)
+ ;;
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ax_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ ;;
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ax_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ ;;
+ *)
+ case $ax_arg in
+ *\'*) ax_arg=`echo "$ax_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ ax_sub_configure_args="$ax_sub_configure_args '$ax_arg'" ;;
+ esac
+ done
+
+ # Always prepend --prefix to ensure using the same prefix
+ # in subdir configurations.
+ ax_arg="--prefix=$prefix"
+ case $ax_arg in
+ *\'*) ax_arg=`echo "$ax_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ ax_sub_configure_args="'$ax_arg' $ax_sub_configure_args"
+
+ # Pass --silent
+ if test "$silent" = yes; then
+ ax_sub_configure_args="--silent $ax_sub_configure_args"
+ fi
+
+ ax_popdir=`pwd`
+ AC_MSG_NOTICE([Configuring sources in $1])
+ dnl for out-of-place builds srcdir and builddir will be different, and
+ dnl builddir may not exist, so we must create it
+ AS_MKDIR_P(["$1"])
+ dnl and also set the variables. As this isn't autoconf, the following may be
+ dnl risky:
+ _AC_SRCDIRS(["$1"])
+ cd "$1"
+
+ # Check for guested configure; otherwise get Cygnus style configure.
+ if test -f "configure.gnu"; then
+ ax_sub_configure=$ac_srcdir/configure.gnu
+ elif test -f "$ac_srcdir/configure"; then
+ ax_sub_configure=$ac_srcdir/configure
+ elif test -f "$ac_srcdir/configure.in"; then
+ # This should be Cygnus configure.
+ ax_sub_configure=$ac_aux_dir/configure
+ else
+ AC_MSG_WARN([no configuration information is in $1])
+ ax_sub_configure=
+ fi
+
+ # The recursion is here.
+ if test -n "$ax_sub_configure"; then
+ # Make the cache file name correct relative to the subdirectory.
+ case $cache_file in
+ [[\\/]]* | ?:[[\\/]]* ) ax_sub_cache_file=$cache_file ;;
+ *) # Relative name.
+ ax_sub_cache_file=$ac_top_build_prefix$cache_file ;;
+ esac
+
+ AC_MSG_NOTICE([running $SHELL $ax_sub_configure $ax_sub_configure_args --cache-file=$ax_sub_cache_file --srcdir=$ac_srcdir])
+ # The eval makes quoting arguments work.
+ eval "\$SHELL \"\$ax_sub_configure\" $ax_sub_configure_args \
+ --cache-file=\"\$ax_sub_cache_file\" --srcdir=\"\$ax_srcdir\""
+ fi
+
+ cd "$ax_popdir"
+ AC_MSG_NOTICE([Done configuring in $1])
+])
+
diff --git a/salut/m4/salut-args.m4 b/salut/m4/salut-args.m4
new file mode 100644
index 000000000..f95ad9df7
--- /dev/null
+++ b/salut/m4/salut-args.m4
@@ -0,0 +1,56 @@
+dnl configure-time options for Telepathy Salut
+
+dnl SALUT_ARG_DEBUG
+dnl SALUT_ARG_VALGRIND
+dnl SALUT_ARG_COVERAGE
+
+AC_DEFUN([SALUT_ARG_DEBUG],
+[
+ dnl debugging stuff
+ AC_ARG_ENABLE(debug,
+ AC_HELP_STRING([--disable-debug],[compile without debug code]),
+ [
+ case "${enableval}" in
+ yes|no) enable="${enableval}" ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;;
+ esac
+ ],
+ [enable=yes])
+
+ if test "$enable" = yes; then
+ AC_DEFINE(ENABLE_DEBUG, [], [Enable debug code])
+ fi
+])
+
+AC_DEFUN([SALUT_ARG_VALGRIND],
+[
+ dnl valgrind inclusion
+ AC_ARG_ENABLE(valgrind,
+ AC_HELP_STRING([--enable-valgrind],[enable valgrind checking and run-time detection]),
+ [
+ case "${enableval}" in
+ yes|no) enable="${enableval}" ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-valgrind) ;;
+ esac
+ ],
+ [enable=no])
+
+ SALUT_VALGRIND($enable, [2.1])
+])
+
+AC_DEFUN([SALUT_ARG_COVERAGE],
+[
+ AC_ARG_ENABLE(coverage,
+ AC_HELP_STRING([--enable-coverage],
+ [compile with coverage profiling instrumentation (gcc only)]),
+ [
+ case "${enableval}" in
+ yes|no) enable="${enableval}" ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-coverage) ;;
+ esac
+ ],
+ [enable=no])
+
+ SALUT_GCOV($enable)
+ SALUT_LCOV($enable)
+])
diff --git a/salut/m4/salut-gcov.m4 b/salut/m4/salut-gcov.m4
new file mode 100644
index 000000000..25fcb50ff
--- /dev/null
+++ b/salut/m4/salut-gcov.m4
@@ -0,0 +1,54 @@
+dnl Detect and set flags for gcov
+
+AC_DEFUN([SALUT_GCOV],
+[
+ enable=$1
+
+ GCOV=`echo $CC | sed s/gcc/gcov/g`
+ AC_CHECK_PROG(have_gcov, $GCOV, yes, no)
+
+ AS_COMPILER_FLAG(["-fprofile-arcs"],
+ [flag_profile_arcs=yes],
+ [flag_profile_arcs=no])
+
+ AS_COMPILER_FLAG(["-ftest-coverage"],
+ [flag_test_coverage=yes],
+ [flag_test_coverage=no])
+
+ if test "x$enable" = xyes &&
+ test "x$GCC" = "xyes" &&
+ test "$flag_profile_arcs" = yes &&
+ test "$flag_test_coverage" = yes ;
+ then
+
+ GCOV_CFLAGS="$GCOV_CFLAGS -fprofile-arcs -ftest-coverage"
+ dnl remove any -O flags - FIXME: is this needed ?
+ GCOV_CFLAGS=`echo "$GCOV_CFLAGS" | sed -e 's/-O[[0-9]]*//g'`
+
+ dnl libtool 1.5.22 and lower strip -fprofile-arcs from the flags
+ dnl passed to the linker, which is a bug; -fprofile-arcs implicitly
+ dnl links in -lgcov, so we do it explicitly here for the same effect
+ GCOV_LIBS=-lgcov
+
+ AC_SUBST([MOSTLYCLEANFILES], "*.bb *.bbg *.da *.gcov *.gcda *.gcno")
+
+ AC_DEFINE_UNQUOTED(HAVE_GCOV, 1,
+ [Defined if gcov is enabled to force a rebuild due to config.h changing])
+
+ CFLAGS="-O0"
+ AC_SUBST(CFLAGS)
+ CXXFLAGS="-O0"
+ AC_SUBST(CXXFLAGS)
+ FFLAGS="-O0"
+ AC_SUBST(FFLAGS)
+ CCASFLAGS="-O0"
+ AC_SUBST(CCASFLAGS)
+ AC_MSG_NOTICE([gcov enabled, setting CFLAGS and friends to $CFLAGS])
+ fi
+
+ AC_SUBST(GCOV_CFLAGS)
+ AC_SUBST(GCOV_LIBS)
+ AC_SUBST(GCOV)
+
+ AM_CONDITIONAL(HAVE_GCOV, test x$have_gcov = xyes)
+])
diff --git a/salut/m4/salut-lcov.m4 b/salut/m4/salut-lcov.m4
new file mode 100644
index 000000000..cfd178f6d
--- /dev/null
+++ b/salut/m4/salut-lcov.m4
@@ -0,0 +1,22 @@
+dnl Check for lcov utility
+
+AC_DEFUN([SALUT_LCOV],
+[
+ enable=$1
+
+ AC_CHECK_PROGS(LCOV_PATH, lcov)
+
+ if test -n "$LCOV_PATH" ; then
+ AC_MSG_CHECKING([whether lcov accepts --compat-libtool])
+ if $LCOV_PATH --compat-libtool --help > /dev/null 2>&1 ; then
+ AC_MSG_RESULT(ok)
+ else
+ AC_MSG_RESULT(no)
+ AC_MSG_WARN([lcov option --compat-libtool is not supported])
+ AC_MSG_WARN([update lcov to version > 1.5])
+ LCOV_PATH=""
+ fi
+ fi
+
+ AM_CONDITIONAL(HAVE_LCOV, test -n "$LCOV_PATH" && test "x$enable" = xyes)
+])
diff --git a/salut/m4/salut-valgrind.m4 b/salut/m4/salut-valgrind.m4
new file mode 100644
index 000000000..8bb679d6f
--- /dev/null
+++ b/salut/m4/salut-valgrind.m4
@@ -0,0 +1,31 @@
+dnl Detect Valgrind location and flags
+
+AC_DEFUN([SALUT_VALGRIND],
+[
+ enable=$1
+ if test -n "$2"; then
+ valgrind_req=$2
+ else
+ valgrind_req="2.1"
+ fi
+
+ PKG_CHECK_MODULES(VALGRIND, valgrind > "$valgrind_req",
+ have_valgrind_runtime="yes", have_valgrind_runtime="no")
+
+ AC_PATH_PROG(VALGRIND_PATH, valgrind)
+
+ # Compile the instrumentation for valgrind only if the valgrind
+ # libraries are installed and the valgrind executable is found
+ if test "x$enable" = xyes &&
+ test "$have_valgrind_runtime" = yes &&
+ test -n "$VALGRIND_PATH" ;
+ then
+ AC_DEFINE(HAVE_VALGRIND, 1, [Define if valgrind should be used])
+ AC_MSG_NOTICE(using compile-time instrumentation for valgrind)
+ fi
+
+ AC_SUBST(VALGRIND_CFLAGS)
+ AC_SUBST(VALGRIND_LIBS)
+
+ AM_CONDITIONAL(HAVE_VALGRIND, test -n "$VALGRIND_PATH")
+])
diff --git a/salut/m4/tp-compiler-flag.m4 b/salut/m4/tp-compiler-flag.m4
new file mode 100644
index 000000000..fc05e9e17
--- /dev/null
+++ b/salut/m4/tp-compiler-flag.m4
@@ -0,0 +1,36 @@
+dnl A version of AS_COMPILER_FLAG that supports both C and C++.
+dnl Based on:
+
+dnl as-compiler-flag.m4 0.1.0
+dnl autostars m4 macro for detection of compiler flags
+dnl David Schleef <ds@schleef.org>
+dnl $Id: as-compiler-flag.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $
+
+dnl TP_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED])
+dnl Tries to compile with the given CFLAGS and CXXFLAGS.
+dnl
+dnl Runs ACTION-IF-ACCEPTED if the compiler for the currently selected
+dnl AC_LANG can compile with the flags, and ACTION-IF-NOT-ACCEPTED otherwise.
+
+AC_DEFUN([TP_COMPILER_FLAG],
+[
+ AC_MSG_CHECKING([to see if compiler understands $1])
+
+ save_CFLAGS="$CFLAGS"
+ save_CXXFLAGS="$CXXFLAGS"
+ CFLAGS="$CFLAGS $1"
+ CXXFLAGS="$CXXFLAGS $1"
+
+ AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ CFLAGS="$save_CFLAGS"
+ CXXFLAGS="$save_CXXFLAGS"
+
+ if test "X$flag_ok" = Xyes ; then
+ $2
+ true
+ else
+ $3
+ true
+ fi
+ AC_MSG_RESULT([$flag_ok])
+])
diff --git a/salut/m4/tp-compiler-warnings.m4 b/salut/m4/tp-compiler-warnings.m4
new file mode 100644
index 000000000..fab5dc898
--- /dev/null
+++ b/salut/m4/tp-compiler-warnings.m4
@@ -0,0 +1,40 @@
+dnl TP_COMPILER_WARNINGS(VARIABLE, WERROR_BY_DEFAULT, DESIRABLE, UNDESIRABLE)
+dnl $1 (VARIABLE): the variable to put flags into
+dnl $2 (WERROR_BY_DEFAULT): a command returning true if -Werror should be the
+dnl default
+dnl $3 (DESIRABLE): warning flags we want (e.g. all extra shadow)
+dnl $4 (UNDESIRABLE): warning flags we don't want (e.g.
+dnl missing-field-initializers unused-parameter)
+AC_DEFUN([TP_COMPILER_WARNINGS],
+[
+ AC_REQUIRE([AC_ARG_ENABLE])dnl
+ AC_REQUIRE([AC_HELP_STRING])dnl
+ AC_REQUIRE([TP_COMPILER_FLAG])dnl
+
+ tp_warnings=""
+ for tp_flag in $3; do
+ TP_COMPILER_FLAG([-W$tp_flag], [tp_warnings="$tp_warnings -W$tp_flag"])
+ done
+
+ tp_error_flags="-Werror"
+ TP_COMPILER_FLAG([-Werror], [tp_werror=yes], [tp_werror=no])
+
+ for tp_flag in $4; do
+ TP_COMPILER_FLAG([-Wno-$tp_flag],
+ [tp_warnings="$tp_warnings -Wno-$tp_flag"])
+ TP_COMPILER_FLAG([-Wno-error=$tp_flag],
+ [tp_error_flags="$tp_error_flags -Wno-error=$tp_flag"], [tp_werror=no])
+ done
+
+ AC_ARG_ENABLE([Werror],
+ AC_HELP_STRING([--disable-Werror],
+ [compile without -Werror (normally enabled in development builds)]),
+ tp_werror=$enableval, :)
+
+ if test "x$tp_werror" = xyes && $2; then
+ $1="$tp_warnings $tp_error_flags"
+ else
+ $1="$tp_warnings"
+ fi
+
+])
diff --git a/salut/plugins/Makefile.am b/salut/plugins/Makefile.am
new file mode 100644
index 000000000..3bef2ce0b
--- /dev/null
+++ b/salut/plugins/Makefile.am
@@ -0,0 +1,32 @@
+plugindir = $(libdir)/telepathy/salut-0
+
+# testing-only plugins
+noinst_LTLIBRARIES = \
+ test.la
+
+installable_plugins =
+ $(NULL)
+
+if ENABLE_PLUGINS
+plugin_LTLIBRARIES = $(installable_plugins)
+else
+# we still compile the plugin (just to make sure it compiles!) but we don't
+# install it
+noinst_LTLIBRARIES += $(installable_plugins)
+endif
+
+AM_LDFLAGS = -module -avoid-version -shared
+
+test_la_SOURCES = \
+ test.c \
+ test.h
+
+# because test.la is not installed, libtool will want to compile it as static
+# despite -shared (a convenience library), unless we also use -rpath
+test_la_LDFLAGS = $(AM_LDFLAGS) -rpath $(plugindir)
+
+AM_CFLAGS = $(ERROR_CFLAGS) \
+ -I $(top_srcdir) -I $(top_builddir) \
+ @GLIB_CFLAGS@ @TELEPATHY_GLIB_CFLAGS@ @WOCKY_CFLAGS@ \
+ -I $(top_srcdir)/salut -I $(top_builddir)/salut \
+ -I $(top_srcdir)/plugins
diff --git a/salut/plugins/test.c b/salut/plugins/test.c
new file mode 100644
index 000000000..9d47d949c
--- /dev/null
+++ b/salut/plugins/test.c
@@ -0,0 +1,147 @@
+#include "config.h"
+
+#include "test.h"
+
+#include <salut/plugin.h>
+
+#include "extensions/extensions.h"
+
+#define DEBUG(msg, ...) \
+ g_debug ("%s: " msg, G_STRFUNC, ##__VA_ARGS__)
+
+static void plugin_iface_init (
+ gpointer g_iface,
+ gpointer data);
+
+#define IFACE_TEST "org.freedesktop.Telepathy.Salut.Plugin.Test"
+
+static const gchar * const sidecar_interfaces[] = {
+ IFACE_TEST,
+ NULL
+};
+
+G_DEFINE_TYPE_WITH_CODE (TestPlugin, test_plugin, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_PLUGIN, plugin_iface_init);
+ )
+
+static void
+test_plugin_init (TestPlugin *object)
+{
+ DEBUG ("%p", object);
+}
+
+static void
+test_plugin_class_init (TestPluginClass *klass)
+{
+}
+
+static void
+initialize (SalutPlugin *plugin,
+ TpBaseConnectionManager *connection_manager)
+{
+ DEBUG ("%p on connection manager %p", plugin, connection_manager);
+
+ /* If you wanted to add another protocol you could do it here by
+ * creating the protocol object and then calling
+ * tp_base_connection_manager_add_protocol(). */
+}
+
+static GPtrArray *
+create_channel_managers (SalutPlugin *plugin,
+ TpBaseConnection *connection)
+{
+ DEBUG ("%p on connection %p", plugin, connection);
+
+ return NULL;
+}
+
+static void
+test_plugin_create_sidecar (
+ SalutPlugin *plugin,
+ const gchar *sidecar_interface,
+ SalutConnection *connection,
+ WockySession *session,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (plugin),
+ callback, user_data,
+ /* sic: all plugins share salut_plugin_create_sidecar_finish() so we
+ * need to use the same source tag.
+ */
+ salut_plugin_create_sidecar_async);
+ SalutSidecar *sidecar = NULL;
+
+ if (!tp_strdiff (sidecar_interface, IFACE_TEST))
+ {
+ sidecar = g_object_new (TEST_TYPE_SIDECAR, NULL);
+ }
+ else
+ {
+ g_simple_async_result_set_error (result, TP_ERRORS,
+ TP_ERROR_NOT_IMPLEMENTED, "'%s' not implemented", sidecar_interface);
+ }
+
+ if (sidecar != NULL)
+ g_simple_async_result_set_op_res_gpointer (result, sidecar, g_object_unref);
+
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+static void
+plugin_iface_init (
+ gpointer g_iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ SalutPluginInterface *iface = g_iface;
+
+ iface->api_version = SALUT_PLUGIN_CURRENT_VERSION;
+ iface->name = "Salut test plugin";
+ iface->version = PACKAGE_VERSION;
+
+ iface->sidecar_interfaces = sidecar_interfaces;
+ iface->create_sidecar = test_plugin_create_sidecar;
+ iface->initialize = initialize;
+ iface->create_channel_managers = create_channel_managers;
+}
+
+SalutPlugin *
+salut_plugin_create ()
+{
+ return g_object_new (test_plugin_get_type (), NULL);
+}
+
+/******************************
+ * TestSidecar implementation *
+ ******************************/
+
+static void sidecar_iface_init (
+ gpointer g_iface,
+ gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (TestSidecar, test_sidecar, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_SIDECAR, sidecar_iface_init);
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_SALUT_PLUGIN_TEST, NULL);
+ )
+
+static void
+test_sidecar_init (TestSidecar *object)
+{
+ DEBUG ("%p", object);
+}
+
+static void
+test_sidecar_class_init (TestSidecarClass *klass)
+{
+}
+
+static void sidecar_iface_init (
+ gpointer g_iface,
+ gpointer data)
+{
+ SalutSidecarInterface *iface = g_iface;
+
+ iface->interface = IFACE_TEST;
+ iface->get_immutable_properties = NULL;
+}
diff --git a/salut/plugins/test.h b/salut/plugins/test.h
new file mode 100644
index 000000000..a37d19a40
--- /dev/null
+++ b/salut/plugins/test.h
@@ -0,0 +1,65 @@
+#include <glib-object.h>
+
+/* plugin */
+typedef struct _TestPluginClass TestPluginClass;
+typedef struct _TestPlugin TestPlugin;
+
+struct _TestPluginClass
+{
+ GObjectClass parent;
+};
+
+struct _TestPlugin
+{
+ GObject parent;
+};
+
+GType test_plugin_get_type (void);
+
+#define TEST_TYPE_PLUGIN \
+ (test_plugin_get_type ())
+#define TEST_PLUGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TEST_TYPE_PLUGIN, TestPlugin))
+#define TEST_PLUGIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TEST_TYPE_PLUGIN, \
+ TestPluginClass))
+#define TEST_IS_PLUGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TEST_TYPE_PLUGIN))
+#define TEST_IS_PLUGIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TEST_TYPE_PLUGIN))
+#define TEST_PLUGIN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_PLUGIN, \
+ TestPluginClass))
+
+
+/* Sidecar */
+typedef struct _TestSidecarClass TestSidecarClass;
+typedef struct _TestSidecar TestSidecar;
+
+struct _TestSidecarClass
+{
+ GObjectClass parent;
+};
+
+struct _TestSidecar
+{
+ GObject parent;
+};
+
+GType test_sidecar_get_type (void);
+
+#define TEST_TYPE_SIDECAR \
+ (test_sidecar_get_type ())
+#define TEST_SIDECAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TEST_TYPE_SIDECAR, TestSidecar))
+#define TEST_SIDECAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TEST_TYPE_SIDECAR, \
+ TestSidecarClass))
+#define TEST_IS_SIDECAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TEST_TYPE_SIDECAR))
+#define TEST_IS_SIDECAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TEST_TYPE_SIDECAR))
+#define TEST_SIDECAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_SIDECAR, \
+ TestSidecarClass))
+
diff --git a/salut/rules/check.mak b/salut/rules/check.mak
new file mode 100644
index 000000000..2cf56f7e2
--- /dev/null
+++ b/salut/rules/check.mak
@@ -0,0 +1,121 @@
+LOOPS = 10
+CLEANFILES += valgrind.*.log
+
+# run any given test by running make test.check
+# if the test fails, run it again at at least debug level 2
+%.check: %
+ @$(TESTS_ENVIRONMENT) \
+ ./$* || \
+ $(TESTS_ENVIRONMENT) \
+ GIBBER_DEBUG=all \
+ ./$*
+
+# run any given test in a loop
+%.torture: %
+ @for i in `seq 1 $(LOOPS)`; do \
+ $(TESTS_ENVIRONMENT) \
+ ./$*; done
+
+# run any given test in an infinite loop
+%.forever: %
+ @while true; do \
+ $(TESTS_ENVIRONMENT) \
+ ./$* || break; done
+
+# valgrind any given test by running make test.valgrind
+%.valgrind: %
+ $(TESTS_ENVIRONMENT) \
+ CK_DEFAULT_TIMEOUT=360 \
+ G_SLICE=always-malloc \
+ G_DEBUG=gc-friendly \
+ libtool --mode=execute \
+ $(VALGRIND_PATH) -q \
+ $(foreach s,$(SUPPRESSIONS),--suppressions=$(s)) \
+ --tool=memcheck --leak-check=full --trace-children=yes \
+ --leak-resolution=high --num-callers=20 \
+ ./$* 2>&1 | tee "valgrind.$*.log"
+ @if grep "==" "valgrind.$*.log" > /dev/null 2>&1; then \
+ exit 1; \
+ fi
+
+# valgrind any given test and generate suppressions for it
+%.valgrind.gen-suppressions: %
+ $(TESTS_ENVIRONMENT) \
+ CK_DEFAULT_TIMEOUT=360 \
+ G_SLICE=always-malloc \
+ G_DEBUG=gc-friendly \
+ libtool --mode=execute \
+ $(VALGRIND_PATH) -q \
+ $(foreach s,$(SUPPRESSIONS),--suppressions=$(s)) \
+ --tool=memcheck --leak-check=full --trace-children=yes \
+ --leak-resolution=high --num-callers=20 \
+ --gen-suppressions=all \
+ ./$* 2>&1 | tee suppressions.log
+
+# valgrind any given test until failure by running make test.valgrind-forever
+%.valgrind-forever: %
+ @while $(MAKE) $*.valgrind; do \
+ true; done
+
+# gdb any given test by running make test.gdb
+%.gdb: %
+ $(TESTS_ENVIRONMENT) \
+ CK_FORK=no \
+ libtool --mode=execute \
+ gdb $*
+
+# torture tests
+torture: $(TESTS)
+ @echo "Torturing tests ..."
+ for i in `seq 1 $(LOOPS)`; do \
+ $(MAKE) check || \
+ (echo "Failure after $$i runs"; exit 1) || \
+ exit 1; \
+ done
+ @banner="All $(LOOPS) loops passed"; \
+ dashes=`echo "$$banner" | sed s/./=/g`; \
+ echo $$dashes; echo $$banner; echo $$dashes
+
+# forever tests
+forever: $(TESTS)
+ @echo "Forever tests ..."
+ while true; do \
+ $(MAKE) check || \
+ (echo "Failure"; exit 1) || \
+ exit 1; \
+ done
+
+# valgrind all tests
+valgrind: $(TESTS)
+ @echo "Valgrinding tests ..."
+ @failed=0; \
+ for t in $(filter-out $(VALGRIND_TESTS_DISABLE),$(TESTS)); do \
+ $(MAKE) $$t.valgrind; \
+ if test "$$?" -ne 0; then \
+ echo "Valgrind error for test $$t"; \
+ failed=`expr $$failed + 1`; \
+ whicht="$$whicht $$t"; \
+ fi; \
+ done; \
+ if test "$$failed" -ne 0; then \
+ echo "$$failed tests had leaks or errors under valgrind:"; \
+ echo "$$whicht"; \
+ false; \
+ fi
+
+help:
+ @echo "make check -- run all checks"
+ @echo "make torture -- run all checks $(LOOPS) times"
+ @echo "make (dir)/(test).check -- run the given check once"
+ @echo "make (dir)/(test).forever -- run the given check forever"
+ @echo "make (dir)/(test).torture -- run the given check $(LOOPS) times"
+ @echo
+ @echo "make (dir)/(test).gdb -- start up gdb for the given test"
+ @echo
+ @echo "make valgrind -- valgrind all tests"
+ @echo "make (dir)/(test).valgrind -- valgrind the given test"
+ @echo "make (dir)/(test).valgrind-forever -- valgrind the given test forever"
+ @echo "make (dir)/(test).valgrind.gen-suppressions -- generate suppressions"
+ @echo " and save to suppressions.log"
+ @echo "make inspect -- inspect all plugin features"
+
diff --git a/salut/rules/lcov.mak b/salut/rules/lcov.mak
new file mode 100644
index 000000000..d3a93696f
--- /dev/null
+++ b/salut/rules/lcov.mak
@@ -0,0 +1,29 @@
+COVERAGE_DIR=coverage
+
+if HAVE_LCOV
+# run lcov from scratch
+lcov:
+ $(MAKE) lcov-run
+ $(MAKE) lcov-report
+
+else
+lcov:
+ @echo "lcov not found or lacking --compat-libtool support"
+ @exit 1
+endif
+
+# reset run coverage tests
+lcov-run:
+ @-rm -rf $(COVERAGE_DIR)
+ @-find . -name "*.gcda" -exec rm {} \;
+ -$(MAKE) check
+
+# generate report based on current coverage data
+lcov-report:
+ @mkdir -p $(COVERAGE_DIR)
+ @lcov --quiet --compat-libtool --directory . --capture --output-file $(COVERAGE_DIR)/lcov.info
+ @lcov --output-file $(COVERAGE_DIR)/lcov.info.clean --remove $(COVERAGE_DIR)/lcov.info '*/_gen/*' '/usr/*'
+ @echo =================================================
+ @genhtml -t "$(PACKAGE_STRING)" -o $(COVERAGE_DIR) $(COVERAGE_DIR)/lcov.info.clean
+ @echo file://@abs_top_builddir@/$(COVERAGE_DIR)/index.html
+ @echo =================================================
diff --git a/salut/salut/Makefile.am b/salut/salut/Makefile.am
new file mode 100644
index 000000000..4c3ce25ad
--- /dev/null
+++ b/salut/salut/Makefile.am
@@ -0,0 +1,16 @@
+if ENABLE_PLUGIN_API
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = telepathy-salut.pc
+
+salutincludedir = $(includedir)/telepathy-salut-0/salut
+salutinclude_HEADERS = \
+ connection.h \
+ plugin.h \
+ protocol.h \
+ caps-channel-manager.h \
+ capabilities.h \
+ capability-set.h \
+ sidecar.h \
+ util.h
+
+endif
diff --git a/salut/salut/capabilities.h b/salut/salut/capabilities.h
new file mode 100644
index 000000000..faecc64fc
--- /dev/null
+++ b/salut/salut/capabilities.h
@@ -0,0 +1,73 @@
+/*
+ * capabilities.h - Connection.Interface.Capabilities constants and utilities
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GABBLE_CAPABILITIES__H__
+#define __GABBLE_CAPABILITIES__H__
+
+#include <glib-object.h>
+
+#include <salut/capability-set.h>
+
+/* Pseudo-capabilities for buggy or strange implementations, represented as
+ * strings starting with a character not allowed in XML (the ASCII beep :-) */
+#define QUIRK_PREFIX_CHAR '\x07'
+#define QUIRK_PREFIX "\x07"
+/* Gabble 0.7.x with 16 <= x < 29 omits @creator on <content/> */
+#define QUIRK_OMITS_CONTENT_CREATORS "\x07omits-content-creators"
+/* The Google Webmail client doesn't support some features */
+#define QUIRK_GOOGLE_WEBMAIL_CLIENT "\x07google-webmail-client"
+
+/* Some useful capability sets for Jingle etc. */
+const GabbleCapabilitySet *gabble_capabilities_get_legacy (void);
+const GabbleCapabilitySet *gabble_capabilities_get_any_audio (void);
+const GabbleCapabilitySet *gabble_capabilities_get_any_video (void);
+const GabbleCapabilitySet *gabble_capabilities_get_any_audio_video (void);
+const GabbleCapabilitySet *gabble_capabilities_get_any_google_av (void);
+const GabbleCapabilitySet *gabble_capabilities_get_any_jingle_av (void);
+const GabbleCapabilitySet *gabble_capabilities_get_any_transport (void);
+const GabbleCapabilitySet *gabble_capabilities_get_geoloc_notify (void);
+const GabbleCapabilitySet *gabble_capabilities_get_olpc_notify (void);
+
+/* XEP-0115 version 1.3:
+ *
+ * "The names of the feature bundles MUST NOT be used for semantic purposes:
+ * they are merely opaque identifiers"
+ *
+ * However, some old Jabber clients (e.g. Gabble 0.2) and various Google
+ * clients require the bundle names "voice-v1" and "video-v1". We keep these
+ * names for compatibility.
+ */
+#define BUNDLE_SHARE_V1 "share-v1"
+#define BUNDLE_VOICE_V1 "voice-v1"
+#define BUNDLE_VIDEO_V1 "video-v1"
+#define BUNDLE_PMUC_V1 "pmuc-v1"
+
+const GabbleCapabilitySet *gabble_capabilities_get_bundle_share_v1 (void);
+const GabbleCapabilitySet *gabble_capabilities_get_bundle_voice_v1 (void);
+const GabbleCapabilitySet *gabble_capabilities_get_bundle_video_v1 (void);
+
+/* Return the capabilities we always have */
+const GabbleCapabilitySet *gabble_capabilities_get_fixed_caps (void);
+
+void gabble_capabilities_init (gpointer conn);
+void gabble_capabilities_finalize (gpointer conn);
+
+#endif /* __GABBLE_CAPABILITIES__H__ */
+
diff --git a/salut/salut/capability-set.h b/salut/salut/capability-set.h
new file mode 100644
index 000000000..3f2152595
--- /dev/null
+++ b/salut/salut/capability-set.h
@@ -0,0 +1,86 @@
+/*
+ * capabilities-set.h - capabilities set API available to telepathy-gabble plugins
+ * Copyright (C) 2005-2010 Collabora Ltd.
+ * Copyright (C) 2005-2010 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef GABBLE_PLUGINS_CAPABILITIES_SET_H
+#define GABBLE_PLUGINS_CAPABILITIES_SET_H
+
+#include <glib-object.h>
+
+#include <wocky/wocky-node.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GabbleCapabilitySet:
+ *
+ * A set of capabilities.
+ */
+typedef struct _GabbleCapabilitySet GabbleCapabilitySet;
+
+GabbleCapabilitySet *gabble_capability_set_new (void);
+GabbleCapabilitySet *gabble_capability_set_new_from_stanza (
+ WockyNode *query_result);
+GabbleCapabilitySet *gabble_capability_set_copy (
+ const GabbleCapabilitySet *caps);
+void gabble_capability_set_update (GabbleCapabilitySet *target,
+ const GabbleCapabilitySet *source);
+void gabble_capability_set_add (GabbleCapabilitySet *caps,
+ const gchar *cap);
+gboolean gabble_capability_set_remove (GabbleCapabilitySet *caps,
+ const gchar *cap);
+void gabble_capability_set_exclude (GabbleCapabilitySet *caps,
+ const GabbleCapabilitySet *removed);
+void gabble_capability_set_intersect (GabbleCapabilitySet *target,
+ const GabbleCapabilitySet *source);
+gint gabble_capability_set_size (const GabbleCapabilitySet *caps);
+gboolean gabble_capability_set_has (const GabbleCapabilitySet *caps,
+ const gchar *cap);
+gboolean gabble_capability_set_has_one (const GabbleCapabilitySet *caps,
+ const GabbleCapabilitySet *alternatives);
+gboolean gabble_capability_set_at_least (const GabbleCapabilitySet *caps,
+ const GabbleCapabilitySet *query);
+gboolean gabble_capability_set_equals (const GabbleCapabilitySet *a,
+ const GabbleCapabilitySet *b);
+void gabble_capability_set_clear (GabbleCapabilitySet *caps);
+void gabble_capability_set_free (GabbleCapabilitySet *caps);
+void gabble_capability_set_foreach (const GabbleCapabilitySet *caps,
+ GFunc func, gpointer user_data);
+gchar *gabble_capability_set_dump (const GabbleCapabilitySet *caps,
+ const gchar *indent);
+gchar *gabble_capability_set_dump_diff (const GabbleCapabilitySet *old_caps,
+ const GabbleCapabilitySet *new_caps,
+ const gchar *indent);
+
+typedef gboolean (*GabbleCapabilitySetPredicate) (
+ const GabbleCapabilitySet *set, gconstpointer user_data);
+/* These functions are compatible with GabbleCapabilitySetPredicate;
+ * pass in the desired capabilities as the user_data */
+#define gabble_capability_set_predicate_equals \
+ ((GabbleCapabilitySetPredicate) gabble_capability_set_equals)
+#define gabble_capability_set_predicate_has \
+ ((GabbleCapabilitySetPredicate) gabble_capability_set_has)
+#define gabble_capability_set_predicate_has_one \
+ ((GabbleCapabilitySetPredicate) gabble_capability_set_has_one)
+#define gabble_capability_set_predicate_at_least \
+ ((GabbleCapabilitySetPredicate) gabble_capability_set_at_least)
+
+G_END_DECLS
+
+#endif
diff --git a/salut/salut/caps-channel-manager.h b/salut/salut/caps-channel-manager.h
new file mode 100644
index 000000000..4e0d561da
--- /dev/null
+++ b/salut/salut/caps-channel-manager.h
@@ -0,0 +1,108 @@
+/*
+ * caps-channel-manager.h - interface holding capabilities functions for
+ * channel managers
+ *
+ * Copyright (C) 2008 Collabora Ltd.
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef GABBLE_CAPS_CHANNEL_MANAGER_H
+#define GABBLE_CAPS_CHANNEL_MANAGER_H
+
+#include <glib-object.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/handle.h>
+
+#include "capabilities.h"
+
+G_BEGIN_DECLS
+
+#define GABBLE_TYPE_CAPS_CHANNEL_MANAGER \
+ (gabble_caps_channel_manager_get_type ())
+
+#define GABBLE_CAPS_CHANNEL_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GABBLE_TYPE_CAPS_CHANNEL_MANAGER, GabbleCapsChannelManager))
+
+#define GABBLE_IS_CAPS_CHANNEL_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GABBLE_TYPE_CAPS_CHANNEL_MANAGER))
+
+#define GABBLE_CAPS_CHANNEL_MANAGER_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE ((obj), \
+ GABBLE_TYPE_CAPS_CHANNEL_MANAGER, GabbleCapsChannelManagerIface))
+
+typedef struct _GabbleCapsChannelManager GabbleCapsChannelManager;
+typedef struct _GabbleCapsChannelManagerIface GabbleCapsChannelManagerIface;
+
+
+/* virtual methods */
+
+typedef void (*GabbleCapsChannelManagerGetContactCapsFunc) (
+ GabbleCapsChannelManager *manager,
+ TpHandle handle,
+ const GabbleCapabilitySet *caps,
+ GPtrArray *arr);
+
+typedef void (*GabbleCapsChannelManagerResetCapsFunc) (
+ GabbleCapsChannelManager *manager);
+
+typedef void (*GabbleCapsChannelManagerAddCapFunc) (
+ GabbleCapsChannelManager *manager,
+ GHashTable *cap,
+ GabbleCapabilitySet *cap_set);
+
+typedef void (*GabbleCapsChannelManagerRepresentClientFunc) (
+ GabbleCapsChannelManager *manager,
+ const gchar *client_name,
+ const GPtrArray *filters,
+ const gchar * const *cap_tokens,
+ GabbleCapabilitySet *cap_set,
+ GPtrArray *data_forms);
+
+void gabble_caps_channel_manager_reset_capabilities (
+ GabbleCapsChannelManager *caps_manager);
+
+void gabble_caps_channel_manager_get_contact_capabilities (
+ GabbleCapsChannelManager *caps_manager,
+ TpHandle handle,
+ const GabbleCapabilitySet *caps,
+ GPtrArray *arr);
+
+void gabble_caps_channel_manager_represent_client (
+ GabbleCapsChannelManager *caps_manager,
+ const gchar *client_name,
+ const GPtrArray *filters,
+ const gchar * const *cap_tokens,
+ GabbleCapabilitySet *cap_set,
+ GPtrArray *data_forms);
+
+struct _GabbleCapsChannelManagerIface {
+ GTypeInterface parent;
+
+ GabbleCapsChannelManagerResetCapsFunc reset_caps;
+ GabbleCapsChannelManagerGetContactCapsFunc get_contact_caps;
+ GabbleCapsChannelManagerRepresentClientFunc represent_client;
+
+ gpointer priv;
+};
+
+GType gabble_caps_channel_manager_get_type (void);
+
+G_END_DECLS
+
+#endif
diff --git a/salut/salut/connection.h b/salut/salut/connection.h
new file mode 100644
index 000000000..f36e13a3f
--- /dev/null
+++ b/salut/salut/connection.h
@@ -0,0 +1,53 @@
+/*
+ * connection.h - connection API available to telepathy-salut plugins
+ * Copyright © 2010 Collabora Ltd.
+ * Copyright © 2010 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SALUT_PLUGINS_CONNECTION_H
+#define SALUT_PLUGINS_CONNECTION_H
+
+#include <wocky/wocky-session.h>
+
+G_BEGIN_DECLS
+
+#define SALUT_TYPE_CONNECTION (salut_connection_get_type ())
+#define SALUT_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_CONNECTION, SalutConnection))
+#define SALUT_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_CONNECTION, \
+ SalutConnectionClass))
+#define SALUT_IS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_CONNECTION))
+#define SALUT_IS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_CONNECTION))
+#define SALUT_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_CONNECTION, \
+ SalutConnectionClass))
+
+typedef struct _SalutConnection SalutConnection;
+typedef struct _SalutConnectionClass SalutConnectionClass;
+
+GType salut_connection_get_type (void);
+
+WockySession * salut_connection_get_session (SalutConnection *connection);
+
+const gchar * salut_connection_get_name (SalutConnection *connection);
+
+G_END_DECLS
+
+#endif
diff --git a/salut/salut/plugin.h b/salut/salut/plugin.h
new file mode 100644
index 000000000..c0f2a0620
--- /dev/null
+++ b/salut/salut/plugin.h
@@ -0,0 +1,161 @@
+/*
+ * plugin.h — plugin API for telepathy-salut plugins
+ * Copyright © 2009-2011 Collabora Ltd.
+ * Copyright © 2009 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SALUT_PLUGINS_PLUGIN_H
+#define SALUT_PLUGINS_PLUGIN_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection-manager.h>
+#include <telepathy-glib/base-connection.h>
+
+#include <wocky/wocky-session.h>
+
+#include <salut/connection.h>
+#include <salut/sidecar.h>
+
+G_BEGIN_DECLS
+
+#define SALUT_TYPE_PLUGIN (salut_plugin_get_type ())
+#define SALUT_PLUGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), SALUT_TYPE_PLUGIN, SalutPlugin))
+#define SALUT_IS_PLUGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SALUT_TYPE_PLUGIN))
+#define SALUT_PLUGIN_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SALUT_TYPE_PLUGIN, \
+ SalutPluginInterface))
+
+typedef struct _SalutPlugin SalutPlugin;
+typedef struct _SalutPluginInterface SalutPluginInterface;
+
+typedef void (*SalutPluginCreateSidecarImpl) (
+ SalutPlugin *plugin,
+ const gchar *sidecar_interface,
+ SalutConnection *connection,
+ WockySession *session,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+/* The caller of this function takes ownership of the returned
+ * GPtrArray and the channel managers inside the array. This has the
+ * same semantics as TpBaseConnectionCreateChannelManagersImpl. */
+typedef GPtrArray * (*SalutPluginCreateChannelManagersImpl) (
+ SalutPlugin *plugin,
+ TpBaseConnection *connection);
+
+typedef void (*SalutPluginInitializeImpl) (
+ SalutPlugin *plugin,
+ TpBaseConnectionManager *connection_manager);
+
+#define SALUT_PLUGIN_CURRENT_VERSION 1
+
+struct _SalutPluginInterface
+{
+ GTypeInterface parent;
+
+ /**
+ * The version of the SalutPluginInterface struct design. The
+ * current version is at %SALUT_PLUGIN_CURRENT_VERSION.
+ */
+ guint api_version;
+
+ /**
+ * An arbitrary human-readable name identifying this plugin.
+ */
+ const gchar *name;
+
+ /**
+ * The plugin's version, conventionally a "."-separated sequence of
+ * numbers.
+ */
+ const gchar *version;
+
+ /**
+ * A %NULL-terminated array of strings listing the sidecar D-Bus interfaces
+ * implemented by this plugin.
+ */
+ const gchar * const *sidecar_interfaces;
+
+ /**
+ * An implementation of salut_plugin_create_sidecar().
+ */
+ SalutPluginCreateSidecarImpl create_sidecar;
+
+ /**
+ * An implementation of salut_plugin_initialize().
+ */
+ SalutPluginInitializeImpl initialize;
+
+ /**
+ * An implementation of salut_plugin_create_channel_managers().
+ */
+ SalutPluginCreateChannelManagersImpl create_channel_managers;
+
+ GCallback _padding[7];
+};
+
+GType salut_plugin_get_type (void);
+
+const gchar * salut_plugin_get_name (
+ SalutPlugin *plugin);
+const gchar * salut_plugin_get_version (
+ SalutPlugin *plugin);
+const gchar * const *salut_plugin_get_sidecar_interfaces (
+ SalutPlugin *plugin);
+
+gboolean salut_plugin_implements_sidecar (
+ SalutPlugin *plugin,
+ const gchar *sidecar_interface);
+
+void salut_plugin_create_sidecar_async (
+ SalutPlugin *plugin,
+ const gchar *sidecar_interface,
+ SalutConnection *connection,
+ WockySession *session,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+SalutSidecar * salut_plugin_create_sidecar_finish (
+ SalutPlugin *plugin,
+ GAsyncResult *result,
+ GError **error);
+
+void salut_plugin_initialize (
+ SalutPlugin *plugin,
+ TpBaseConnectionManager *connection_manager);
+
+GPtrArray * salut_plugin_create_channel_managers (
+ SalutPlugin *plugin,
+ TpBaseConnection *connection);
+
+/**
+ * salut_plugin_create:
+ *
+ * Prototype for the plugin entry point.
+ *
+ * Returns: a new instance of this plugin, which must not be %NULL.
+ */
+SalutPlugin * salut_plugin_create (void);
+
+typedef SalutPlugin * (*SalutPluginCreateImpl) (void);
+
+G_END_DECLS
+
+#endif
diff --git a/salut/salut/protocol.h b/salut/salut/protocol.h
new file mode 100644
index 000000000..5fc966347
--- /dev/null
+++ b/salut/salut/protocol.h
@@ -0,0 +1,91 @@
+/*
+ * protocol.h - SalutProtocol
+ * Copyright © 2007-2010 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SALUT_PROTOCOL_H
+#define SALUT_PROTOCOL_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-protocol.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutProtocol SalutProtocol;
+typedef struct _SalutProtocolPrivate SalutProtocolPrivate;
+typedef struct _SalutProtocolClass SalutProtocolClass;
+typedef struct _SalutProtocolClassPrivate SalutProtocolClassPrivate;
+
+struct _SalutProtocolClass {
+ TpBaseProtocolClass parent_class;
+
+ SalutProtocolClassPrivate *priv;
+};
+
+struct _SalutProtocol {
+ TpBaseProtocol parent;
+
+ SalutProtocolPrivate *priv;
+};
+
+GType salut_protocol_get_type (void);
+
+#define SALUT_TYPE_PROTOCOL \
+ (salut_protocol_get_type ())
+#define SALUT_PROTOCOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ SALUT_TYPE_PROTOCOL, \
+ SalutProtocol))
+#define SALUT_PROTOCOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ SALUT_TYPE_PROTOCOL, \
+ SalutProtocolClass))
+#define GABBLE_IS_JABBER_PROTOCOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ SALUT_TYPE_PROTOCOL))
+#define SALUT_PROTOCOL_GET_CLASS(klass) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ SALUT_TYPE_PROTOCOL, \
+ SalutProtocolClass))
+
+#define SALUT_PROTOCOL_LOCAL_XMPP_NAME "local-xmpp"
+#define SALUT_PROTOCOL_LOCAL_XMPP_ENGLISH_NAME "Link-local XMPP"
+#define SALUT_PROTOCOL_LOCAL_XMPP_ICON_NAME "im-" SALUT_PROTOCOL_LOCAL_XMPP_NAME
+
+/**
+ * salut_protocol_new:
+ * @backend_type: the #GType of the discovery client to use, or
+ * %G_TYPE_NONE for the avahi backend.
+ * @dnssd_name: The DNS-SD name to use (only used in avahi backend),
+ * or %NULL for the default avahi DNS-SD name.
+ * @protocol_name: Name of the protocol.
+ * @english_name: English name of the protocol.
+ * @icon_name: Icon name of the protocol.
+ *
+ * <!-- -->
+ *
+ * Returns: a new #TpBaseProtocol oject for the supplied arguments
+ */
+TpBaseProtocol *salut_protocol_new (GType backend_type,
+ const gchar *dnssd_name,
+ const gchar *protocol_name,
+ const gchar *english_name,
+ const gchar *icon_name);
+
+G_END_DECLS
+
+#endif
diff --git a/salut/salut/sidecar.h b/salut/salut/sidecar.h
new file mode 100644
index 000000000..1f5e78ffb
--- /dev/null
+++ b/salut/salut/sidecar.h
@@ -0,0 +1,67 @@
+/*
+ * sidecar.h — sidecar API available to telepathy-salut plugins
+ * Copyright © 2009-2011 Collabora Ltd.
+ * Copyright © 2009 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SALUT_PLUGINS_SIDECAR_H
+#define SALUT_PLUGINS_SIDECAR_H
+
+#include <glib-object.h>
+
+#include <salut/connection.h>
+
+G_BEGIN_DECLS
+
+#define SALUT_TYPE_SIDECAR (salut_sidecar_get_type ())
+#define SALUT_SIDECAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), SALUT_TYPE_SIDECAR, SalutSidecar))
+#define SALUT_IS_SIDECAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SALUT_TYPE_SIDECAR))
+#define SALUT_SIDECAR_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SALUT_TYPE_SIDECAR, \
+ SalutSidecarInterface))
+
+typedef struct _SalutSidecar SalutSidecar;
+typedef struct _SalutSidecarInterface SalutSidecarInterface;
+
+typedef GHashTable * (*SalutSidecarGetImmutablePropertiesImpl) (
+ SalutSidecar *);
+
+struct _SalutSidecarInterface
+{
+ GTypeInterface parent;
+
+ /**
+ * The D-Bus interface implemented by this sidecar.
+ */
+ const gchar *interface;
+
+ /**
+ * An implementation of salut_sidecar_get_immutable_properties().
+ */
+ SalutSidecarGetImmutablePropertiesImpl get_immutable_properties;
+};
+
+GType salut_sidecar_get_type (void);
+
+const gchar * salut_sidecar_get_interface (SalutSidecar *sidecar);
+GHashTable * salut_sidecar_get_immutable_properties (SalutSidecar *sidecar);
+
+G_END_DECLS
+
+#endif
diff --git a/salut/salut/telepathy-salut-uninstalled.pc.in b/salut/salut/telepathy-salut-uninstalled.pc.in
new file mode 100644
index 000000000..c5932a407
--- /dev/null
+++ b/salut/salut/telepathy-salut-uninstalled.pc.in
@@ -0,0 +1,15 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+abs_top_srcdir=@abs_top_srcdir@
+abs_top_builddir=@abs_top_builddir@
+libdir=@libdir@
+
+plugindir=${libdir}/telepathy/salut-0
+salutpath=${abs_top_builddir}/src/telepathy-salut
+
+Name: Telepathy-Salut (uninstalled)
+Description: Link-local XMPP backend for the Telepathy framework (uninstalled)
+Version: @VERSION@
+Requires: pkg-config >= 0.21
+Requires.private: glib-2.0 >= 2.16, gobject-2.0 >= 2.16, telepathy-glib >= 0.13.12
+Cflags: -I${abs_top_srcdir} -I${abs_top_srcdir}/lib/ext/wocky
diff --git a/salut/salut/telepathy-salut.pc.in b/salut/salut/telepathy-salut.pc.in
new file mode 100644
index 000000000..4292b1621
--- /dev/null
+++ b/salut/salut/telepathy-salut.pc.in
@@ -0,0 +1,15 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+libexecdir=@libexecdir@
+
+plugindir=${libdir}/telepathy/salut-0
+salutpath=${libexecdir}/telepathy-salut
+
+Name: Telepathy-Salut
+Description: Link-local XMPP backend for the Telepathy framework
+Version: @VERSION@
+Requires: pkg-config >= 0.21
+Requires.private: glib-2.0 >= 2.16, gobject-2.0 >= 2.16, telepathy-glib >= 0.13.12
+Cflags: -I${includedir}/telepathy-salut-0
diff --git a/salut/salut/util.h b/salut/salut/util.h
new file mode 100644
index 000000000..5746c5b24
--- /dev/null
+++ b/salut/salut/util.h
@@ -0,0 +1,29 @@
+/*
+ * util.h - Headers for Salut utility functions
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SALUT_UTIL_H
+#define SALUT_UTIL_H
+
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-session.h>
+
+void salut_send_ll_pep_event (WockySession *session,
+ WockyStanza *stanza);
+
+#endif /* SALUT_UTIL_H */
diff --git a/salut/src/Makefile.am b/salut/src/Makefile.am
new file mode 100644
index 000000000..6841adb1a
--- /dev/null
+++ b/salut/src/Makefile.am
@@ -0,0 +1,265 @@
+BUILT_SOURCES = \
+ signals-marshal.list \
+ signals-marshal.h \
+ signals-marshal.c \
+ enumtypes.h \
+ enumtypes.c
+
+# correctly clean the generated source files
+CLEANFILES = $(BUILT_SOURCES)
+
+dist-hook:
+ $(shell for x in $(BUILT_SOURCES); do rm -f $(distdir)/$$x ; done)
+
+libexec_PROGRAMS=telepathy-salut
+noinst_PROGRAMS = write-mgr-file
+
+CORE_SOURCES = \
+ symbol-hacks.c \
+ symbol-hacks.h \
+ capability-set.c \
+ $(top_srcdir)/salut/capabilities.h \
+ $(top_srcdir)/salut/capability-set.h \
+ caps-channel-manager.c \
+ $(top_srcdir)/salut/caps-channel-manager.h \
+ gabble_namespaces.h \
+ namespaces.h \
+ capabilities.c \
+ capabilities.h \
+ caps-hash.c \
+ caps-hash.h \
+ connection-manager.c \
+ connection-manager.h \
+ contact-manager.c \
+ contact-manager.h \
+ disco.c \
+ disco.h \
+ im-manager.c \
+ im-manager.h \
+ im-channel.c \
+ im-channel.h \
+ muc-manager.c \
+ muc-manager.h \
+ roomlist-manager.c \
+ roomlist-manager.h \
+ muc-channel.c \
+ ft-manager.c \
+ ft-manager.h \
+ file-transfer-channel.c \
+ file-transfer-channel.h \
+ muc-channel.h \
+ presence-cache.c \
+ presence-cache.h \
+ tubes-manager.c \
+ tubes-manager.h \
+ contact.h \
+ contact.c \
+ self.h \
+ self.c \
+ connection.c \
+ connection.h \
+ connection-contact-info.c \
+ connection-contact-info.h \
+ presence.h \
+ si-bytestream-manager.h \
+ si-bytestream-manager.c \
+ text-helper.c \
+ text-helper.h \
+ roomlist-channel.h \
+ roomlist-channel.c \
+ discovery-client.h \
+ discovery-client.c \
+ tube-dbus.h \
+ tube-dbus.c \
+ tube-iface.h \
+ tube-iface.c \
+ tube-stream.h \
+ tube-stream.c \
+ tubes-channel.h \
+ tubes-channel.c \
+ util.h \
+ util.c \
+ $(top_srcdir)/salut/util.h \
+ debug.c \
+ debug.h \
+ protocol.c \
+ $(top_srcdir)/salut/protocol.h \
+ plugin-loader.c \
+ plugin-loader.h \
+ $(top_srcdir)/salut/plugin.h \
+ plugin.c \
+ $(top_srcdir)/salut/connection.h \
+ $(top_srcdir)/salut/sidecar.h \
+ sidecar.c
+
+AVAHI_BACKEND_SOURCES = \
+ avahi-discovery-client.h \
+ avahi-discovery-client.c \
+ avahi-muc-manager.h \
+ avahi-muc-manager.c \
+ avahi-roomlist-manager.h \
+ avahi-roomlist-manager.c \
+ avahi-muc-channel.h \
+ avahi-muc-channel.c \
+ avahi-contact-manager.h \
+ avahi-contact-manager.c \
+ avahi-contact.h \
+ avahi-contact.c \
+ avahi-self.h \
+ avahi-self.c
+
+if ENABLE_OLPC
+ CORE_SOURCES += \
+ olpc-activity.h \
+ olpc-activity.c \
+ olpc-activity-manager.h \
+ olpc-activity-manager.c
+
+ AVAHI_BACKEND_SOURCES += \
+ avahi-olpc-activity-manager.h \
+ avahi-olpc-activity-manager.c \
+ avahi-olpc-activity.h \
+ avahi-olpc-activity.c
+endif
+
+DUMMY_BACKEND_SOURCES = \
+ dummy-discovery-client.h \
+ dummy-discovery-client.c
+
+SHA1_SOURCES = \
+ sha1/sha1-util.h \
+ sha1/sha1-util.c
+
+libsalut_convenience_la_SOURCES = $(CORE_SOURCES) $(BUILT_SOURCES) \
+ $(SHA1_SOURCES) $(AVAHI_BACKEND_SOURCES) $(DUMMY_BACKEND_SOURCES)
+
+write_mgr_file_SOURCES = write-mgr-file.c
+write_mgr_file_LDADD = libsalut-convenience.la \
+ $(top_builddir)/lib/gibber/libgibber.la \
+ $(top_builddir)/extensions/libsalut-extensions.la \
+ -ltelepathy-glib
+telepathy_salut_SOURCES = \
+ salut.c
+
+telepathy_salut_LDFLAGS = -export-dynamic
+
+# Coding style checks
+check_c_sources = \
+ $(telepathy_salut_SOURCES) \
+ $(CORE_SOURCES) \
+ $(AVAHI_BACKEND_SOURCES) \
+ $(DUMMY_BACKEND_SOURCES) \
+ $(write_mgr_file_SOURCES)
+
+include $(top_srcdir)/tools/check-coding-style.mk
+check-local: check-coding-style
+
+# TELEPATHY_GLIB_LIBS is only here so compiling against a static lib succeeds
+telepathy_salut_android_libs = libsalut-convenience.la \
+ -lgibber-salut \
+ $(top_builddir)/extensions/libsalut-extensions.la \
+ -ltelepathy-glib
+
+telepathy_salut_LDADD = libsalut-convenience.la \
+ $(top_builddir)/lib/gibber/libgibber.la \
+ $(top_builddir)/extensions/libsalut-extensions.la \
+ -ltelepathy-glib
+
+noinst_LTLIBRARIES = libsalut-convenience.la
+
+AM_CFLAGS = \
+ -I $(top_srcdir) -I $(top_builddir) \
+ -I $(top_srcdir)/lib -I $(top_builddir)/lib \
+ -I $(top_srcdir)/salut \
+ -DG_LOG_DOMAIN=\"salut\" \
+ -DPLUGIN_DIR=\"$(libdir)/telepathy/salut-0\" \
+ $(ERROR_CFLAGS) \
+ $(GCOV_CFLAGS) \
+ @LIBXML2_CFLAGS@ \
+ @GLIB_CFLAGS@ \
+ @GMODULE_CFLAGS@ \
+ @WOCKY_CFLAGS@ \
+ @DBUS_CFLAGS@ \
+ @AVAHI_CFLAGS@ \
+ @TELEPATHY_GLIB_CFLAGS@ \
+ @UUID_CFLAGS@ \
+ @LIBSOUP_CFLAGS@ \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(GCOV_LIBS) \
+ @LIBXML2_LIBS@ \
+ @GLIB_LIBS@ \
+ @GMODULE_LIBS@ \
+ @WOCKY_LIBS@ \
+ @DBUS_LIBS@ \
+ @AVAHI_LIBS@ \
+ @TELEPATHY_GLIB_LIBS@ \
+ @UUID_LIBS@ \
+ @LIBSOUP_LIBS@ \
+ $(NULL)
+
+# Teach it how to make libgibber.la
+$(top_builddir)/lib/gibber/libgibber.la:
+ ${MAKE} -C $(top_builddir)/lib/gibber libgibber.la
+
+.PHONY: $(top_builddir)/lib/gibber/libgibber.la
+
+signals-marshal.list: $(CORE_SOURCES) $(AVAHI_BACKEND_SOURCES) $(DUMMY_BACKEND_SOURCES) Makefile.am
+ $(AM_V_GEN)( cd $(srcdir) && \
+ sed -n -e 's/.*salut_signals_marshal_\([[:upper:][:digit:]]*__[[:upper:][:digit:]_]*\).*/\1/p' \
+ $(CORE_SOURCES) $(AVAHI_BACKEND_SOURCES) $(DUMMY_BACKEND_SOURCES) ) \
+ | sed -e 's/__/:/' -e 'y/_/,/' | sort -u > $@.tmp
+ if cmp -s $@.tmp $@; then \
+ rm $@.tmp; \
+ else \
+ mv $@.tmp $@; \
+ fi
+
+signals-marshal.h: signals-marshal.list Makefile.am
+ $(AM_V_GEN)glib-genmarshal --header --prefix=salut_signals_marshal $< > $@
+
+signals-marshal.c: signals-marshal.list Makefile.am
+ $(AM_V_GEN){ echo '#include "signals-marshal.h"' && \
+ glib-genmarshal --body --prefix=salut_signals_marshal $< ; \
+ } > $@
+
+# rules for making the glib enum objects
+enumtype_sources = \
+ $(top_srcdir)/src/presence.h
+
+enumtypes.h: $(enumtype_sources) Makefile.in
+ $(AM_V_GEN)glib-mkenums \
+ --fhead "#ifndef __SALUT_ENUM_TYPES_H__\n#define __SALUT_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+ --fprod "/* enumerations from \"@filename@\" */\n" \
+ --vhead "GType @enum_name@_get_type (void);\n#define SALUT_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \
+ --ftail "G_END_DECLS\n\n#endif /* __SALUT_ENUM_TYPES_H__ */" \
+ $(enumtype_sources) > $@
+
+enumtypes.c: $(enumtype_sources) Makefile.in
+ $(AM_V_GEN)glib-mkenums \
+ --fhead "#include <$*.h>" \
+ --fprod "\n/* enumerations from \"@filename@\" */\n#include \"@filename@\"" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \
+ $(enumtype_sources) > $@
+
+Android.mk: Makefile.am $(BUILT_SOURCES)
+ androgenizer -:PROJECT telepathy-salut -:STATIC salut-convenience \
+ -:TAGS eng debug \
+ -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+ -:SOURCES $(libsalut_convenience_la_SOURCES) \
+ -:CFLAGS $(DEFS) $(CFLAGS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CFLAGS) \
+ -:CPPFLAGS $(CPPFLAGS) $(AM_CPPFLAGS) \
+ -:SHARED telepathy-salut \
+ -:TAGS eng debug \
+ -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+ -:SOURCES $(telepathy_salut_SOURCES) \
+ -:CFLAGS $(DEFS) $(CFLAGS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CFLAGS) \
+ -:CPPFLAGS $(CPPFLAGS) $(AM_CPPFLAGS) -DBUILD_AS_ANDROID_SERVICE \
+ -:LDFLAGS $(telepathy_salut_android_libs) $(AM_LDFLAGS) \
+ -:LIBFILTER_STATIC salut-convenience \
+ > $@
diff --git a/salut/src/avahi-contact-manager.c b/salut/src/avahi-contact-manager.c
new file mode 100644
index 000000000..fd736da2c
--- /dev/null
+++ b/salut/src/avahi-contact-manager.c
@@ -0,0 +1,336 @@
+/*
+ * avahi-contact-manager.c - Source for SalutAvahiContactManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+
+#include <avahi-gobject/ga-service-browser.h>
+#include <avahi-gobject/ga-enums.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+#include "avahi-contact-manager.h"
+#include "avahi-contact.h"
+
+G_DEFINE_TYPE (SalutAvahiContactManager, salut_avahi_contact_manager,
+ SALUT_TYPE_CONTACT_MANAGER);
+
+/* properties */
+enum
+{
+ PROP_DISCOVERY_CLIENT = 1,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutAvahiContactManagerPrivate SalutAvahiContactManagerPrivate;
+
+struct _SalutAvahiContactManagerPrivate
+{
+ SalutAvahiDiscoveryClient *discovery_client;
+ GaServiceBrowser *presence_browser;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_AVAHI_CONTACT_MANAGER_GET_PRIVATE(obj) \
+ ((SalutAvahiContactManagerPrivate *) \
+ ((SalutAvahiContactManager *) obj)->priv)
+
+
+static void
+salut_avahi_contact_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiContactManager *chan = SALUT_AVAHI_CONTACT_MANAGER (object);
+ SalutAvahiContactManagerPrivate *priv =
+ SALUT_AVAHI_CONTACT_MANAGER_GET_PRIVATE (chan);
+
+ switch (property_id) {
+ case PROP_DISCOVERY_CLIENT:
+ g_value_set_object (value, priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_contact_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiContactManager *chan = SALUT_AVAHI_CONTACT_MANAGER (object);
+ SalutAvahiContactManagerPrivate *priv =
+ SALUT_AVAHI_CONTACT_MANAGER_GET_PRIVATE (chan);
+
+ switch (property_id) {
+ case PROP_DISCOVERY_CLIENT:
+ priv->discovery_client = g_value_get_object (value);
+ g_object_ref (priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_contact_manager_init (SalutAvahiContactManager *self)
+{
+ SalutAvahiContactManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_AVAHI_CONTACT_MANAGER, SalutAvahiContactManagerPrivate);
+
+ self->priv = priv;
+
+ priv->discovery_client = NULL;
+}
+
+static SalutContact *
+salut_avahi_contact_manager_create_contact (SalutContactManager *mgr,
+ const gchar *name)
+{
+ SalutAvahiContactManager *self = SALUT_AVAHI_CONTACT_MANAGER (mgr);
+ SalutAvahiContactManagerPrivate *priv =
+ SALUT_AVAHI_CONTACT_MANAGER_GET_PRIVATE (self);
+
+ return SALUT_CONTACT (salut_avahi_contact_new (mgr->connection,
+ name, priv->discovery_client));
+}
+
+static void
+browser_found (GaServiceBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain,
+ GaLookupResultFlags flags,
+ SalutAvahiContactManager *self)
+{
+ SalutContactManager *mgr = SALUT_CONTACT_MANAGER (self);
+ SalutContact *contact;
+ const char *contact_name = name;
+
+ if (flags & AVAHI_LOOKUP_RESULT_OUR_OWN)
+ return;
+
+ /* FIXME: For now we assume name is unique on the lan */
+ contact = g_hash_table_lookup (mgr->contacts, contact_name);
+ if (contact == NULL)
+ {
+ contact = salut_avahi_contact_manager_create_contact (mgr, contact_name);
+ salut_contact_manager_contact_created (mgr, contact);
+ }
+ else if (!salut_avahi_contact_has_services (SALUT_AVAHI_CONTACT (contact)))
+ {
+ /* We keep a ref on the contact as long it has services */
+ g_object_ref (contact);
+ }
+
+ if (!salut_avahi_contact_add_service (SALUT_AVAHI_CONTACT (contact),
+ interface, protocol, name, type, domain))
+ {
+ /* If we couldn't add the server check the refcounting */
+ if (!salut_avahi_contact_has_services (SALUT_AVAHI_CONTACT (contact)))
+ g_object_unref (contact);
+ }
+ else
+ {
+ WockyContactFactory *contact_factory;
+
+ contact_factory = wocky_session_get_contact_factory (
+ mgr->connection->session);
+
+ wocky_contact_factory_add_ll_contact (contact_factory,
+ WOCKY_LL_CONTACT (contact));
+ }
+}
+
+static void
+browser_removed (GaServiceBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain,
+ GaLookupResultFlags flags,
+ SalutAvahiContactManager *self)
+{
+ SalutContactManager *mgr = SALUT_CONTACT_MANAGER (self);
+ SalutContact *contact;
+ const char *contact_name = name;
+
+ DEBUG("Browser removed for %s", name);
+
+ contact = g_hash_table_lookup (mgr->contacts, contact_name);
+ if (contact != NULL)
+ {
+ salut_avahi_contact_remove_service (SALUT_AVAHI_CONTACT (contact),
+ interface, protocol, name, type, domain);
+ if (!salut_avahi_contact_has_services (SALUT_AVAHI_CONTACT (contact)))
+ g_object_unref (contact);
+
+ }
+ else
+ {
+ DEBUG ("Unknown contact removed from service browser");
+ }
+}
+
+static void
+browser_failed (GaServiceBrowser *browser,
+ GError *error,
+ SalutAvahiContactManager *self)
+{
+ /* FIXME proper error handling */
+ g_warning ("browser failed -> %s", error->message);
+}
+
+static void
+browser_all_for_now (GaServiceBrowser *browser,
+ SalutAvahiContactManager *self)
+{
+ g_signal_emit_by_name (self, "all-for-now");
+}
+
+static gboolean
+salut_avahi_contact_manager_start (SalutContactManager *mgr,
+ GError **error)
+{
+ SalutAvahiContactManager *self = SALUT_AVAHI_CONTACT_MANAGER (mgr);
+ SalutAvahiContactManagerPrivate *priv =
+ SALUT_AVAHI_CONTACT_MANAGER_GET_PRIVATE (self);
+
+ g_signal_connect (priv->presence_browser, "new-service",
+ G_CALLBACK (browser_found), mgr);
+ g_signal_connect (priv->presence_browser, "removed-service",
+ G_CALLBACK (browser_removed), mgr);
+ g_signal_connect (priv->presence_browser, "failure",
+ G_CALLBACK (browser_failed), mgr);
+ g_signal_connect (priv->presence_browser, "all-for-now",
+ G_CALLBACK (browser_all_for_now), mgr);
+
+ if (!ga_service_browser_attach (priv->presence_browser,
+ priv->discovery_client->avahi_client, error))
+ {
+ DEBUG ("browser attach failed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+salut_avahi_contact_manager_dispose_contact (SalutContactManager *mgr,
+ SalutContact *contact)
+{
+ if (salut_avahi_contact_has_services (SALUT_AVAHI_CONTACT (contact)))
+ {
+ /* We reffed this contact as it has services */
+ g_object_unref (contact);
+ }
+}
+
+static void
+salut_avahi_contact_manager_close_all (SalutContactManager *mgr)
+{
+ SalutAvahiContactManager *self = SALUT_AVAHI_CONTACT_MANAGER (mgr);
+ SalutAvahiContactManagerPrivate *priv =
+ SALUT_AVAHI_CONTACT_MANAGER_GET_PRIVATE (self);
+
+ if (priv->presence_browser != NULL)
+ {
+ g_object_unref (priv->presence_browser);
+ priv->presence_browser = NULL;
+ }
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ priv->discovery_client = NULL;
+ }
+}
+
+static void
+salut_avahi_contact_manager_constructed (GObject *object)
+{
+ SalutAvahiContactManager *self = SALUT_AVAHI_CONTACT_MANAGER (object);
+ SalutAvahiContactManagerPrivate *priv =
+ SALUT_AVAHI_CONTACT_MANAGER_GET_PRIVATE (self);
+ const gchar *dnssd_name = salut_avahi_discovery_client_get_dnssd_name (
+ priv->discovery_client);
+
+ priv->presence_browser = ga_service_browser_new ((gchar *) dnssd_name);
+
+ if (G_OBJECT_CLASS (salut_avahi_contact_manager_parent_class)->constructed)
+ G_OBJECT_CLASS (salut_avahi_contact_manager_parent_class)->constructed (object);
+}
+
+static void
+salut_avahi_contact_manager_class_init (
+ SalutAvahiContactManagerClass *salut_avahi_contact_manager_class) {
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_contact_manager_class);
+ SalutContactManagerClass *contact_manager_class = SALUT_CONTACT_MANAGER_CLASS (
+ salut_avahi_contact_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_avahi_contact_manager_class,
+ sizeof (SalutAvahiContactManagerPrivate));
+
+ object_class->get_property = salut_avahi_contact_manager_get_property;
+ object_class->set_property = salut_avahi_contact_manager_set_property;
+ object_class->constructed = salut_avahi_contact_manager_constructed;
+
+ contact_manager_class->start = salut_avahi_contact_manager_start;
+ contact_manager_class->create_contact =
+ salut_avahi_contact_manager_create_contact;
+ contact_manager_class->dispose_contact =
+ salut_avahi_contact_manager_dispose_contact;
+ contact_manager_class->close_all = salut_avahi_contact_manager_close_all;
+
+ param_spec = g_param_spec_object (
+ "discovery-client",
+ "SalutAvahiDiscoveryClient object",
+ "The Salut Avahi Discovery client associated with this muc channel",
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DISCOVERY_CLIENT,
+ param_spec);
+}
+
+SalutAvahiContactManager *
+salut_avahi_contact_manager_new (SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client)
+{
+ return g_object_new (SALUT_TYPE_AVAHI_CONTACT_MANAGER,
+ "connection", connection,
+ "discovery-client", discovery_client,
+ NULL);
+}
diff --git a/salut/src/avahi-contact-manager.h b/salut/src/avahi-contact-manager.h
new file mode 100644
index 000000000..147543aca
--- /dev/null
+++ b/salut/src/avahi-contact-manager.h
@@ -0,0 +1,67 @@
+/*
+ * avahi-contact-manager.h - Header for SalutAvahiContactManager
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#ifndef __SALUT_AVAHI_CONTACT_MANAGER_H__
+#define __SALUT_AVAHI_CONTACT_MANAGER_H__
+
+#include <glib-object.h>
+#include <avahi-gobject/ga-client.h>
+
+#include "contact-manager.h"
+#include "connection.h"
+#include "contact.h"
+#include "avahi-discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiContactManager SalutAvahiContactManager;
+typedef struct _SalutAvahiContactManagerClass SalutAvahiContactManagerClass;
+
+struct _SalutAvahiContactManagerClass {
+ SalutContactManagerClass parent_class;
+};
+
+struct _SalutAvahiContactManager {
+ SalutContactManager parent;
+
+ gpointer priv;
+};
+
+
+GType salut_avahi_contact_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_CONTACT_MANAGER \
+ (salut_avahi_contact_manager_get_type ())
+#define SALUT_AVAHI_CONTACT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_CONTACT_MANAGER, SalutAvahiContactManager))
+#define SALUT_AVAHI_CONTACT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_CONTACT_MANAGER, SalutAvahiContactManagerClass))
+#define SALUT_IS_AVAHI_CONTACT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_CONTACT_MANAGER))
+#define SALUT_IS_AVAHI_CONTACT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_CONTACT_MANAGER))
+#define SALUT_AVAHI_CONTACT_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_CONTACT_MANAGER, SalutAvahiContactManagerClass))
+
+SalutAvahiContactManager * salut_avahi_contact_manager_new (
+ SalutConnection *connection, SalutAvahiDiscoveryClient *discovery_client);
+
+#endif /* #ifndef __SALUT_AVAHI_CONTACT_MANAGER_H__*/
diff --git a/salut/src/avahi-contact.c b/salut/src/avahi-contact.c
new file mode 100644
index 000000000..b8e0769fb
--- /dev/null
+++ b/salut/src/avahi-contact.c
@@ -0,0 +1,890 @@
+/*
+ * avahi-contact.c - Source for SalutAvahiContact
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include <avahi-gobject/ga-service-resolver.h>
+#include <avahi-gobject/ga-record-browser.h>
+#include <avahi-common/address.h>
+#include <avahi-common/defs.h>
+#include <avahi-common/malloc.h>
+
+#include "avahi-contact.h"
+
+#include <telepathy-glib/channel-factory-iface.h>
+#include <telepathy-glib/interfaces.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+#define DEBUG_CONTACT(contact, format, ...) G_STMT_START { \
+ DEBUG ("Contact %s: " format, \
+ SALUT_CONTACT (contact)->name, ##__VA_ARGS__); \
+} G_STMT_END
+
+#define DEBUG_RESOLVER(contact, resolver, format, ...) G_STMT_START { \
+ gchar *_name; \
+ gchar *_type; \
+ gint _interface; \
+ gint _protocol; \
+ \
+ g_object_get (G_OBJECT(resolver), \
+ "name", &_name, \
+ "type", &_type, \
+ "interface", &_interface, \
+ "protocol", &_protocol, \
+ NULL \
+ ); \
+ \
+ DEBUG_CONTACT (contact, "Resolver (%s %s intf: %d proto: %d): " format, \
+ _name, _type, _interface, _protocol, ##__VA_ARGS__); \
+ \
+ g_free (_name); \
+ g_free (_type); \
+} G_STMT_END
+
+#define PRESENCE_TIMEOUT (1200 * 1000)
+
+G_DEFINE_TYPE (SalutAvahiContact, salut_avahi_contact,
+ SALUT_TYPE_CONTACT);
+
+/* properties */
+enum {
+ PROP_CLIENT = 1,
+ LAST_PROP
+};
+
+/* private structure */
+typedef struct _SalutAvahiContactPrivate SalutAvahiContactPrivate;
+
+struct _SalutAvahiContactPrivate
+{
+ SalutAvahiDiscoveryClient *discovery_client;
+ GSList *resolvers;
+ guint presence_resolver_failed_timer;
+ GaRecordBrowser *record_browser;
+
+ gboolean dispose_has_run;
+};
+
+static void
+salut_avahi_contact_init (SalutAvahiContact *self)
+{
+ SalutAvahiContactPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_AVAHI_CONTACT, SalutAvahiContactPrivate);
+
+ self->priv = priv;
+
+ priv->resolvers = NULL;
+}
+
+static void
+salut_avahi_contact_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiContact *self = SALUT_AVAHI_CONTACT (object);
+ SalutAvahiContactPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_contact_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiContact *self = SALUT_AVAHI_CONTACT (object);
+ SalutAvahiContactPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ priv->discovery_client = g_value_get_object (value);
+ g_object_ref (priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+_avahi_address_to_sockaddr (AvahiAddress *address,
+ guint16 port,
+ AvahiIfIndex index_,
+ struct sockaddr *sockaddr)
+{
+ switch (address->proto)
+ {
+ case AVAHI_PROTO_INET:
+ {
+ struct sockaddr_in *sockaddr4 = (struct sockaddr_in *) sockaddr;
+ sockaddr4->sin_family = AF_INET;
+ sockaddr4->sin_port = htons (port);
+ /* ->address is already in network byte order */
+ sockaddr4->sin_addr.s_addr = address->data.ipv4.address;
+ break;
+ }
+ case AVAHI_PROTO_INET6:
+ {
+ struct sockaddr_in6 *sockaddr6 = (struct sockaddr_in6 *) sockaddr;
+ sockaddr6->sin6_family = AF_INET6;
+ sockaddr6->sin6_port = htons (port);
+ memcpy (sockaddr6->sin6_addr.s6_addr, address->data.ipv6.address, 16);
+
+ sockaddr6->sin6_flowinfo = 0;
+ sockaddr6->sin6_scope_id = index_;
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+_add_address (GaServiceResolver *resolver,
+ GArray *addresses)
+{
+ salut_contact_address_t s_address;
+ AvahiAddress address;
+ guint16 port;
+ AvahiIfIndex ifindex;
+
+ g_object_get (resolver, "interface", &ifindex, NULL);
+ if (ga_service_resolver_get_address (resolver, &address, &port))
+ {
+ _avahi_address_to_sockaddr (&address, port, ifindex,
+ (struct sockaddr *) &s_address.address);
+ g_array_append_val (addresses, s_address);
+ }
+}
+
+static GArray *
+salut_avahi_contact_get_addresses (SalutContact *contact)
+{
+ SalutAvahiContact *self = SALUT_AVAHI_CONTACT (contact);
+ SalutAvahiContactPrivate *priv = self->priv;
+ GArray *addresses;
+
+ addresses = g_array_sized_new (TRUE, TRUE, sizeof (salut_contact_address_t),
+ g_slist_length (priv->resolvers));
+ g_slist_foreach (priv->resolvers, (GFunc) _add_address, addresses);
+
+ return addresses;
+}
+
+static gchar *
+salut_avahi_contact_dup_jid (WockyContact *contact)
+{
+ SalutContact *self = SALUT_CONTACT (contact);
+
+ return g_strdup (self->name);
+}
+
+static GList *
+salut_avahi_contact_ll_get_addresses (WockyLLContact *contact)
+{
+ SalutAvahiContact *self = SALUT_AVAHI_CONTACT (contact);
+ SalutAvahiContactPrivate *priv = self->priv;
+ /* omg, GQueue! */
+ GQueue queue = G_QUEUE_INIT;
+ GSList *l;
+
+ for (l = priv->resolvers; l != NULL; l = l->next)
+ {
+ GaServiceResolver *resolver = l->data;
+ AvahiAddress address;
+ guint16 port;
+
+ if (ga_service_resolver_get_address (resolver, &address, &port))
+ {
+ GInetAddress *addr;
+ GSocketAddress *socket_address;
+
+ if (address.proto == AVAHI_PROTO_INET)
+ {
+ addr = g_inet_address_new_from_bytes (
+ (guint8 *) &(address.data.ipv4.address), G_SOCKET_FAMILY_IPV4);
+ }
+ else if (address.proto == AVAHI_PROTO_INET6)
+ {
+ addr = g_inet_address_new_from_bytes (
+ (guint8 *) &(address.data.ipv6.address), G_SOCKET_FAMILY_IPV6);
+ }
+ else
+ g_assert_not_reached ();
+
+ socket_address = g_inet_socket_address_new (addr, port);
+ g_object_unref (addr);
+
+ g_queue_push_tail (&queue, socket_address);
+ }
+ }
+
+ return queue.head;
+}
+
+static gint
+_compare_address (GaServiceResolver *resolver,
+ struct sockaddr *addr_b)
+{
+ union {
+ struct sockaddr_storage storage;
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ } addr_a;
+ AvahiIfIndex ifindex;
+ AvahiAddress address;
+ uint16_t port;
+
+ g_object_get (resolver, "interface", &ifindex, NULL);
+ if (!ga_service_resolver_get_address (resolver, &address, &port))
+ return -1;
+
+ _avahi_address_to_sockaddr (&address, port, ifindex,
+ (struct sockaddr *) &addr_a);
+
+ if (addr_a.storage.ss_family != addr_b->sa_family)
+ return -1;
+
+ switch (addr_a.storage.ss_family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in *b4 = (struct sockaddr_in *) addr_b;
+ return b4->sin_addr.s_addr - addr_a.in.sin_addr.s_addr;
+ }
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *b6 = (struct sockaddr_in6 *) addr_b;
+ /* FIXME should we compare the scope_id too ? */
+ return memcmp (addr_a.in6.sin6_addr.s6_addr,
+ b6->sin6_addr.s6_addr, 16);
+ }
+ default:
+ g_assert_not_reached ();
+ }
+
+ return 0;
+}
+
+static gboolean
+salut_avahi_contact_has_address (SalutContact *contact,
+ struct sockaddr *address,
+ guint size)
+{
+ SalutAvahiContact *self = SALUT_AVAHI_CONTACT (contact);
+ SalutAvahiContactPrivate *priv = self->priv;
+
+ return (g_slist_find_custom (priv->resolvers, address,
+ (GCompareFunc) _compare_address) != NULL);
+}
+
+static void
+salut_avahi_contact_avatar_request_flush (SalutAvahiContact *self,
+ guint8 *data,
+ gsize size)
+{
+ SalutAvahiContactPrivate *priv = self->priv;
+
+ if (priv->record_browser != NULL)
+ g_object_unref (priv->record_browser);
+ priv->record_browser = NULL;
+
+ salut_contact_avatar_request_flush (SALUT_CONTACT (self), data, size);
+}
+
+static void
+salut_contact_avatar_all_for_now (GaRecordBrowser *browser,
+ SalutAvahiContact *self)
+{
+ DEBUG ("All for now for resolving %s's record", SALUT_CONTACT (self)->name);
+ salut_avahi_contact_avatar_request_flush (self, NULL, 0);
+}
+
+static void
+salut_contact_avatar_failure (GaRecordBrowser *browser,
+ GError *error,
+ SalutAvahiContact *self)
+{
+
+ DEBUG ("Resolving record for %s failed: %s", SALUT_CONTACT(self)->name,
+ error->message);
+
+ salut_avahi_contact_avatar_request_flush (self, NULL, 0);
+}
+
+static void
+salut_contact_avatar_found (GaRecordBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ gchar *name,
+ guint16 clazz,
+ guint16 type,
+ guint8 *rdata,
+ gsize rdata_size,
+ AvahiLookupFlags flags,
+ SalutAvahiContact *self)
+{
+ DEBUG ("Found avatar for %s for size %" G_GSIZE_FORMAT,
+ SALUT_CONTACT (self)->name, rdata_size);
+
+ if (rdata_size <= 0)
+ salut_avahi_contact_avatar_request_flush (self, NULL, 0);
+ else
+ salut_avahi_contact_avatar_request_flush (self, rdata, rdata_size);
+}
+
+static void
+salut_avahi_contact_retrieve_avatar (SalutContact *contact)
+{
+ SalutAvahiContact *self = SALUT_AVAHI_CONTACT (contact);
+ SalutAvahiContactPrivate *priv = self->priv;
+ gchar *name;
+ GError *error = NULL;
+ const gchar *dnssd_name;
+
+ if (priv->record_browser != NULL)
+ {
+ g_object_unref (priv->record_browser);
+ priv->record_browser = NULL;
+ }
+
+ if (contact->avatar_token == NULL)
+ {
+ salut_avahi_contact_avatar_request_flush (self, NULL, 0);
+ return;
+ }
+
+ dnssd_name = salut_avahi_discovery_client_get_dnssd_name (
+ priv->discovery_client);
+ name = g_strdup_printf ("%s.%s.local", contact->name, dnssd_name);
+ priv->record_browser = ga_record_browser_new (name, 0xA);
+ g_free (name);
+
+ g_signal_connect (priv->record_browser, "new-record",
+ G_CALLBACK (salut_contact_avatar_found), contact);
+ g_signal_connect (priv->record_browser, "all-for-now",
+ G_CALLBACK (salut_contact_avatar_all_for_now), contact);
+ g_signal_connect (priv->record_browser, "failure",
+ G_CALLBACK (salut_contact_avatar_failure), contact);
+
+ if (!ga_record_browser_attach (priv->record_browser,
+ priv->discovery_client->avahi_client, &error))
+ {
+ DEBUG ("browser attached failed: %s", error->message);
+ g_error_free (error);
+
+ salut_avahi_contact_avatar_request_flush (self, NULL, 0);
+ }
+}
+
+static void salut_avahi_contact_dispose (GObject *object);
+
+static void
+salut_avahi_contact_class_init (
+ SalutAvahiContactClass *salut_avahi_contact_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_contact_class);
+ SalutContactClass *contact_class = SALUT_CONTACT_CLASS (
+ salut_avahi_contact_class);
+ WockyContactClass *w_contact_class = WOCKY_CONTACT_CLASS (
+ salut_avahi_contact_class);
+ WockyLLContactClass *ll_contact_class = WOCKY_LL_CONTACT_CLASS (
+ salut_avahi_contact_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_avahi_contact_class,
+ sizeof (SalutAvahiContactPrivate));
+
+ object_class->get_property = salut_avahi_contact_get_property;
+ object_class->set_property = salut_avahi_contact_set_property;
+
+ object_class->dispose = salut_avahi_contact_dispose;
+
+ contact_class->get_addresses = salut_avahi_contact_get_addresses;
+ contact_class->has_address = salut_avahi_contact_has_address;
+ contact_class->retrieve_avatar = salut_avahi_contact_retrieve_avatar;
+
+ w_contact_class->dup_jid = salut_avahi_contact_dup_jid;
+ ll_contact_class->get_addresses = salut_avahi_contact_ll_get_addresses;
+
+ param_spec = g_param_spec_object (
+ "discovery-client",
+ "SalutAvahiDiscoveryClient object",
+ "The Salut Avahi Discovery client associated with this muc manager",
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CLIENT,
+ param_spec);
+}
+
+void
+salut_avahi_contact_dispose (GObject *object)
+{
+ SalutAvahiContact *self = SALUT_AVAHI_CONTACT (object);
+ SalutAvahiContactPrivate *priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->presence_resolver_failed_timer > 0)
+ {
+ g_source_remove (priv->presence_resolver_failed_timer);
+ priv->presence_resolver_failed_timer = 0;
+ }
+
+ g_slist_foreach (priv->resolvers, (GFunc) g_object_unref, NULL);
+ g_slist_free (priv->resolvers);
+ priv->resolvers = NULL;
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ priv->discovery_client = NULL;
+ }
+
+ if (G_OBJECT_CLASS (salut_avahi_contact_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_avahi_contact_parent_class)->dispose (object);
+}
+
+/* public functions */
+SalutAvahiContact *
+salut_avahi_contact_new (SalutConnection *connection,
+ const gchar *name,
+ SalutAvahiDiscoveryClient *discovery_client)
+{
+ g_assert (connection != NULL);
+ g_assert (name != NULL);
+ g_assert (discovery_client != NULL);
+
+ return g_object_new (SALUT_TYPE_AVAHI_CONTACT,
+ "connection", connection,
+ "name", name,
+ "discovery-client", discovery_client,
+ NULL);
+}
+
+static void
+contact_drop_resolver (SalutAvahiContact *self,
+ GaServiceResolver *resolver)
+{
+ SalutAvahiContactPrivate *priv = self->priv;
+ SalutContact *contact = SALUT_CONTACT (self);
+ gint resolvers_left;
+
+ priv->resolvers = g_slist_remove (priv->resolvers, resolver);
+
+ resolvers_left = g_slist_length (priv->resolvers);
+
+ DEBUG_RESOLVER (self, resolver, "removed, %d left for %s", resolvers_left,
+ SALUT_CONTACT (self)->name);
+
+ g_object_unref (resolver);
+
+ if (resolvers_left == 0)
+ {
+ salut_contact_lost (contact);
+ }
+}
+
+struct resolverinfo {
+ AvahiIfIndex interface;
+ AvahiProtocol protocol;
+ const gchar *name;
+ const gchar *type;
+ const gchar *domain;
+};
+
+static gint
+compare_resolver (GaServiceResolver *a,
+ struct resolverinfo *info)
+{
+ AvahiIfIndex interface;
+ AvahiProtocol protocol;
+ gchar *name;
+ gchar *type;
+ gchar *domain;
+ gint result;
+
+ g_object_get (a,
+ "interface", &interface,
+ "protocol", &protocol,
+ "name", &name,
+ "type", &type,
+ "domain", &domain,
+ NULL);
+
+ if (interface == info->interface
+ && protocol == info->protocol
+ && !tp_strdiff (name, info->name)
+ && !tp_strdiff (type, info->type)
+ && !tp_strdiff (domain, info->domain))
+ result = 0;
+ else
+ result = 1;
+
+ g_free (name);
+ g_free (type);
+ g_free (domain);
+ return result;
+}
+
+static GaServiceResolver *
+find_resolver (SalutAvahiContact *contact,
+ AvahiIfIndex interface, AvahiProtocol protocol,
+ const gchar *name,
+ const gchar *type,
+ const gchar *domain)
+{
+ SalutAvahiContactPrivate *priv = contact->priv;
+ struct resolverinfo info;
+ GSList *ret;
+ info.interface = interface;
+ info.protocol = protocol;
+ info.name = name;
+ info.type = type;
+ info.domain = domain;
+ ret = g_slist_find_custom (priv->resolvers, &info,
+ (GCompareFunc) compare_resolver);
+ return ret ? GA_SERVICE_RESOLVER (ret->data) : NULL;
+}
+
+static void
+update_alias (SalutAvahiContact *self,
+ const gchar *nick)
+{
+ SalutContact *contact = SALUT_CONTACT (self);
+
+ if (!tp_str_empty (nick))
+ {
+ salut_contact_change_alias (contact, nick);
+ return;
+ }
+
+ if (!tp_str_empty (contact->full_name))
+ {
+ salut_contact_change_alias (contact, contact->full_name);
+ return;
+ }
+
+ salut_contact_change_alias (contact, NULL);
+}
+
+/* Returned string needs to be freed with avahi_free ! */
+static char *
+_avahi_txt_get_keyval_with_size (AvahiStringList *txt,
+ const gchar *key, gsize *size)
+{
+ AvahiStringList *t;
+ gchar *s = NULL;
+
+
+ if ((t = avahi_string_list_find (txt, key)) == NULL)
+ return NULL;
+
+ avahi_string_list_get_pair (t, NULL, &s, size);
+
+ return s;
+}
+
+static char *
+_avahi_txt_get_keyval (AvahiStringList *txt, const gchar *key)
+{
+ return _avahi_txt_get_keyval_with_size (txt, key, NULL);
+}
+
+static void
+contact_resolved_cb (GaServiceResolver *resolver,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ gchar *name,
+ gchar *type,
+ gchar *domain,
+ gchar *host_name,
+ AvahiAddress *address,
+ gint port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ SalutAvahiContact *self)
+{
+ SalutAvahiContactPrivate *priv = self->priv;
+ SalutContact *contact = SALUT_CONTACT (self);
+ char *s;
+ char *nick, *first, *last;
+ /* node, hash and ver as defined by XEP-0115 */
+ char *node, *hash, *ver;
+#ifdef ENABLE_OLPC
+ char *activity_id, *room_id;
+ char *olpc_key_part;
+ gsize size;
+#endif
+
+ DEBUG_RESOLVER (self, resolver, "contact %s resolved", contact->name);
+
+ if (priv->presence_resolver_failed_timer != 0)
+ {
+ DEBUG_CONTACT (self, "remove presence resolver timer");
+ g_source_remove (priv->presence_resolver_failed_timer);
+ priv->presence_resolver_failed_timer = 0;
+ }
+
+ salut_contact_freeze (contact);
+
+ /* status */
+
+ if ((s = _avahi_txt_get_keyval (txt, "status")) != NULL)
+ {
+ int i;
+ for (i = 0; i < SALUT_PRESENCE_NR_PRESENCES ; i++)
+ {
+ if (!tp_strdiff (s, salut_presence_status_txt_names[i]))
+ {
+ salut_contact_change_status (contact, i);
+ break;
+ }
+ }
+ avahi_free (s);
+ }
+
+ /* status message */
+ s = _avahi_txt_get_keyval (txt, "msg");
+ salut_contact_change_status_message (contact, s);
+ avahi_free (s);
+
+ /* real name and nick */
+ nick = _avahi_txt_get_keyval (txt, "nick");
+ first = _avahi_txt_get_keyval (txt, "1st");
+ last = _avahi_txt_get_keyval (txt, "last");
+
+ salut_contact_change_real_name (contact, first, last);
+ update_alias (self, nick);
+
+ avahi_free (nick);
+ avahi_free (first);
+ avahi_free (last);
+
+ /* capabilities */
+ hash = _avahi_txt_get_keyval (txt, "hash");
+ node = _avahi_txt_get_keyval (txt, "node");
+ ver = _avahi_txt_get_keyval (txt, "ver");
+ salut_contact_change_capabilities (contact, hash, node, ver);
+ avahi_free (node);
+ avahi_free (hash);
+ avahi_free (ver);
+
+ /* avatar token */
+ s = _avahi_txt_get_keyval (txt, "phsh");
+ salut_contact_change_avatar_token (contact, s);
+ avahi_free (s);
+
+ /* email */
+ s = _avahi_txt_get_keyval (txt, "email");
+ salut_contact_change_email (contact, s);
+ avahi_free (s);
+
+ /* jid */
+ s = _avahi_txt_get_keyval (txt, "jid");
+ salut_contact_change_jid (contact, s);
+ avahi_free (s);
+
+#ifdef ENABLE_OLPC
+ /* OLPC color */
+ s = _avahi_txt_get_keyval (txt, "olpc-color");
+ salut_contact_change_olpc_color (contact, s);
+ avahi_free (s);
+
+ /* current activity */
+ activity_id = _avahi_txt_get_keyval (txt, "olpc-current-activity");
+ room_id = _avahi_txt_get_keyval (txt, "olpc-current-activity-room");
+
+ salut_contact_change_current_activity (contact, room_id, activity_id);
+ avahi_free (activity_id);
+ avahi_free (room_id);
+
+ /* OLPC key */
+ olpc_key_part = _avahi_txt_get_keyval_with_size (txt,
+ "olpc-key-part0", &size);
+
+ if (olpc_key_part != NULL)
+ {
+ guint i = 0;
+ gchar *olpc_key_part_name = NULL;
+ GArray *olpc_key;
+
+ /* FIXME: how big are OLPC keys anyway? */
+ olpc_key = g_array_sized_new (FALSE, FALSE, sizeof (guint8), 512);
+
+ do
+ {
+ g_array_append_vals (olpc_key, olpc_key_part, size);
+ avahi_free (olpc_key_part);
+
+ i++;
+ olpc_key_part_name = g_strdup_printf ("olpc-key-part%u", i);
+ olpc_key_part = _avahi_txt_get_keyval_with_size (txt,
+ olpc_key_part_name, &size);
+ g_free (olpc_key_part_name);
+ }
+ while (olpc_key_part != NULL);
+
+ salut_contact_change_olpc_key (contact, olpc_key);
+ g_array_unref (olpc_key);
+ }
+
+ /* address */
+ if (address != NULL)
+ {
+ gchar* saddr = g_malloc0 (AVAHI_ADDRESS_STR_MAX);
+
+ if (avahi_address_snprint (saddr, AVAHI_ADDRESS_STR_MAX, address))
+ {
+ switch (address->proto)
+ {
+ case AVAHI_PROTO_INET:
+ salut_contact_change_ipv4_addr (contact, saddr);
+ break;
+ case AVAHI_PROTO_INET6:
+ salut_contact_change_ipv6_addr (contact, saddr);
+ break;
+ default:
+ break;
+ }
+ }
+ g_free (saddr);
+ }
+#endif
+
+ salut_contact_found (contact);
+ salut_contact_thaw (contact);
+}
+
+static gboolean
+presence_resolver_failed_timeout (SalutAvahiContact *self)
+{
+ SalutAvahiContactPrivate *priv = self->priv;
+
+ DEBUG_CONTACT (self, "presence resolver timer expired. Remove contact");
+ priv->presence_resolver_failed_timer = 0;
+ salut_contact_lost (SALUT_CONTACT (self));
+
+ return FALSE;
+}
+
+static void
+contact_failed_cb (GaServiceResolver *resolver,
+ GError *error,
+ SalutAvahiContact *self)
+{
+ SalutAvahiContactPrivate *priv = self->priv;
+
+ if (priv->presence_resolver_failed_timer != 0)
+ /* There is already a timer running */
+ return;
+
+ DEBUG_RESOLVER (self, resolver, "failed: %s. Start presence resolver timer",
+ error->message);
+
+ priv->presence_resolver_failed_timer = g_timeout_add (
+ PRESENCE_TIMEOUT, (GSourceFunc) presence_resolver_failed_timeout,
+ self);
+}
+
+gboolean
+salut_avahi_contact_add_service (SalutAvahiContact *self,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain)
+{
+ SalutAvahiContactPrivate *priv = self->priv;
+ GaServiceResolver *resolver;
+ GError *error = NULL;
+
+ resolver = find_resolver (self, interface, protocol, name, type, domain);
+ if (resolver != NULL)
+ return TRUE;
+
+ resolver = ga_service_resolver_new (interface, protocol, name, type, domain,
+ protocol, 0);
+
+ g_signal_connect (resolver, "found", G_CALLBACK (contact_resolved_cb),
+ self);
+ g_signal_connect (resolver, "failure", G_CALLBACK (contact_failed_cb),
+ self);
+
+ if (!ga_service_resolver_attach (resolver,
+ priv->discovery_client->avahi_client, &error))
+ {
+ DEBUG_CONTACT(self, "Failed to attach resolver: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ DEBUG_RESOLVER (self, resolver, "added");
+ priv->resolvers = g_slist_prepend (priv->resolvers, resolver);
+
+ return TRUE;
+}
+
+void
+salut_avahi_contact_remove_service (SalutAvahiContact *self,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain)
+{
+ GaServiceResolver *resolver;
+
+ resolver = find_resolver (self, interface, protocol, name, type, domain);
+ if (resolver == NULL)
+ return;
+
+ DEBUG_RESOLVER (self, resolver, "remove requested");
+
+ contact_drop_resolver (self, resolver);
+}
+
+gboolean
+salut_avahi_contact_has_services (SalutAvahiContact *self)
+{
+ SalutAvahiContactPrivate *priv = self->priv;
+
+ return priv->resolvers != NULL;
+}
diff --git a/salut/src/avahi-contact.h b/salut/src/avahi-contact.h
new file mode 100644
index 000000000..a9f78b21d
--- /dev/null
+++ b/salut/src/avahi-contact.h
@@ -0,0 +1,75 @@
+/*
+ * avahi-contact.h - Header for SalutAvahiContact
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_AVAHI_CONTACT_H__
+#define __SALUT_AVAHI_CONTACT_H__
+
+#include <glib-object.h>
+
+#include "contact.h"
+#include "avahi-discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiContact SalutAvahiContact;
+typedef struct _SalutAvahiContactClass SalutAvahiContactClass;
+
+struct _SalutAvahiContactClass {
+ SalutContactClass parent_class;
+};
+
+struct _SalutAvahiContact {
+ SalutContact parent;
+
+ gpointer priv;
+};
+
+GType salut_avahi_contact_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_CONTACT \
+ (salut_avahi_contact_get_type ())
+#define SALUT_AVAHI_CONTACT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_CONTACT, SalutAvahiContact))
+#define SALUT_AVAHI_CONTACT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_CONTACT, SalutAvahiContactClass))
+#define SALUT_IS_AVAHI_CONTACT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_CONTACT))
+#define SALUT_IS_AVAHI_CONTACT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_CONTACT))
+#define SALUT_AVAHI_CONTACT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_CONTACT, SalutAvahiContactClass))
+
+SalutAvahiContact *
+salut_avahi_contact_new (SalutConnection *connection, const gchar *name,
+ SalutAvahiDiscoveryClient *discovery_client);
+
+gboolean salut_avahi_contact_add_service (SalutAvahiContact *contact,
+ AvahiIfIndex interface, AvahiProtocol protocol, const char *name,
+ const char *type, const char *domain);
+
+void salut_avahi_contact_remove_service (SalutAvahiContact *contact,
+ AvahiIfIndex interface, AvahiProtocol protocol, const char *name,
+ const char *type, const char *domain);
+
+gboolean salut_avahi_contact_has_services (SalutAvahiContact *contact);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_AVAHI_CONTACT_H__*/
diff --git a/salut/src/avahi-discovery-client.c b/salut/src/avahi-discovery-client.c
new file mode 100644
index 000000000..b75172ab6
--- /dev/null
+++ b/salut/src/avahi-discovery-client.c
@@ -0,0 +1,388 @@
+/*
+ * avahi-discovery-client.c - Source for SalutAvahiDiscoveryClient
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "avahi-discovery-client.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <glib.h>
+
+#include <avahi-gobject/ga-client.h>
+
+#define DEBUG_FLAG DEBUG_DISCOVERY
+#include "debug.h"
+
+#include "avahi-muc-manager.h"
+#include "avahi-contact-manager.h"
+#include "avahi-roomlist-manager.h"
+#include "avahi-self.h"
+#ifdef ENABLE_OLPC
+#include "avahi-olpc-activity-manager.h"
+#endif
+
+#include "presence.h"
+#include "signals-marshal.h"
+
+static void
+discovery_client_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SalutAvahiDiscoveryClient, salut_avahi_discovery_client,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_DISCOVERY_CLIENT,
+ discovery_client_init));
+
+/* signals */
+enum
+{
+ STATE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_STATE = 1,
+ PROP_DNSSD_NAME,
+ LAST_PROPERTY
+};
+
+typedef struct _SalutAvahiDiscoveryClientPrivate \
+ SalutAvahiDiscoveryClientPrivate;
+struct _SalutAvahiDiscoveryClientPrivate
+{
+ SalutDiscoveryClientState state;
+
+ gchar *dnssd_name;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_AVAHI_DISCOVERY_CLIENT_GET_PRIVATE(obj) \
+ ((SalutAvahiDiscoveryClientPrivate *) \
+ ((SalutAvahiDiscoveryClient *) obj)->priv)
+
+static void
+salut_avahi_discovery_client_init (SalutAvahiDiscoveryClient *self)
+{
+ SalutAvahiDiscoveryClientPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT, SalutAvahiDiscoveryClientPrivate);
+
+ self->priv = priv;
+ priv->dispose_has_run = FALSE;
+
+ priv->state = SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTED;
+}
+
+static void
+change_state (SalutAvahiDiscoveryClient *self,
+ SalutDiscoveryClientState state)
+{
+ SalutAvahiDiscoveryClientPrivate *priv =
+ SALUT_AVAHI_DISCOVERY_CLIENT_GET_PRIVATE (self);
+
+ if (priv->state == state)
+ return;
+
+ priv->state = state;
+ g_signal_emit (G_OBJECT (self), signals[STATE_CHANGED], 0, state);
+}
+
+static void
+disconnect_client (SalutAvahiDiscoveryClient *self)
+{
+ change_state (self, SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTING);
+ change_state (self, SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTED);
+}
+
+static void
+salut_avahi_discovery_client_dispose (GObject *object)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (object);
+ SalutAvahiDiscoveryClientPrivate *priv =
+ SALUT_AVAHI_DISCOVERY_CLIENT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (self->avahi_client != NULL)
+ {
+ disconnect_client (self);
+ g_object_unref (self->avahi_client);
+ self->avahi_client = NULL;
+ }
+
+ tp_clear_pointer (&priv->dnssd_name, g_free);
+
+ G_OBJECT_CLASS (salut_avahi_discovery_client_parent_class)->dispose (object);
+}
+
+static void
+salut_avahi_discovery_client_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (object);
+ SalutAvahiDiscoveryClientPrivate *priv =
+ SALUT_AVAHI_DISCOVERY_CLIENT_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case PROP_DNSSD_NAME:
+ g_value_set_string (value, priv->dnssd_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_discovery_client_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (object);
+ SalutAvahiDiscoveryClientPrivate *priv =
+ SALUT_AVAHI_DISCOVERY_CLIENT_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_DNSSD_NAME:
+ priv->dnssd_name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_discovery_client_class_init (
+ SalutAvahiDiscoveryClientClass *salut_avahi_discovery_client_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (
+ salut_avahi_discovery_client_class);
+
+ g_type_class_add_private (salut_avahi_discovery_client_class,
+ sizeof (SalutAvahiDiscoveryClientPrivate));
+
+ object_class->dispose = salut_avahi_discovery_client_dispose;
+
+ object_class->get_property = salut_avahi_discovery_client_get_property;
+ object_class->set_property = salut_avahi_discovery_client_set_property;
+
+ g_object_class_override_property (object_class, PROP_STATE,
+ "state");
+ g_object_class_override_property (object_class, PROP_DNSSD_NAME,
+ "dnssd-name");
+
+ signals[STATE_CHANGED] =
+ g_signal_new ("state-changed",
+ G_OBJECT_CLASS_TYPE (salut_avahi_discovery_client_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ salut_signals_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+}
+
+static void
+_ga_client_running_cb (GaClient *c,
+ GaClientState state,
+ SalutAvahiDiscoveryClient *self)
+{
+ change_state (self, SALUT_DISCOVERY_CLIENT_STATE_CONNECTED);
+}
+
+static void
+_ga_client_failure_cb (GaClient *c,
+ GaClientState state,
+ SalutAvahiDiscoveryClient *self)
+{
+ disconnect_client (self);
+}
+
+/*
+ * salut_avahi_discovery_client_start
+ *
+ * Implements salut_discovery_client_start on SalutDiscoveryClient
+ */
+static gboolean
+salut_avahi_discovery_client_start (SalutDiscoveryClient *client,
+ GError **error)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (client);
+
+ self->avahi_client = ga_client_new (GA_CLIENT_FLAG_NO_FAIL);
+
+ g_signal_connect (self->avahi_client, "state-changed::running",
+ G_CALLBACK (_ga_client_running_cb), self);
+ g_signal_connect (self->avahi_client, "state-changed::failure",
+ G_CALLBACK (_ga_client_failure_cb), self);
+
+ change_state (self, SALUT_DISCOVERY_CLIENT_STATE_CONNECTING);
+ return ga_client_start (self->avahi_client, error);
+}
+
+/*
+ * salut_avahi_discovery_client_create_muc_manager
+ *
+ * Implements salut_discovery_client_create_muc_manager on SalutDiscoveryClient
+ */
+static SalutMucManager *
+salut_avahi_discovery_client_create_muc_manager (SalutDiscoveryClient *client,
+ SalutConnection *connection)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (client);
+
+ return SALUT_MUC_MANAGER (salut_avahi_muc_manager_new (connection,
+ self));
+}
+
+/*
+ * salut_avahi_discovery_client_create_roomlist_manager
+ *
+ * Implements salut_discovery_client_create_roomlist_manager on
+ * SalutDiscoveryClient
+ */
+static SalutRoomlistManager *
+salut_avahi_discovery_client_create_roomlist_manager (
+ SalutDiscoveryClient *client,
+ SalutConnection *connection)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (client);
+
+ return SALUT_ROOMLIST_MANAGER (salut_avahi_roomlist_manager_new (connection,
+ self));
+}
+
+/*
+ * salut_avahi_discovery_client_create_contact_manager
+ *
+ * Implements salut_discovery_client_create_contact_manager on
+ * SalutDiscoveryClient
+ */
+static SalutContactManager *
+salut_avahi_discovery_client_create_contact_manager (
+ SalutDiscoveryClient *client,
+ SalutConnection *connection)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (client);
+
+ return SALUT_CONTACT_MANAGER (salut_avahi_contact_manager_new (connection,
+ self));
+}
+
+#ifdef ENABLE_OLPC
+/*
+ * salut_avahi_discovery_client_create_olpc_activity_manager
+ *
+ * Implements salut_discovery_client_create_olpc_activity_manager on
+ * SalutDiscoveryClient
+ */
+static SalutOlpcActivityManager *
+salut_avahi_discovery_client_create_olpc_activity_manager (
+ SalutDiscoveryClient *client,
+ SalutConnection *connection)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (client);
+
+ return SALUT_OLPC_ACTIVITY_MANAGER (salut_avahi_olpc_activity_manager_new (
+ connection, self));
+}
+#endif
+
+/*
+ * salut_avahi_discovery_client_create_self
+ *
+ * Implements salut_discovery_client_create_self on SalutDiscoveryClient
+ */
+static SalutSelf *
+salut_avahi_discovery_client_create_self (SalutDiscoveryClient *client,
+ SalutConnection *connection,
+ const gchar *nickname,
+ const gchar *first_name,
+ const gchar *last_name,
+ const gchar *jid,
+ const gchar *email,
+ const gchar *published_name,
+ const GArray *olpc_key,
+ const gchar *olpc_color)
+{
+ SalutAvahiDiscoveryClient *self = SALUT_AVAHI_DISCOVERY_CLIENT (client);
+
+ return SALUT_SELF (salut_avahi_self_new (connection, self, nickname, first_name,
+ last_name, jid, email, published_name, olpc_key, olpc_color));
+}
+
+static const gchar *
+salut_avahi_discovery_client_get_host_name_fqdn (SalutDiscoveryClient *clt)
+{
+ return avahi_client_get_host_name_fqdn (
+ SALUT_AVAHI_DISCOVERY_CLIENT (clt)->avahi_client->avahi_client);
+}
+
+const gchar *
+salut_avahi_discovery_client_get_dnssd_name (SalutAvahiDiscoveryClient *clt)
+{
+ SalutAvahiDiscoveryClientPrivate *priv =
+ SALUT_AVAHI_DISCOVERY_CLIENT_GET_PRIVATE (clt);
+
+ if (priv->dnssd_name != NULL)
+ return priv->dnssd_name;
+ else
+ return SALUT_DNSSD_PRESENCE;
+}
+
+static void
+discovery_client_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutDiscoveryClientClass *klass = (SalutDiscoveryClientClass *) g_iface;
+
+ klass->start = salut_avahi_discovery_client_start;
+ klass->create_muc_manager = salut_avahi_discovery_client_create_muc_manager;
+ klass->create_roomlist_manager =
+ salut_avahi_discovery_client_create_roomlist_manager;
+ klass->create_contact_manager =
+ salut_avahi_discovery_client_create_contact_manager;
+#ifdef ENABLE_OLPC
+ klass->create_olpc_activity_manager =
+ salut_avahi_discovery_client_create_olpc_activity_manager;
+#endif
+ klass->create_self = salut_avahi_discovery_client_create_self;
+ klass->get_host_name_fqdn = salut_avahi_discovery_client_get_host_name_fqdn;
+}
diff --git a/salut/src/avahi-discovery-client.h b/salut/src/avahi-discovery-client.h
new file mode 100644
index 000000000..2582c1aa8
--- /dev/null
+++ b/salut/src/avahi-discovery-client.h
@@ -0,0 +1,72 @@
+/*
+ * avahi-discovery-client.h - Header for SalutAvahiDiscoveryClient
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_AVAHI_DISCOVERY_CLIENT_H__
+#define __SALUT_AVAHI_DISCOVERY_CLIENT_H__
+
+#include <glib-object.h>
+
+#include <netdb.h>
+
+#include <avahi-gobject/ga-client.h>
+
+#include "discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiDiscoveryClient SalutAvahiDiscoveryClient;
+typedef struct _SalutAvahiDiscoveryClientClass SalutAvahiDiscoveryClientClass;
+
+struct _SalutAvahiDiscoveryClientClass {
+ GObjectClass parent_class;
+};
+
+struct _SalutAvahiDiscoveryClient {
+ GObject parent;
+
+ GaClient *avahi_client;
+
+ gpointer priv;
+};
+
+GType salut_avahi_discovery_client_get_type (void);
+
+const gchar * salut_avahi_discovery_client_get_dnssd_name (
+ SalutAvahiDiscoveryClient *self);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_DISCOVERY_CLIENT \
+ (salut_avahi_discovery_client_get_type ())
+#define SALUT_AVAHI_DISCOVERY_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,\
+ SalutAvahiDiscoveryClient))
+#define SALUT_AVAHI_DISCOVERY_CLIENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,\
+ SalutAvahiDiscoveryClientClass))
+#define SALUT_IS_AVAHI_DISCOVERY_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_DISCOVERY_CLIENT))
+#define SALUT_IS_AVAHI_DISCOVERY_CLIENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_DISCOVERY_CLIENT))
+#define SALUT_AVAHI_DISCOVERY_CLIENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,\
+ SalutAvahiDiscoveryClientClass))
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_AVAHI_DISCOVERY_CLIENT_H__ */
diff --git a/salut/src/avahi-muc-channel.c b/salut/src/avahi-muc-channel.c
new file mode 100644
index 000000000..ebe3d19ab
--- /dev/null
+++ b/salut/src/avahi-muc-channel.c
@@ -0,0 +1,332 @@
+/*
+ * avahi-muc-channel.c - Source for SalutAvahiMucChannel
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+#include "avahi-muc-channel.h"
+
+#include <avahi-gobject/ga-entry-group.h>
+
+G_DEFINE_TYPE (SalutAvahiMucChannel, salut_avahi_muc_channel,
+ SALUT_TYPE_MUC_CHANNEL);
+
+/* properties */
+enum
+{
+ PROP_DISCOVERY_CLIENT = 1,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutAvahiMucChannelPrivate SalutAvahiMucChannelPrivate;
+
+struct _SalutAvahiMucChannelPrivate
+{
+ SalutAvahiDiscoveryClient *discovery_client;
+ GaEntryGroup *muc_group;
+ GaEntryGroupService *service;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_AVAHI_MUC_CHANNEL_GET_PRIVATE(obj) \
+ ((SalutAvahiMucChannelPrivate *) ((SalutAvahiMucChannel *) obj)->priv)
+
+
+static void
+salut_avahi_muc_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiMucChannel *chan = SALUT_AVAHI_MUC_CHANNEL (object);
+ SalutAvahiMucChannelPrivate *priv =
+ SALUT_AVAHI_MUC_CHANNEL_GET_PRIVATE (chan);
+
+ switch (property_id) {
+ case PROP_DISCOVERY_CLIENT:
+ g_value_set_object (value, priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_muc_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiMucChannel *chan = SALUT_AVAHI_MUC_CHANNEL (object);
+ SalutAvahiMucChannelPrivate *priv =
+ SALUT_AVAHI_MUC_CHANNEL_GET_PRIVATE (chan);
+
+ switch (property_id) {
+ case PROP_DISCOVERY_CLIENT:
+ priv->discovery_client = g_value_get_object (value);
+ g_object_ref (priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_muc_channel_init (SalutAvahiMucChannel *self)
+{
+ SalutAvahiMucChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_AVAHI_MUC_CHANNEL, SalutAvahiMucChannelPrivate);
+
+ self->priv = priv;
+
+ priv->discovery_client = NULL;
+}
+
+static void salut_avahi_muc_channel_dispose (GObject *object);
+
+static gboolean
+salut_avahi_muc_channel_publish_service (SalutMucChannel *channel,
+ GibberMucConnection *muc_connection,
+ const gchar *muc_name)
+{
+ SalutAvahiMucChannel *self = SALUT_AVAHI_MUC_CHANNEL (channel);
+ SalutAvahiMucChannelPrivate *priv =
+ SALUT_AVAHI_MUC_CHANNEL_GET_PRIVATE (self);
+ AvahiStringList *txt_record = NULL;
+ GError *error = NULL;
+ const GHashTable *params;
+ const gchar *address, *port_str;
+ gchar *host = NULL;
+ guint16 port;
+ uint16_t dns_type;
+ size_t dns_payload_length;
+ AvahiAddress addr;
+
+ /* We are already announcing this muc group */
+ if (priv->muc_group != NULL)
+ return TRUE;
+
+ g_assert (priv->service == NULL);
+
+ /* We didn't connect to this group just yet */
+ if (muc_connection->state != GIBBER_MUC_CONNECTION_CONNECTED)
+ {
+ DEBUG ("Not yet connected to this muc, not announcing");
+ return TRUE;
+ }
+
+ priv->muc_group = ga_entry_group_new ();
+
+ if (!ga_entry_group_attach (priv->muc_group,
+ priv->discovery_client->avahi_client, &error))
+ {
+ DEBUG ("entry group attach failed: %s", error->message);
+ goto publish_service_error;
+ }
+
+ params = gibber_muc_connection_get_parameters (muc_connection);
+ address = g_hash_table_lookup ((GHashTable *) params, "address");
+ if (address == NULL)
+ {
+ DEBUG ("can't find connection address");
+ goto publish_service_error;
+ }
+ port_str = g_hash_table_lookup ((GHashTable *) params, "port");
+ if (port_str == NULL)
+ {
+ DEBUG ("can't find connection port");
+ goto publish_service_error;
+ }
+
+ if (avahi_address_parse (address, AVAHI_PROTO_UNSPEC, &addr) == NULL)
+ {
+ DEBUG ("Can't convert address \"%s\" to AvahiAddress", address);
+ goto publish_service_error;
+ }
+
+ switch (addr.proto)
+ {
+ case AVAHI_PROTO_INET:
+ dns_type = AVAHI_DNS_TYPE_A;
+ dns_payload_length = sizeof (AvahiIPv4Address);
+ break;
+ case AVAHI_PROTO_INET6:
+ dns_type = AVAHI_DNS_TYPE_AAAA;
+ dns_payload_length = sizeof (AvahiIPv6Address);
+ break;
+ default:
+ DEBUG ("Don't know how to convert AvahiProtocol 0x%x to DNS record",
+ addr.proto);
+ goto publish_service_error;
+ }
+
+ host = g_strdup_printf ("%s." SALUT_DNSSD_CLIQUE ".local", muc_name);
+
+ /* Add the record */
+ if (!ga_entry_group_add_record_full (priv->muc_group,
+ AVAHI_IF_UNSPEC, addr.proto, 0,
+ host, AVAHI_DNS_CLASS_IN, dns_type, AVAHI_DEFAULT_TTL_HOST_NAME,
+ &(addr.data.data), dns_payload_length, &error))
+ {
+ DEBUG ("add A/AAAA record failed: %s", error->message);
+ goto publish_service_error;
+ }
+
+ port = atoi (port_str);
+
+ txt_record = avahi_string_list_new ("txtvers=0", NULL);
+
+ /* We shouldn't add the service but manually create the SRV record so
+ * we'll be able to allow multiple announcers */
+ priv->service = ga_entry_group_add_service_full_strlist (
+ priv->muc_group, AVAHI_IF_UNSPEC, addr.proto, 0, muc_name,
+ SALUT_DNSSD_CLIQUE, NULL, host, port, &error, txt_record);
+ if (priv->service == NULL)
+ {
+ DEBUG ("add service failed: %s", error->message);
+ goto publish_service_error;
+ }
+
+ if (!ga_entry_group_commit (priv->muc_group, &error))
+ {
+ DEBUG ("entry group commit failed: %s", error->message);
+ goto publish_service_error;
+ }
+
+ DEBUG ("service created: %s %s %d", muc_name, host, port);
+ avahi_string_list_free (txt_record);
+ g_free (host);
+ return TRUE;
+
+publish_service_error:
+ if (priv->muc_group != NULL)
+ {
+ g_object_unref (priv->muc_group);
+ priv->muc_group = NULL;
+ }
+
+ priv->service = NULL;
+
+ if (txt_record != NULL)
+ avahi_string_list_free (txt_record);
+
+ if (host != NULL)
+ g_free (host);
+
+ if (error != NULL)
+ g_error_free (error);
+ return FALSE;
+}
+
+static void
+salut_avahi_muc_channel_class_init (
+ SalutAvahiMucChannelClass *salut_avahi_muc_channel_class) {
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_muc_channel_class);
+ SalutMucChannelClass *muc_channel_class = SALUT_MUC_CHANNEL_CLASS (
+ salut_avahi_muc_channel_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_avahi_muc_channel_class,
+ sizeof (SalutAvahiMucChannelPrivate));
+
+ object_class->dispose = salut_avahi_muc_channel_dispose;
+
+ object_class->get_property = salut_avahi_muc_channel_get_property;
+ object_class->set_property = salut_avahi_muc_channel_set_property;
+
+ muc_channel_class->publish_service = salut_avahi_muc_channel_publish_service;
+
+ param_spec = g_param_spec_object (
+ "discovery-client",
+ "SalutAvahiDiscoveryClient object",
+ "The Salut Avahi Discovery client associated with this muc channel",
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DISCOVERY_CLIENT,
+ param_spec);
+
+ /* FIXME: This is an ugly workaround. See fd.o #15092 */
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutMucChannelClass, group_class),
+ salut_muc_channel_add_member, NULL);
+}
+
+static void
+salut_avahi_muc_channel_dispose (GObject *object)
+{
+ SalutAvahiMucChannel *self = SALUT_AVAHI_MUC_CHANNEL (object);
+ SalutAvahiMucChannelPrivate *priv =
+ SALUT_AVAHI_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ priv->discovery_client = NULL;
+ }
+
+ if (priv->muc_group != NULL)
+ {
+ g_object_unref (priv->muc_group);
+ priv->muc_group = NULL;
+ }
+
+ if (G_OBJECT_CLASS (salut_avahi_muc_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_avahi_muc_channel_parent_class)->dispose (object);
+}
+
+SalutAvahiMucChannel *
+salut_avahi_muc_channel_new (SalutConnection *connection,
+ const gchar *path,
+ GibberMucConnection *muc_connection,
+ TpHandle handle,
+ const gchar *name,
+ SalutAvahiDiscoveryClient *discovery_client,
+ TpHandle initiator,
+ gboolean creator,
+ gboolean requested)
+{
+ return g_object_new (SALUT_TYPE_AVAHI_MUC_CHANNEL,
+ "connection", connection,
+ "object-path", path,
+ "muc_connection", muc_connection,
+ "handle", handle,
+ "name", name,
+ "discovery-client", discovery_client,
+ "initiator-handle", initiator,
+ "creator", creator,
+ "requested", requested,
+ NULL);
+}
diff --git a/salut/src/avahi-muc-channel.h b/salut/src/avahi-muc-channel.h
new file mode 100644
index 000000000..1aa5924ef
--- /dev/null
+++ b/salut/src/avahi-muc-channel.h
@@ -0,0 +1,72 @@
+/*
+ * avahi-muc-channel.h - Header for SalutAvahiMucChannel
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_AVAHI_MUC_CHANNEL_H__
+#define __SALUT_AVAHI_MUC_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <gibber/gibber-muc-connection.h>
+
+#include "muc-channel.h"
+#include "connection.h"
+#include "avahi-discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiMucChannel SalutAvahiMucChannel;
+typedef struct _SalutAvahiMucChannelClass SalutAvahiMucChannelClass;
+
+struct _SalutAvahiMucChannelClass
+{
+ SalutMucChannelClass parent_class;
+};
+
+struct _SalutAvahiMucChannel
+{
+ SalutMucChannel parent;
+
+ gpointer priv;
+};
+
+GType salut_avahi_muc_channel_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_MUC_CHANNEL \
+ (salut_avahi_muc_channel_get_type ())
+#define SALUT_AVAHI_MUC_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_MUC_CHANNEL, SalutAvahiMucChannel))
+#define SALUT_AVAHI_MUC_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_MUC_CHANNEL, SalutAvahiMucChannelClass))
+#define SALUT_IS_AVAHI_MUC_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_MUC_CHANNEL))
+#define SALUT_IS_AVAHI_MUC_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_MUC_CHANNEL))
+#define SALUT_AVAHI_MUC_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_MUC_CHANNEL, SalutAvahiMucChannelClass))
+
+SalutAvahiMucChannel * salut_avahi_muc_channel_new (SalutConnection *connection,
+ const gchar *path, GibberMucConnection *muc_connection, TpHandle handle,
+ const gchar *name, SalutAvahiDiscoveryClient *discovery_client,
+ TpHandle initiator, gboolean creator,
+ gboolean requested);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_AVAHI_MUC_CHANNEL_H__*/
diff --git a/salut/src/avahi-muc-manager.c b/salut/src/avahi-muc-manager.c
new file mode 100644
index 000000000..5456f53d9
--- /dev/null
+++ b/salut/src/avahi-muc-manager.c
@@ -0,0 +1,207 @@
+/*
+ * avahi-muc-manager.c - Source for SalutAvahiMucManager
+ * Copyright (C) 2006 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "avahi-muc-manager.h"
+
+#include <avahi-gobject/ga-service-browser.h>
+#include <avahi-gobject/ga-service-resolver.h>
+
+#include <wocky/wocky-namespaces.h>
+
+#include <gibber/gibber-muc-connection.h>
+
+#include "muc-channel.h"
+#include "contact-manager.h"
+#include "tubes-channel.h"
+#include "roomlist-channel.h"
+#include "avahi-muc-channel.h"
+
+#include <telepathy-glib/channel-factory-iface.h>
+#include <telepathy-glib/interfaces.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+G_DEFINE_TYPE (SalutAvahiMucManager, salut_avahi_muc_manager,
+ SALUT_TYPE_MUC_MANAGER);
+
+/* properties */
+enum {
+ PROP_CLIENT = 1,
+ LAST_PROP
+};
+
+/* private structure */
+typedef struct _SalutAvahiMucManagerPrivate SalutAvahiMucManagerPrivate;
+
+struct _SalutAvahiMucManagerPrivate
+{
+ SalutAvahiDiscoveryClient *discovery_client;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_AVAHI_MUC_MANAGER_GET_PRIVATE(obj) \
+ ((SalutAvahiMucManagerPrivate *) ((SalutAvahiMucManager *) obj)->priv)
+
+static void
+salut_avahi_muc_manager_init (SalutAvahiMucManager *self)
+{
+ SalutAvahiMucManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_AVAHI_MUC_MANAGER, SalutAvahiMucManagerPrivate);
+
+ self->priv = priv;
+}
+
+static void
+salut_avahi_muc_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiMucManager *self = SALUT_AVAHI_MUC_MANAGER (object);
+ SalutAvahiMucManagerPrivate *priv = SALUT_AVAHI_MUC_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_muc_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiMucManager *self = SALUT_AVAHI_MUC_MANAGER (object);
+ SalutAvahiMucManagerPrivate *priv = SALUT_AVAHI_MUC_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ priv->discovery_client = g_value_get_object (value);
+ g_object_ref (priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void salut_avahi_muc_manager_dispose (GObject *object);
+
+static SalutMucChannel *
+salut_avahi_muc_manager_create_muc_channel (
+ SalutMucManager *mgr,
+ SalutConnection *connection,
+ const gchar *path,
+ GibberMucConnection *muc_connection,
+ TpHandle handle,
+ const gchar *name,
+ TpHandle initiator,
+ gboolean creator,
+ gboolean requested)
+{
+ SalutAvahiMucManager *self = SALUT_AVAHI_MUC_MANAGER (mgr);
+ SalutAvahiMucManagerPrivate *priv = SALUT_AVAHI_MUC_MANAGER_GET_PRIVATE (self);
+
+ return SALUT_MUC_CHANNEL (salut_avahi_muc_channel_new (connection,
+ path, muc_connection, handle, name, priv->discovery_client, initiator,
+ creator, requested));
+}
+
+static void
+salut_avahi_muc_manager_class_init (
+ SalutAvahiMucManagerClass *salut_avahi_muc_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_muc_manager_class);
+ SalutMucManagerClass *muc_manager_class = SALUT_MUC_MANAGER_CLASS (
+ salut_avahi_muc_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_avahi_muc_manager_class,
+ sizeof (SalutAvahiMucManagerPrivate));
+
+ object_class->get_property = salut_avahi_muc_manager_get_property;
+ object_class->set_property = salut_avahi_muc_manager_set_property;
+
+ object_class->dispose = salut_avahi_muc_manager_dispose;
+
+ muc_manager_class->create_muc_channel =
+ salut_avahi_muc_manager_create_muc_channel;
+
+ param_spec = g_param_spec_object (
+ "discovery-client",
+ "SalutAvahiDiscoveryClient object",
+ "The Salut Avahi Discovery client associated with this muc manager",
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CLIENT,
+ param_spec);
+}
+
+static void
+salut_avahi_muc_manager_dispose (GObject *object)
+{
+ SalutAvahiMucManager *self = SALUT_AVAHI_MUC_MANAGER (object);
+ SalutAvahiMucManagerPrivate *priv = SALUT_AVAHI_MUC_MANAGER_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ priv->discovery_client = NULL;
+ }
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (salut_avahi_muc_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_avahi_muc_manager_parent_class)->dispose (object);
+}
+
+/* public functions */
+SalutAvahiMucManager *
+salut_avahi_muc_manager_new (SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client)
+{
+ g_assert (connection != NULL);
+ g_assert (discovery_client != NULL);
+
+ return g_object_new (SALUT_TYPE_AVAHI_MUC_MANAGER,
+ "connection", connection,
+ "discovery-client", discovery_client,
+ NULL);
+}
diff --git a/salut/src/avahi-muc-manager.h b/salut/src/avahi-muc-manager.h
new file mode 100644
index 000000000..ea8034b54
--- /dev/null
+++ b/salut/src/avahi-muc-manager.h
@@ -0,0 +1,69 @@
+/*
+ * avahi-muc-manager.h - Header for SalutAvahiMucManager
+ * Copyright (C) 2006 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_AVAHI_MUC_MANAGER_H__
+#define __SALUT_AVAHI_MUC_MANAGER_H__
+
+#include <glib-object.h>
+
+#include "muc-manager.h"
+#include "connection.h"
+#include "avahi-discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiMucManager SalutAvahiMucManager;
+typedef struct _SalutAvahiMucManagerClass SalutAvahiMucManagerClass;
+
+struct _SalutAvahiMucManagerClass {
+ SalutMucManagerClass parent_class;
+};
+
+struct _SalutAvahiMucManager {
+ SalutMucManager parent;
+
+ gpointer priv;
+};
+
+GType salut_avahi_muc_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_MUC_MANAGER \
+ (salut_avahi_muc_manager_get_type ())
+#define SALUT_AVAHI_MUC_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_MUC_MANAGER, \
+ SalutAvahiMucManager))
+#define SALUT_AVAHI_MUC_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_MUC_MANAGER, \
+ SalutAvahiMucManagerClass))
+#define SALUT_IS_AVAHI_MUC_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_MUC_MANAGER))
+#define SALUT_IS_AVAHI_MUC_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_MUC_MANAGER))
+#define SALUT_AVAHI_MUC_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_MUC_MANAGER, \
+ SalutAvahiMucManagerClass))
+
+SalutAvahiMucManager *
+salut_avahi_muc_manager_new (SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_AVAHI_MUC_MANAGER_H__*/
diff --git a/salut/src/avahi-olpc-activity-manager.c b/salut/src/avahi-olpc-activity-manager.c
new file mode 100644
index 000000000..94da87181
--- /dev/null
+++ b/salut/src/avahi-olpc-activity-manager.c
@@ -0,0 +1,381 @@
+/*
+ * avahi-avahi_olpc-activity-manager.c - Source for
+ * SalutAvahiOlpcActivityManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <avahi-gobject/ga-service-browser.h>
+
+#include "avahi-olpc-activity-manager.h"
+#include "avahi-olpc-activity.h"
+#include "avahi-discovery-client.h"
+#include "olpc-activity.h"
+
+#define DEBUG_FLAG DEBUG_OLPC_ACTIVITY
+#include "debug.h"
+
+G_DEFINE_TYPE (SalutAvahiOlpcActivityManager, salut_avahi_olpc_activity_manager,
+ SALUT_TYPE_OLPC_ACTIVITY_MANAGER);
+
+/* properties */
+enum {
+ PROP_CLIENT = 1,
+ LAST_PROP
+};
+
+/* private structure */
+typedef struct _SalutAvahiOlpcActivityManagerPrivate SalutAvahiOlpcActivityManagerPrivate;
+
+struct _SalutAvahiOlpcActivityManagerPrivate
+{
+ SalutAvahiDiscoveryClient *discovery_client;
+ GaServiceBrowser *browser;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_AVAHI_OLPC_ACTIVITY_MANAGER, SalutAvahiOlpcActivityManagerPrivate))
+
+static void
+salut_avahi_olpc_activity_manager_init (SalutAvahiOlpcActivityManager *self)
+{
+ SalutAvahiOlpcActivityManagerPrivate *priv =
+ SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ priv->browser = ga_service_browser_new (SALUT_DNSSD_OLPC_ACTIVITY);
+}
+
+static void
+salut_avahi_olpc_activity_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiOlpcActivityManager *self = SALUT_AVAHI_OLPC_ACTIVITY_MANAGER (object);
+ SalutAvahiOlpcActivityManagerPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_olpc_activity_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiOlpcActivityManager *self = SALUT_AVAHI_OLPC_ACTIVITY_MANAGER (object);
+ SalutAvahiOlpcActivityManagerPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ priv->discovery_client = g_value_get_object (value);
+ g_object_ref (priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+split_activity_name (const gchar *name,
+ gchar **room_name,
+ gchar **contact_name)
+{
+ gchar **tmp;
+
+ tmp = g_strsplit (name, ":", 2);
+ if (tmp[0] == NULL || tmp[1] == NULL)
+ {
+ DEBUG ("Ignoring invalid OLPC activity DNS-SD with no ':': %s", name);
+ return FALSE;
+ }
+
+ if (room_name != NULL)
+ *room_name = g_strdup (tmp[0]);
+ if (contact_name != NULL)
+ *contact_name = g_strdup (tmp[1]);
+
+ g_strfreev (tmp);
+ return TRUE;
+}
+
+static void
+browser_found (GaServiceBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain,
+ GaLookupResultFlags flags,
+ SalutAvahiOlpcActivityManager *self)
+{
+ SalutOlpcActivityManager *mgr = SALUT_OLPC_ACTIVITY_MANAGER (self);
+ SalutOlpcActivity *activity;
+ gchar *room_name = NULL;
+ gchar *contact_name = NULL;
+ TpBaseConnection *base_conn = (TpBaseConnection *) mgr->connection;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_ROOM);
+ TpHandle room;
+ GError *error = NULL;
+ SalutContactManager *contact_manager;
+ SalutContact *contact;
+
+ if (flags & AVAHI_LOOKUP_RESULT_OUR_OWN)
+ return;
+
+ if (base_conn->status == TP_CONNECTION_STATUS_DISCONNECTED)
+ return;
+
+ if (!split_activity_name (name, &room_name, &contact_name))
+ return;
+
+ room = tp_handle_ensure (room_repo, room_name, NULL, &error);
+ if (room == 0)
+ {
+ DEBUG ("invalid room name %s: %s", room_name, error->message);
+ g_free (room_name);
+ g_free (contact_name);
+ return;
+ }
+
+ activity = salut_olpc_activity_manager_ensure_activity_by_room (mgr,
+ room);
+
+ salut_avahi_olpc_activity_add_service (SALUT_AVAHI_OLPC_ACTIVITY (activity),
+ interface, protocol, name, type, domain);
+
+ g_object_get (mgr->connection,
+ "contact-manager", &contact_manager, NULL);
+ g_assert (contact_manager != NULL);
+
+ contact = salut_contact_manager_ensure_contact (contact_manager,
+ contact_name);
+ salut_olpc_activity_manager_contact_joined (mgr, contact, activity);
+
+ g_object_unref (activity);
+ tp_handle_unref (room_repo, room);
+ g_free (contact_name);
+ g_free (room_name);
+ g_object_unref (contact);
+ g_object_unref (contact_manager);
+}
+
+static void
+browser_removed (GaServiceBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain,
+ GaLookupResultFlags flags,
+ SalutAvahiOlpcActivityManager *self)
+{
+ SalutOlpcActivityManager *mgr = SALUT_OLPC_ACTIVITY_MANAGER (self);
+ SalutOlpcActivity *activity;
+ gchar *room_name = NULL;
+ gchar *contact_name = NULL;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) mgr->connection, TP_HANDLE_TYPE_ROOM);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) mgr->connection, TP_HANDLE_TYPE_CONTACT);
+ TpHandle room;
+ TpHandle contact_handle;
+ GError *error = NULL;
+ SalutContactManager *contact_manager;
+ SalutContact *contact;
+
+ if (!split_activity_name (name, &room_name, &contact_name))
+ return;
+
+ room = tp_handle_ensure (room_repo, room_name, NULL, &error);
+ g_free (room_name);
+ if (room == 0)
+ {
+ DEBUG ("invalid room name %s: %s", room_name, error->message);
+ g_free (contact_name);
+ g_error_free (error);
+ return;
+ }
+
+ contact_handle = tp_handle_ensure (contact_repo, contact_name, NULL, &error);
+ if (contact_handle == 0)
+ {
+ DEBUG ("Invalid contact name %s: %s", contact_name, error->message);
+ g_error_free (error);
+ g_free (contact_name);
+ tp_handle_unref (room_repo, room);
+ return;
+ }
+ g_free (contact_name);
+
+ activity = salut_olpc_activity_manager_get_activity_by_room (mgr, room);
+ tp_handle_unref (room_repo, room);
+ if (activity == NULL)
+ {
+ tp_handle_unref (contact_repo, contact_handle);
+ return;
+ }
+
+ salut_avahi_olpc_activity_remove_service (SALUT_AVAHI_OLPC_ACTIVITY (activity),
+ interface, protocol, name, type, domain);
+
+ g_object_get (mgr->connection,
+ "contact-manager", &contact_manager, NULL);
+ g_assert (contact_manager != NULL);
+
+ contact = salut_contact_manager_get_contact (contact_manager,
+ contact_handle);
+ tp_handle_unref (contact_repo, contact_handle);
+ g_object_unref (contact_manager);
+ if (contact == NULL)
+ return;
+
+ salut_olpc_activity_manager_contact_left (mgr, contact, activity);
+ g_object_unref (contact);
+}
+
+static void
+browser_failed (GaServiceBrowser *browser,
+ GError *error,
+ SalutAvahiOlpcActivityManager *self)
+{
+ g_warning ("browser failed -> %s", error->message);
+}
+
+static gboolean
+salut_avahi_olpc_activity_manager_start (SalutOlpcActivityManager *mgr,
+ GError **error)
+{
+ SalutAvahiOlpcActivityManager *self = SALUT_AVAHI_OLPC_ACTIVITY_MANAGER (mgr);
+ SalutAvahiOlpcActivityManagerPrivate *priv =
+ SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ g_signal_connect (priv->browser, "new-service",
+ G_CALLBACK (browser_found), self);
+ g_signal_connect (priv->browser, "removed-service",
+ G_CALLBACK (browser_removed), self);
+ g_signal_connect (priv->browser, "failure",
+ G_CALLBACK (browser_failed), self);
+
+ if (!ga_service_browser_attach (priv->browser,
+ priv->discovery_client->avahi_client, error))
+ {
+ DEBUG ("browser attach failed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static SalutOlpcActivity *
+salut_avahi_olpc_activity_manager_create_activity (
+ SalutOlpcActivityManager *mgr)
+{
+ SalutAvahiOlpcActivityManager *self = SALUT_AVAHI_OLPC_ACTIVITY_MANAGER (mgr);
+ SalutAvahiOlpcActivityManagerPrivate *priv =
+ SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ return SALUT_OLPC_ACTIVITY (salut_avahi_olpc_activity_new (
+ mgr->connection, priv->discovery_client));
+}
+
+static void salut_avahi_olpc_activity_manager_dispose (GObject *object);
+
+static void
+salut_avahi_olpc_activity_manager_class_init (SalutAvahiOlpcActivityManagerClass *salut_avahi_olpc_activity_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_olpc_activity_manager_class);
+ SalutOlpcActivityManagerClass *activity_manager_class = SALUT_OLPC_ACTIVITY_MANAGER_CLASS (
+ salut_avahi_olpc_activity_manager_class);
+
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_avahi_olpc_activity_manager_class,
+ sizeof (SalutAvahiOlpcActivityManagerPrivate));
+
+ object_class->get_property = salut_avahi_olpc_activity_manager_get_property;
+ object_class->set_property = salut_avahi_olpc_activity_manager_set_property;
+
+ object_class->dispose = salut_avahi_olpc_activity_manager_dispose;
+
+ activity_manager_class->start = salut_avahi_olpc_activity_manager_start;
+ activity_manager_class->create_activity =
+ salut_avahi_olpc_activity_manager_create_activity;
+
+ param_spec = g_param_spec_object (
+ "discovery-client",
+ "SalutAvahiDiscoveryClient object",
+ "The Salut Avahi Discovery client associated with this manager",
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CLIENT,
+ param_spec);
+}
+
+static void
+salut_avahi_olpc_activity_manager_dispose (GObject *object)
+{
+ SalutAvahiOlpcActivityManager *self = SALUT_AVAHI_OLPC_ACTIVITY_MANAGER (object);
+ SalutAvahiOlpcActivityManagerPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ priv->discovery_client = NULL;
+ }
+
+ if (priv->browser != NULL)
+ {
+ g_object_unref (priv->browser);
+ priv->browser = NULL;
+ }
+
+ if (G_OBJECT_CLASS (salut_avahi_olpc_activity_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_avahi_olpc_activity_manager_parent_class)->dispose (object);
+}
+
+SalutAvahiOlpcActivityManager *
+salut_avahi_olpc_activity_manager_new (SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client)
+{
+ return g_object_new (SALUT_TYPE_AVAHI_OLPC_ACTIVITY_MANAGER,
+ "connection", connection,
+ "discovery-client", discovery_client,
+ NULL);
+}
diff --git a/salut/src/avahi-olpc-activity-manager.h b/salut/src/avahi-olpc-activity-manager.h
new file mode 100644
index 000000000..557f663bb
--- /dev/null
+++ b/salut/src/avahi-olpc-activity-manager.h
@@ -0,0 +1,68 @@
+/*
+ * avahi-olpc-activity-managere.h - Header for
+ * SalutAvahiOlpcActivityManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_H__
+#define __SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/handle.h>
+
+#include "olpc-activity-manager.h"
+#include "connection.h"
+#include "avahi-discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiOlpcActivityManager SalutAvahiOlpcActivityManager;
+typedef struct _SalutAvahiOlpcActivityManagerClass SalutAvahiOlpcActivityManagerClass;
+
+struct _SalutAvahiOlpcActivityManagerClass {
+ SalutOlpcActivityManagerClass parent_class;
+};
+
+struct _SalutAvahiOlpcActivityManager {
+ SalutOlpcActivityManager parent;
+};
+
+GType salut_avahi_olpc_activity_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_OLPC_ACTIVITY_MANAGER \
+ (salut_avahi_olpc_activity_manager_get_type ())
+#define SALUT_AVAHI_OLPC_ACTIVITY_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_OLPC_ACTIVITY_MANAGER, \
+ SalutAvahiOlpcActivityManager))
+#define SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_OLPC_ACTIVITY_MANAGER, \
+ SalutAvahiOlpcActivityManagerClass))
+#define SALUT_IS_AVAHI_OLPC_ACTIVITY_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_OLPC_ACTIVITY_MANAGER))
+#define SALUT_IS_AVAHI_OLPC_ACTIVITY_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_OLPC_ACTIVITY_MANAGER))
+#define SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_OLPC_ACTIVITY_MANAGER, SalutAvahiOlpcActivityManagerClass))
+
+SalutAvahiOlpcActivityManager * salut_avahi_olpc_activity_manager_new (
+ SalutConnection *connection, SalutAvahiDiscoveryClient *discovery_client);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_AVAHI_OLPC_ACTIVITY_MANAGER_H__*/
diff --git a/salut/src/avahi-olpc-activity.c b/salut/src/avahi-olpc-activity.c
new file mode 100644
index 000000000..8db8bd85c
--- /dev/null
+++ b/salut/src/avahi-olpc-activity.c
@@ -0,0 +1,546 @@
+/*
+ * avahi-olpc-activity.c - Source for SalutAvahiOlpcActivity
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <avahi-gobject/ga-entry-group.h>
+#include <avahi-gobject/ga-service-resolver.h>
+#include <avahi-common/malloc.h>
+
+#include "avahi-olpc-activity.h"
+
+#define DEBUG_FLAG DEBUG_OLPC_ACTIVITY
+#include "debug.h"
+
+G_DEFINE_TYPE (SalutAvahiOlpcActivity, salut_avahi_olpc_activity,
+ SALUT_TYPE_OLPC_ACTIVITY);
+
+/* properties */
+enum {
+ PROP_CLIENT = 1,
+ LAST_PROP
+};
+
+/* private structure */
+typedef struct _SalutAvahiOlpcActivityPrivate SalutAvahiOlpcActivityPrivate;
+
+struct _SalutAvahiOlpcActivityPrivate
+{
+ SalutAvahiDiscoveryClient *discovery_client;
+ GSList *resolvers;
+ /* group and service can be NULL if we are not announcing this activity */
+ GaEntryGroup *group;
+ GaEntryGroupService *service;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_AVAHI_OLPC_ACTIVITY, SalutAvahiOlpcActivityPrivate))
+
+static void
+salut_avahi_olpc_activity_init (SalutAvahiOlpcActivity *obj)
+{
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ obj);
+
+ priv->resolvers = NULL;
+}
+
+static void
+salut_avahi_olpc_activity_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiOlpcActivity *self = SALUT_AVAHI_OLPC_ACTIVITY (object);
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_olpc_activity_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiOlpcActivity *self = SALUT_AVAHI_OLPC_ACTIVITY (object);
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ priv->discovery_client = g_value_get_object (value);
+ g_object_ref (priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+activity_is_announced (SalutAvahiOlpcActivity *self)
+{
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+
+ return (priv->group != NULL && priv->service != NULL);
+}
+
+static gboolean
+update_activity_service (SalutAvahiOlpcActivity *self,
+ GError **error)
+{
+ SalutOlpcActivity *activity = SALUT_OLPC_ACTIVITY (self);
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+ GError *err = NULL;
+
+ if (!activity_is_announced (self))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Trying to update an activity that's not announced");
+ return FALSE;
+ }
+
+ ga_entry_group_service_freeze (priv->service);
+
+ if (activity->name != NULL)
+ ga_entry_group_service_set (priv->service, "name",
+ activity->name, NULL);
+
+ if (activity->color != NULL)
+ ga_entry_group_service_set (priv->service, "color",
+ activity->color, NULL);
+
+ if (activity->type != NULL)
+ ga_entry_group_service_set (priv->service, "type",
+ activity->type, NULL);
+
+ if (activity->tags != NULL)
+ ga_entry_group_service_set (priv->service, "tags",
+ activity->tags, NULL);
+
+ return ga_entry_group_service_thaw (priv->service, &err);
+}
+
+static gboolean
+salut_avahi_olpc_activity_announce (SalutOlpcActivity *activity,
+ GError **error)
+{
+ SalutAvahiOlpcActivity *self = SALUT_AVAHI_OLPC_ACTIVITY (activity);
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+ const gchar *room_name;
+ gchar *name;
+ AvahiStringList *txt_record;
+ TpHandleRepoIface *room_repo;
+ gchar *published_name;
+
+ g_return_val_if_fail (!activity->is_private, FALSE);
+ g_return_val_if_fail (!activity_is_announced (self), FALSE);
+
+ room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) activity->connection, TP_HANDLE_TYPE_ROOM);
+
+ room_name = tp_handle_inspect (room_repo, activity->room);
+ /* caller should already have validated this */
+ g_return_val_if_fail (room_name != NULL, FALSE);
+
+ priv->group = ga_entry_group_new ();
+ if (!ga_entry_group_attach (priv->group, priv->discovery_client->avahi_client,
+ error))
+ return FALSE;
+
+ g_object_get (activity->connection, "published-name", &published_name, NULL);
+
+ name = g_strdup_printf ("%s:%s@%s", room_name, published_name,
+ avahi_client_get_host_name (
+ priv->discovery_client->avahi_client->avahi_client));
+
+ g_free (published_name);
+
+ txt_record = avahi_string_list_new ("txtvers=0", NULL);
+ txt_record = avahi_string_list_add_printf (txt_record, "room=%s", room_name);
+ if (activity->id != NULL)
+ txt_record = avahi_string_list_add_printf (txt_record, "activity-id=%s",
+ activity->id);
+
+ priv->service = ga_entry_group_add_service_strlist (priv->group, name,
+ SALUT_DNSSD_OLPC_ACTIVITY, 0, error, txt_record);
+
+ if (priv->service == NULL)
+ return FALSE;
+
+ DEBUG ("announce activity %s", name);
+ g_free (name);
+ avahi_string_list_free (txt_record);
+
+ if (!ga_entry_group_commit (priv->group, error))
+ return FALSE;
+
+ /* announce activities properties */
+ if (!update_activity_service (self, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+salut_avahi_olpc_activity_stop_announce (SalutOlpcActivity *activity)
+{
+ SalutAvahiOlpcActivity *self = SALUT_AVAHI_OLPC_ACTIVITY (activity);
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+
+ /* Announcing the activity could have failed, so check if we're actually
+ * announcing it */
+ if (!activity_is_announced (self))
+ return;
+
+ g_object_unref (priv->group);
+ priv->group = NULL;
+ priv->service = NULL;
+
+ DEBUG ("stop announce activity %s", activity->id);
+}
+
+static gboolean
+salut_avahi_update (SalutOlpcActivity *activity,
+ GError **error)
+{
+ SalutAvahiOlpcActivity *self = SALUT_AVAHI_OLPC_ACTIVITY (activity);
+
+ return update_activity_service (self, error);
+}
+
+static void salut_avahi_olpc_activity_dispose (GObject *object);
+static void salut_avahi_olpc_activity_finalize (GObject *object);
+
+static void
+salut_avahi_olpc_activity_class_init (
+ SalutAvahiOlpcActivityClass *salut_avahi_olpc_activity_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_olpc_activity_class);
+ SalutOlpcActivityClass *activity_class = SALUT_OLPC_ACTIVITY_CLASS (
+ salut_avahi_olpc_activity_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_avahi_olpc_activity_class,
+ sizeof (SalutAvahiOlpcActivityPrivate));
+
+ object_class->get_property = salut_avahi_olpc_activity_get_property;
+ object_class->set_property = salut_avahi_olpc_activity_set_property;
+
+ object_class->dispose = salut_avahi_olpc_activity_dispose;
+ object_class->finalize = salut_avahi_olpc_activity_finalize;
+
+ activity_class->announce = salut_avahi_olpc_activity_announce;
+ activity_class->stop_announce = salut_avahi_olpc_activity_stop_announce;
+ activity_class->update = salut_avahi_update;
+
+ param_spec = g_param_spec_object (
+ "discovery-client",
+ "SalutAvahiDiscoveryClient object",
+ "The Salut Avahi Discovery client associated with this manager",
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CLIENT,
+ param_spec);
+}
+
+void
+salut_avahi_olpc_activity_dispose (GObject *object)
+{
+ SalutAvahiOlpcActivity *self = SALUT_AVAHI_OLPC_ACTIVITY (object);
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ g_slist_foreach (priv->resolvers, (GFunc) g_object_unref, NULL);
+ g_slist_free (priv->resolvers);
+ priv->resolvers = NULL;
+
+ if (priv->group != NULL)
+ {
+ g_object_unref (priv->group);
+ priv->group = NULL;
+ }
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ priv->discovery_client = NULL;
+ }
+
+ if (G_OBJECT_CLASS (salut_avahi_olpc_activity_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_avahi_olpc_activity_parent_class)->dispose (object);
+}
+
+void
+salut_avahi_olpc_activity_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (salut_avahi_olpc_activity_parent_class)->finalize (object);
+}
+
+SalutAvahiOlpcActivity *
+salut_avahi_olpc_activity_new (SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client)
+{
+ return g_object_new (SALUT_TYPE_AVAHI_OLPC_ACTIVITY,
+ "connection", connection,
+ "discovery-client", discovery_client,
+ NULL);
+}
+
+struct resolverinfo
+{
+ AvahiIfIndex interface;
+ AvahiProtocol protocol;
+ const gchar *name;
+ const gchar *type;
+ const gchar *domain;
+};
+
+static gint
+compare_resolver (GaServiceResolver *resolver,
+ struct resolverinfo *info)
+{
+ AvahiIfIndex interface;
+ AvahiProtocol protocol;
+ gchar *name;
+ gchar *type;
+ gchar *domain;
+ gint result;
+
+ g_object_get (resolver,
+ "interface", &interface,
+ "protocol", &protocol,
+ "name", &name,
+ "type", &type,
+ "domain", &domain,
+ NULL);
+
+ if (interface == info->interface
+ && protocol == info->protocol
+ && !tp_strdiff (name, info->name)
+ && !tp_strdiff (type, info->type)
+ && !tp_strdiff (domain, info->domain))
+ {
+ result = 0;
+ }
+ else
+ {
+ result = 1;
+ }
+
+ g_free (name);
+ g_free (type);
+ g_free (domain);
+ return result;
+}
+
+static GaServiceResolver *
+find_resolver (SalutAvahiOlpcActivity *self,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const gchar *name,
+ const gchar *type,
+ const gchar *domain)
+{
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+ struct resolverinfo info;
+ GSList *ret;
+
+ info.interface = interface;
+ info.protocol = protocol;
+ info.name = name;
+ info.type = type;
+ info.domain = domain;
+ ret = g_slist_find_custom (priv->resolvers, &info,
+ (GCompareFunc) compare_resolver);
+
+ return ret ? GA_SERVICE_RESOLVER (ret->data) : NULL;
+}
+
+static void
+activity_resolved_cb (GaServiceResolver *resolver,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ gchar *name,
+ gchar *type,
+ gchar *domain,
+ gchar *host_name,
+ AvahiAddress *a,
+ gint port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ SalutAvahiOlpcActivity *self)
+{
+ SalutOlpcActivity *act = SALUT_OLPC_ACTIVITY (self);
+ AvahiStringList *t;
+ char *activity_id = NULL;
+ char *color = NULL;
+ char *activity_name = NULL;
+ char *activity_type = NULL;
+ char *tags = NULL;
+ char *room_name = NULL;
+ TpHandle room = 0;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) act->connection, TP_HANDLE_TYPE_ROOM);
+
+ DEBUG ("called: \"%s\".%s. on %s port %u", name, domain, host_name, port);
+
+ if ((t = avahi_string_list_find (txt, "txtvers")) != NULL)
+ {
+ char *txtvers;
+
+ avahi_string_list_get_pair (t, NULL, &txtvers, NULL);
+ if (tp_strdiff (txtvers, "0"))
+ {
+ DEBUG ("Ignoring record with txtvers not 0: %s",
+ txtvers ? txtvers : "(no value)");
+ avahi_free (txtvers);
+ return;
+ }
+ avahi_free (txtvers);
+ }
+
+ if ((t = avahi_string_list_find (txt, "room")) != NULL)
+ {
+ avahi_string_list_get_pair (t, NULL, &room_name, NULL);
+
+ room = tp_handle_ensure (room_repo, room_name, NULL, NULL);
+ avahi_free (room_name);
+ if (room == 0)
+ {
+ DEBUG ("Ignoring record with invalid room name: %s", room_name);
+ return;
+ }
+ }
+
+ if ((t = avahi_string_list_find (txt, "activity-id")) != NULL)
+ {
+ avahi_string_list_get_pair (t, NULL, &activity_id, NULL);
+ }
+
+ if ((t = avahi_string_list_find (txt, "color")) != NULL)
+ {
+ avahi_string_list_get_pair (t, NULL, &color, NULL);
+ }
+
+ if ((t = avahi_string_list_find (txt, "name")) != NULL)
+ {
+ avahi_string_list_get_pair (t, NULL, &activity_name, NULL);
+ }
+
+ if ((t = avahi_string_list_find (txt, "type")) != NULL)
+ {
+ avahi_string_list_get_pair (t, NULL, &activity_type, NULL);
+ }
+
+ if ((t = avahi_string_list_find (txt, "tags")) != NULL)
+ {
+ avahi_string_list_get_pair (t, NULL, &tags, NULL);
+ }
+
+ salut_olpc_activity_update (SALUT_OLPC_ACTIVITY (self), room,
+ activity_id, activity_name, activity_type, color, tags, FALSE);
+
+ tp_handle_unref (room_repo, room);
+ avahi_free (activity_id);
+ avahi_free (activity_type);
+ avahi_free (activity_name);
+ avahi_free (color);
+ avahi_free (tags);
+}
+
+void
+salut_avahi_olpc_activity_add_service (SalutAvahiOlpcActivity *self,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain)
+{
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+ GaServiceResolver *resolver;
+ GError *error = NULL;
+
+ resolver = find_resolver (self, interface, protocol, name, type, domain);
+ if (resolver != NULL)
+ return;
+
+ resolver = ga_service_resolver_new (interface, protocol, name, type, domain,
+ protocol, 0);
+
+ g_signal_connect (resolver, "found", G_CALLBACK (activity_resolved_cb),
+ self);
+
+ if (!ga_service_resolver_attach (resolver,
+ priv->discovery_client->avahi_client, &error))
+ {
+ g_warning ("Failed to attach resolver: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* DEBUG_RESOLVER (contact, resolver, "added"); */
+ priv->resolvers = g_slist_prepend (priv->resolvers, resolver);
+}
+
+void
+salut_avahi_olpc_activity_remove_service (SalutAvahiOlpcActivity *self,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain)
+{
+ SalutAvahiOlpcActivityPrivate *priv = SALUT_AVAHI_OLPC_ACTIVITY_GET_PRIVATE (
+ self);
+ GaServiceResolver *resolver;
+
+ resolver = find_resolver (self, interface, protocol, name, type, domain);
+
+ if (resolver == NULL)
+ return;
+
+ priv->resolvers = g_slist_remove (priv->resolvers, resolver);
+ g_object_unref (resolver);
+}
diff --git a/salut/src/avahi-olpc-activity.h b/salut/src/avahi-olpc-activity.h
new file mode 100644
index 000000000..6343261af
--- /dev/null
+++ b/salut/src/avahi-olpc-activity.h
@@ -0,0 +1,70 @@
+/*
+ * avahi-olpc-activity.h - Header for SalutAvahiOlpcActivity
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_AVAHI_OLPC_ACTIVITY_H__
+#define __SALUT_AVAHI_OLPC_ACTIVITY_H__
+
+#include <glib-object.h>
+
+#include "olpc-activity.h"
+#include "avahi-discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiOlpcActivity SalutAvahiOlpcActivity;
+typedef struct _SalutAvahiOlpcActivityClass SalutAvahiOlpcActivityClass;
+
+struct _SalutAvahiOlpcActivityClass {
+ SalutOlpcActivityClass parent_class;
+};
+
+struct _SalutAvahiOlpcActivity {
+ SalutOlpcActivity parent;
+};
+
+GType salut_avahi_olpc_activity_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_OLPC_ACTIVITY \
+ (salut_avahi_olpc_activity_get_type ())
+#define SALUT_AVAHI_OLPC_ACTIVITY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_OLPC_ACTIVITY, SalutAvahiOlpcActivity))
+#define SALUT_AVAHI_OLPC_ACTIVITY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_OLPC_ACTIVITY, SalutAvahiOlpcActivityClass))
+#define SALUT_IS_AVAHI_OLPC_ACTIVITY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_OLPC_ACTIVITY))
+#define SALUT_IS_AVAHI_OLPC_ACTIVITY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_OLPC_ACTIVITY))
+#define SALUT_AVAHI_OLPC_ACTIVITY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_OLPC_ACTIVITY, SalutAvahiOlpcActivityClass))
+
+SalutAvahiOlpcActivity * salut_avahi_olpc_activity_new (
+ SalutConnection *connection, SalutAvahiDiscoveryClient *discovery_client);
+
+void salut_avahi_olpc_activity_add_service (SalutAvahiOlpcActivity *activity,
+ AvahiIfIndex interface, AvahiProtocol protocol,
+ const char *name, const char *type, const char *domain);
+
+void salut_avahi_olpc_activity_remove_service (SalutAvahiOlpcActivity *activity,
+ AvahiIfIndex interface, AvahiProtocol protocol,
+ const char *name, const char *type, const char *domain);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_AVAHI_OLPC_ACTIVITY_H__*/
diff --git a/salut/src/avahi-roomlist-manager.c b/salut/src/avahi-roomlist-manager.c
new file mode 100644
index 000000000..a6416489e
--- /dev/null
+++ b/salut/src/avahi-roomlist-manager.c
@@ -0,0 +1,442 @@
+/*
+ * avahi-roomlist-manager.c - Source for SalutAvahiRoomlistManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "avahi-roomlist-manager.h"
+
+#include <avahi-gobject/ga-service-browser.h>
+#include <avahi-gobject/ga-service-resolver.h>
+
+#include <wocky/wocky-namespaces.h>
+
+#include <gibber/gibber-muc-connection.h>
+
+#include "contact-manager.h"
+#include "tubes-channel.h"
+#include "roomlist-channel.h"
+
+#include <telepathy-glib/channel-factory-iface.h>
+#include <telepathy-glib/interfaces.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+G_DEFINE_TYPE (SalutAvahiRoomlistManager, salut_avahi_roomlist_manager,
+ SALUT_TYPE_ROOMLIST_MANAGER);
+
+/* properties */
+enum {
+ PROP_CLIENT = 1,
+ LAST_PROP
+};
+
+/* private structure */
+typedef struct _SalutAvahiRoomlistManagerPrivate
+ SalutAvahiRoomlistManagerPrivate;
+
+struct _SalutAvahiRoomlistManagerPrivate
+{
+ SalutAvahiDiscoveryClient *discovery_client;
+ GaServiceBrowser *browser;
+
+ /* room name => GArray of GaServiceResolver* */
+ GHashTable *room_resolvers;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE(obj) \
+ ((SalutAvahiRoomlistManagerPrivate *) \
+ ((SalutAvahiRoomlistManager *) obj)->priv)
+
+static void
+room_resolver_removed (gpointer data)
+{
+ GArray *arr = (GArray *) data;
+ guint i;
+ for (i = 0; i < arr->len; i++)
+ {
+ g_object_unref (g_array_index (arr, GObject *, i));
+ }
+ g_array_unref (arr);
+}
+
+static void
+salut_avahi_roomlist_manager_init (SalutAvahiRoomlistManager *self)
+{
+ SalutAvahiRoomlistManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_AVAHI_ROOMLIST_MANAGER, SalutAvahiRoomlistManagerPrivate);
+
+ self->priv = priv;
+ priv->browser = ga_service_browser_new (SALUT_DNSSD_CLIQUE);
+
+ priv->room_resolvers = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, room_resolver_removed);
+}
+
+static void
+salut_avahi_roomlist_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiRoomlistManager *self = SALUT_AVAHI_ROOMLIST_MANAGER (object);
+ SalutAvahiRoomlistManagerPrivate *priv =
+ SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_roomlist_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiRoomlistManager *self = SALUT_AVAHI_ROOMLIST_MANAGER (object);
+ SalutAvahiRoomlistManagerPrivate *priv =
+ SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CLIENT:
+ priv->discovery_client = g_value_get_object (value);
+ g_object_ref (priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void salut_avahi_roomlist_manager_dispose (GObject *object);
+
+static void
+browser_found (GaServiceBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain,
+ GaLookupResultFlags flags,
+ SalutAvahiRoomlistManager *self)
+{
+ SalutAvahiRoomlistManagerPrivate *priv =
+ SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE (self);
+ GArray *arr;
+ GaServiceResolver *resolver;
+ GError *error = NULL;
+
+ DEBUG ("found room: %s.%s.%s", name, type, domain);
+ resolver = ga_service_resolver_new (interface, protocol,
+ name, type, domain, protocol, 0);
+
+ if (!ga_service_resolver_attach (resolver,
+ priv->discovery_client->avahi_client, &error))
+ {
+ DEBUG ("resolver attach failed: %s", error->message);
+ g_object_unref (resolver);
+ g_error_free (error);
+ return;
+ }
+
+ arr = g_hash_table_lookup (priv->room_resolvers, name);
+ if (arr == NULL)
+ {
+ arr = g_array_new (FALSE, FALSE, sizeof (GObject *));
+ g_hash_table_insert (priv->room_resolvers, g_strdup (name), arr);
+ salut_roomlist_manager_room_discovered (SALUT_ROOMLIST_MANAGER (self),
+ name);
+ }
+ g_array_append_val (arr, resolver);
+}
+
+static void
+browser_removed (GaServiceBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ const char *name,
+ const char *type,
+ const char *domain,
+ GaLookupResultFlags flags,
+ SalutAvahiRoomlistManager *self)
+{
+ SalutAvahiRoomlistManagerPrivate *priv =
+ SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE (self);
+ GArray *arr;
+ guint i;
+
+ arr = g_hash_table_lookup (priv->room_resolvers, name);
+
+ if (arr == NULL) {
+ DEBUG ("Browser removed for %s, but didn't have any resolvers", name);
+ return;
+ }
+
+ for (i = 0; i < arr->len; i++)
+ {
+ GaServiceResolver *resolver;
+ AvahiIfIndex r_interface;
+ AvahiProtocol r_protocol;
+ gchar *r_name;
+ gchar *r_type;
+ gchar *r_domain;
+
+ resolver = g_array_index (arr, GaServiceResolver *, i);
+ g_object_get ((gpointer) resolver,
+ "interface", &r_interface,
+ "protocol", &r_protocol,
+ "name", &r_name,
+ "type", &r_type,
+ "domain", &r_domain,
+ NULL);
+ if (interface == r_interface
+ && protocol == r_protocol
+ && !tp_strdiff (name, r_name)
+ && !tp_strdiff (type, r_type)
+ && !tp_strdiff (domain, r_domain))
+ {
+ g_free (r_name);
+ g_free (r_type);
+ g_free (r_domain);
+ g_object_unref (resolver);
+ g_array_remove_index_fast (arr, i);
+ break;
+ }
+
+ g_free (r_name);
+ g_free (r_type);
+ g_free (r_domain);
+ }
+
+ if (arr->len > 0)
+ return;
+
+ DEBUG ("remove room: %s.%s.%s", name, type, domain);
+
+ g_hash_table_remove (priv->room_resolvers, name);
+
+ salut_roomlist_manager_room_removed (SALUT_ROOMLIST_MANAGER (self), name);
+}
+
+static void
+browser_failed (GaServiceBrowser *browser,
+ GError *error,
+ SalutAvahiRoomlistManager *self)
+{
+ /* FIXME proper error handling */
+ DEBUG ("browser failed -> %s", error->message);
+}
+
+static gboolean
+salut_avahi_roomlist_manager_start (SalutRoomlistManager *mgr,
+ GError **error)
+{
+ SalutAvahiRoomlistManager *self = SALUT_AVAHI_ROOMLIST_MANAGER (mgr);
+ SalutAvahiRoomlistManagerPrivate *priv =
+ SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ g_signal_connect (priv->browser, "new-service",
+ G_CALLBACK (browser_found), self);
+ g_signal_connect (priv->browser, "removed-service",
+ G_CALLBACK (browser_removed), self);
+ g_signal_connect (priv->browser, "failure",
+ G_CALLBACK (browser_failed), self);
+
+ if (!ga_service_browser_attach (priv->browser,
+ priv->discovery_client->avahi_client, error))
+ {
+ DEBUG ("browser attach failed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gchar *
+_avahi_address_to_string_address (const AvahiAddress *address)
+{
+ gchar str[AVAHI_ADDRESS_STR_MAX];
+
+ if (avahi_address_snprint (str, sizeof (str), address) == NULL)
+ {
+ DEBUG ("Failed to convert AvahiAddress to string");
+ return NULL;
+ }
+ return g_strdup (str);
+}
+
+static gboolean
+salut_avahi_roomlist_manager_find_muc_address (SalutRoomlistManager *mgr,
+ const gchar *name,
+ gchar **address,
+ guint16 *port)
+{
+ SalutAvahiRoomlistManager *self = SALUT_AVAHI_ROOMLIST_MANAGER (mgr);
+ SalutAvahiRoomlistManagerPrivate *priv =
+ SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE (self);
+ GArray *arr;
+ AvahiAddress avahi_address;
+ guint i;
+
+ arr = g_hash_table_lookup (priv->room_resolvers, name);
+ if (arr == NULL || arr->len == 0)
+ return FALSE;
+
+ for (i = 0; i < arr->len; i++)
+ {
+ GaServiceResolver *resolver;
+ resolver = g_array_index (arr, GaServiceResolver *, i);
+
+ if (!ga_service_resolver_get_address (resolver, &avahi_address, port))
+ {
+ DEBUG ("..._get_address failed: creating a new MUC room instead");
+ return FALSE;
+ }
+ else
+ {
+ *address = _avahi_address_to_string_address (&avahi_address);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+add_room_to_list (const gchar *room,
+ GaServiceResolver *resolver,
+ GSList **list)
+{
+ *list = g_slist_prepend (*list, (gchar *) room);
+}
+
+static GSList *
+salut_avahi_roomlist_manager_get_rooms (SalutRoomlistManager *mgr)
+{
+ SalutAvahiRoomlistManager *self = SALUT_AVAHI_ROOMLIST_MANAGER (mgr);
+ SalutAvahiRoomlistManagerPrivate *priv =
+ SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE (self);
+ GSList *rooms = NULL;
+
+ g_hash_table_foreach (priv->room_resolvers, (GHFunc) add_room_to_list,
+ &rooms);
+
+ return rooms;
+}
+
+static void
+salut_avahi_roomlist_manager_class_init (
+ SalutAvahiRoomlistManagerClass *salut_avahi_roomlist_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (
+ salut_avahi_roomlist_manager_class);
+ SalutRoomlistManagerClass *roomlist_manager_class =
+ SALUT_ROOMLIST_MANAGER_CLASS (
+ salut_avahi_roomlist_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_avahi_roomlist_manager_class,
+ sizeof (SalutAvahiRoomlistManagerPrivate));
+
+ object_class->get_property = salut_avahi_roomlist_manager_get_property;
+ object_class->set_property = salut_avahi_roomlist_manager_set_property;
+
+ object_class->dispose = salut_avahi_roomlist_manager_dispose;
+
+ roomlist_manager_class->start = salut_avahi_roomlist_manager_start;
+ roomlist_manager_class->find_muc_address =
+ salut_avahi_roomlist_manager_find_muc_address;
+ roomlist_manager_class->get_rooms = salut_avahi_roomlist_manager_get_rooms;
+
+ param_spec = g_param_spec_object (
+ "discovery-client",
+ "SalutAvahiDiscoveryClient object",
+ "The Salut Avahi Discovery client associated with this roomlist manager",
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CLIENT,
+ param_spec);
+}
+
+static void
+salut_avahi_roomlist_manager_dispose (GObject *object)
+{
+ SalutAvahiRoomlistManager *self = SALUT_AVAHI_ROOMLIST_MANAGER (object);
+ SalutAvahiRoomlistManagerPrivate *priv =
+ SALUT_AVAHI_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->room_resolvers != NULL)
+ {
+ g_hash_table_unref (priv->room_resolvers);
+ priv->room_resolvers = NULL;
+ }
+
+ if (priv->browser != NULL)
+ {
+ g_object_unref (priv->browser);
+ priv->browser = NULL;
+ }
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ priv->discovery_client = NULL;
+ }
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (salut_avahi_roomlist_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_avahi_roomlist_manager_parent_class)->dispose (
+ object);
+}
+
+/* public functions */
+SalutAvahiRoomlistManager *
+salut_avahi_roomlist_manager_new (
+ SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client)
+{
+ g_assert (connection != NULL);
+ g_assert (discovery_client != NULL);
+
+ return g_object_new (SALUT_TYPE_AVAHI_ROOMLIST_MANAGER,
+ "connection", connection,
+ "discovery-client", discovery_client,
+ NULL);
+}
diff --git a/salut/src/avahi-roomlist-manager.h b/salut/src/avahi-roomlist-manager.h
new file mode 100644
index 000000000..bc891bef1
--- /dev/null
+++ b/salut/src/avahi-roomlist-manager.h
@@ -0,0 +1,69 @@
+/*
+ * avahi-roomlist-manager.h - Header for SalutAvahiRoomlistManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_AVAHI_ROOMLIST_MANAGER_H__
+#define __SALUT_AVAHI_ROOMLIST_MANAGER_H__
+
+#include <glib-object.h>
+
+#include "roomlist-manager.h"
+#include "connection.h"
+#include "avahi-discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiRoomlistManager SalutAvahiRoomlistManager;
+typedef struct _SalutAvahiRoomlistManagerClass SalutAvahiRoomlistManagerClass;
+
+struct _SalutAvahiRoomlistManagerClass {
+ SalutRoomlistManagerClass parent_class;
+};
+
+struct _SalutAvahiRoomlistManager {
+ SalutRoomlistManager parent;
+
+ gpointer priv;
+};
+
+GType salut_avahi_roomlist_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_ROOMLIST_MANAGER \
+ (salut_avahi_roomlist_manager_get_type ())
+#define SALUT_AVAHI_ROOMLIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_ROOMLIST_MANAGER, \
+ SalutAvahiRoomlistManager))
+#define SALUT_AVAHI_ROOMLIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_ROOMLIST_MANAGER, \
+ SalutAvahiRoomlistManagerClass))
+#define SALUT_IS_AVAHI_ROOMLIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_ROOMLIST_MANAGER))
+#define SALUT_IS_AVAHI_ROOMLIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_ROOMLIST_MANAGER))
+#define SALUT_AVAHI_ROOMLIST_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_ROOMLIST_MANAGER, \
+ SalutAvahiRoomlistManagerClass))
+
+SalutAvahiRoomlistManager *
+salut_avahi_roomlist_manager_new (SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_AVAHI_ROOMLIST_MANAGER_H__*/
diff --git a/salut/src/avahi-self.c b/salut/src/avahi-self.c
new file mode 100644
index 000000000..4b839500a
--- /dev/null
+++ b/salut/src/avahi-self.c
@@ -0,0 +1,557 @@
+/*
+ * avahi-self.c - Source for SalutAvahiSelf
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <avahi-gobject/ga-entry-group.h>
+
+#define DEBUG_FLAG DEBUG_SELF
+#include "debug.h"
+
+#include "avahi-self.h"
+
+#include "sha1/sha1-util.h"
+
+#ifdef ENABLE_OLPC
+#define KEY_SEGMENT_SIZE 200
+#endif
+
+G_DEFINE_TYPE (SalutAvahiSelf, salut_avahi_self, SALUT_TYPE_SELF);
+
+/* properties */
+enum
+{
+ PROP_DISCOVERY_CLIENT = 1,
+ LAST_PROPERTY
+};
+
+struct _SalutAvahiSelfPrivate
+{
+ SalutAvahiDiscoveryClient *discovery_client;
+ GaEntryGroup *presence_group;
+ GaEntryGroupService *presence;
+ GaEntryGroup *avatar_group;
+
+ gboolean dispose_has_run;
+};
+
+static void
+salut_avahi_self_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (object);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ switch (property_id) {
+ case PROP_DISCOVERY_CLIENT:
+ g_value_set_object (value, priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_self_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (object);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ switch (property_id) {
+ case PROP_DISCOVERY_CLIENT:
+ priv->discovery_client = g_value_get_object (value);
+ g_object_ref (priv->discovery_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_avahi_self_init (SalutAvahiSelf *self)
+{
+ SalutAvahiSelfPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_AVAHI_SELF, SalutAvahiSelfPrivate);
+
+ self->priv = priv;
+
+ priv->discovery_client = NULL;
+}
+
+static void
+_avahi_presence_group_established (GaEntryGroup *group,
+ GaEntryGroupState state,
+ SalutAvahiSelf *self)
+{
+ salut_self_established (SALUT_SELF (self));
+}
+
+static void
+_avahi_presence_group_failed (GaEntryGroup *group,
+ GaEntryGroupState state,
+ SalutAvahiSelf *self)
+{
+ DEBUG ("self presence group failed");
+}
+
+static AvahiStringList *
+create_txt_record (SalutAvahiSelf *self,
+ guint16 port)
+{
+ SalutSelf *_self = SALUT_SELF (self);
+ AvahiStringList *ret;
+
+ ret = avahi_string_list_new ("txtvers=1", NULL);
+
+ /* Some silly clients still use this */
+ ret = avahi_string_list_add_printf (ret, "port.p2pj=%" G_GUINT16_FORMAT, port);
+
+ if (_self->nickname != NULL)
+ ret = avahi_string_list_add_printf (ret, "nick=%s", _self->nickname);
+ if (_self->first_name != NULL)
+ ret = avahi_string_list_add_printf (ret, "1st=%s", _self->first_name);
+ if (_self->last_name != NULL)
+ ret = avahi_string_list_add_printf (ret, "last=%s", _self->last_name);
+ if (_self->email != NULL)
+ ret = avahi_string_list_add_printf (ret, "email=%s", _self->email);
+ if (_self->jid != NULL)
+ ret = avahi_string_list_add_printf (ret, "jid=%s", _self->jid);
+
+#ifdef ENABLE_OLPC
+ if (_self->olpc_color)
+ ret = avahi_string_list_add_printf (ret, "olpc-color=%s",
+ _self->olpc_color);
+
+ if (_self->olpc_key != NULL)
+ {
+ uint8_t *key = (uint8_t *) _self->olpc_key->data;
+ size_t key_len = _self->olpc_key->len;
+ guint i = 0;
+
+ while (key_len > 0)
+ {
+ size_t step = MIN (key_len, KEY_SEGMENT_SIZE);
+ gchar *name = g_strdup_printf ("olpc-key-part%u", i);
+
+ ret = avahi_string_list_add_pair_arbitrary (ret, name, key, step);
+ key += step;
+ key_len -= step;
+ i++;
+ }
+ }
+#endif
+
+ ret = avahi_string_list_add_printf (ret, "status=%s",
+ salut_presence_status_txt_names[_self->status]);
+
+ if (_self->status_message != NULL)
+ ret = avahi_string_list_add_printf (ret, "msg=%s", _self->status_message);
+
+ return ret;
+}
+
+static gboolean
+salut_avahi_self_set_caps (SalutSelf *_self,
+ GError **error)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ if (priv->presence == NULL)
+ /* Service is not announced yet */
+ return TRUE;
+
+ ga_entry_group_service_freeze (priv->presence);
+
+ if (_self->node == NULL)
+ ga_entry_group_service_remove_key (priv->presence, "node", NULL);
+ else
+ ga_entry_group_service_set (priv->presence, "node", _self->node, NULL);
+
+ if (_self->hash == NULL)
+ ga_entry_group_service_remove_key (priv->presence, "hash", NULL);
+ else
+ ga_entry_group_service_set (priv->presence, "hash", _self->hash, NULL);
+
+ if (_self->ver == NULL)
+ ga_entry_group_service_remove_key (priv->presence, "ver", NULL);
+ else
+ ga_entry_group_service_set (priv->presence, "ver", _self->ver, NULL);
+
+ return ga_entry_group_service_thaw (priv->presence, error);
+}
+
+static gboolean
+salut_avahi_self_announce (SalutSelf *_self,
+ guint16 port,
+ GError **error)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self);
+ SalutAvahiSelfPrivate *priv = self->priv;
+ AvahiStringList *txt_record = NULL;
+ const char *dnssd_name;
+
+ priv->presence_group = ga_entry_group_new ();
+
+ g_signal_connect (priv->presence_group, "state-changed::established",
+ G_CALLBACK (_avahi_presence_group_established), self);
+ g_signal_connect (priv->presence_group, "state-changed::collision",
+ G_CALLBACK (_avahi_presence_group_failed), self);
+ g_signal_connect (priv->presence_group, "state-changed::failure",
+ G_CALLBACK(_avahi_presence_group_failed), self);
+
+ if (!ga_entry_group_attach (priv->presence_group,
+ priv->discovery_client->avahi_client, error))
+ goto error;
+
+ _self->name = g_strdup_printf ("%s@%s", _self->published_name,
+ avahi_client_get_host_name (priv->discovery_client->avahi_client->avahi_client));
+
+ txt_record = create_txt_record (self, port);
+
+ dnssd_name = salut_avahi_discovery_client_get_dnssd_name (
+ priv->discovery_client);
+
+ priv->presence = ga_entry_group_add_service_strlist (priv->presence_group,
+ _self->name, dnssd_name, port, error, txt_record);
+ if (priv->presence == NULL)
+ goto error;
+
+ if (!salut_avahi_self_set_caps (_self, error))
+ goto error;
+
+ if (!ga_entry_group_commit (priv->presence_group, error))
+ goto error;
+
+ avahi_string_list_free (txt_record);
+ return TRUE;
+
+error:
+ avahi_string_list_free (txt_record);
+ return FALSE;
+}
+
+static gboolean
+salut_avahi_self_set_presence (SalutSelf *self,
+ GError **error)
+{
+ SalutAvahiSelf *avahi_self = SALUT_AVAHI_SELF (self);
+ SalutAvahiSelfPrivate *priv = avahi_self->priv;
+
+ ga_entry_group_service_freeze (priv->presence);
+ ga_entry_group_service_set (priv->presence, "status",
+ salut_presence_status_txt_names[self->status], NULL);
+
+ if (self->status_message)
+ ga_entry_group_service_set (priv->presence, "msg",
+ self->status_message, NULL);
+ else
+ ga_entry_group_service_remove_key (priv->presence, "msg", NULL);
+
+ return ga_entry_group_service_thaw (priv->presence, error);
+}
+
+static gboolean
+salut_avahi_self_set_alias (SalutSelf *_self,
+ GError **error)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ return ga_entry_group_service_set (priv->presence, "nick",
+ _self->alias, error);
+}
+
+static void
+salut_avahi_self_remove_avatar (SalutSelf *_self)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ ga_entry_group_service_remove_key (priv->presence, "phsh", NULL);
+ if (priv->avatar_group != NULL)
+ {
+ g_object_unref (priv->avatar_group);
+ priv->avatar_group = NULL;
+ }
+}
+
+static gboolean
+salut_avahi_self_publish_avatar (SalutAvahiSelf *self,
+ guint8 *data,
+ gsize size,
+ GError **error)
+{
+ SalutAvahiSelfPrivate *priv = self->priv;
+ SalutSelf *_self = SALUT_SELF (self);
+ gchar *name;
+ gboolean ret;
+ gboolean is_new = FALSE;
+ const gchar *dnssd_name;
+
+ dnssd_name = salut_avahi_discovery_client_get_dnssd_name (
+ priv->discovery_client);
+
+ name = g_strdup_printf ("%s.%s.local", _self->name, dnssd_name);
+
+ if (priv->avatar_group == NULL)
+ {
+ priv->avatar_group = ga_entry_group_new ();
+ ga_entry_group_attach (priv->avatar_group,
+ priv->discovery_client->avahi_client, NULL);
+ is_new = TRUE;
+ }
+
+ ret = ga_entry_group_add_record (priv->avatar_group,
+ is_new ? 0 : AVAHI_PUBLISH_UPDATE, name, 0xA, 120, data, size, error);
+
+ g_free (name);
+
+ if (is_new)
+ ga_entry_group_commit (priv->avatar_group, error);
+
+ return ret;
+}
+
+static gboolean
+salut_avahi_self_set_avatar (SalutSelf *_self,
+ guint8 *data,
+ gsize size,
+ GError **error)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ if (!salut_avahi_self_publish_avatar (self, data, size, error))
+ return FALSE;
+
+ _self->avatar = g_memdup (data, size);
+ _self->avatar_size = size;
+
+ if (size > 0)
+ _self->avatar_token = sha1_hex (data, size);
+
+ return ga_entry_group_service_set (priv->presence, "phsh",
+ _self->avatar_token, error);
+}
+
+#ifdef ENABLE_OLPC
+static gboolean
+salut_avahi_self_update_current_activity (SalutSelf *_self,
+ const gchar *room_name,
+ GError **error)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ ga_entry_group_service_freeze (priv->presence);
+
+ ga_entry_group_service_set (priv->presence,
+ "olpc-current-activity", _self->olpc_cur_act, NULL);
+
+ ga_entry_group_service_set (priv->presence,
+ "olpc-current-activity-room", room_name, NULL);
+
+ return ga_entry_group_service_thaw (priv->presence, error);
+}
+
+static gboolean
+salut_avahi_self_set_olpc_properties (SalutSelf *_self,
+ const GArray *key,
+ const gchar *color,
+ const gchar *jid,
+ GError **error)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ ga_entry_group_service_freeze (priv->presence);
+
+ if (key != NULL)
+ {
+ size_t key_len = key->len;
+ const guint8 *key_data = (const guint8 *) key->data;
+ guint i;
+ guint to_remove;
+
+ if (_self->olpc_key == NULL)
+ {
+ to_remove = 0;
+ }
+ else
+ {
+ to_remove = (_self->olpc_key->len + KEY_SEGMENT_SIZE - 1) /
+ KEY_SEGMENT_SIZE;
+ }
+
+ i = 0;
+ while (key_len > 0)
+ {
+ size_t step = MIN (key_len, KEY_SEGMENT_SIZE);
+ gchar *name = g_strdup_printf ("olpc-key-part%u", i);
+
+ ga_entry_group_service_set_arbitrary (priv->presence, name,
+ key_data, step, NULL);
+ g_free (name);
+
+ key_data += step;
+ key_len -= step;
+ i++;
+ }
+
+ /* if the new key is shorter than the old, clean up any stray segments */
+ while (i < to_remove)
+ {
+ gchar *name = g_strdup_printf ("olpc-key-part%u", i);
+
+ ga_entry_group_service_remove_key (priv->presence, name,
+ NULL);
+ g_free (name);
+
+ i++;
+ }
+ }
+
+ if (color != NULL)
+ {
+ ga_entry_group_service_set (priv->presence, "olpc-color",
+ color, NULL);
+ }
+
+ if (jid != NULL)
+ {
+ ga_entry_group_service_set (priv->presence, "jid",
+ jid, NULL);
+ }
+
+ return ga_entry_group_service_thaw (priv->presence, error);
+}
+#endif
+
+static void salut_avahi_self_dispose (GObject *object);
+
+static void
+salut_avahi_self_class_init (
+ SalutAvahiSelfClass *salut_avahi_self_class) {
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_self_class);
+ SalutSelfClass *self_class = SALUT_SELF_CLASS (
+ salut_avahi_self_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_avahi_self_class,
+ sizeof (SalutAvahiSelfPrivate));
+
+ object_class->dispose = salut_avahi_self_dispose;
+
+ object_class->get_property = salut_avahi_self_get_property;
+ object_class->set_property = salut_avahi_self_set_property;
+
+ self_class->announce = salut_avahi_self_announce;
+ self_class->set_presence = salut_avahi_self_set_presence;
+ self_class->set_caps = salut_avahi_self_set_caps;
+ self_class->set_alias = salut_avahi_self_set_alias;
+ self_class->remove_avatar = salut_avahi_self_remove_avatar;
+ self_class->set_avatar = salut_avahi_self_set_avatar;
+#ifdef ENABLE_OLPC
+ self_class->update_current_activity =
+ salut_avahi_self_update_current_activity;
+ self_class->set_olpc_properties = salut_avahi_self_set_olpc_properties;
+#endif
+
+ param_spec = g_param_spec_object (
+ "discovery-client",
+ "SalutAvahiDiscoveryClient object",
+ "The Salut Avahi Discovery client associated with this self object",
+ SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DISCOVERY_CLIENT,
+ param_spec);
+}
+
+void
+salut_avahi_self_dispose (GObject *object)
+{
+ SalutAvahiSelf *self = SALUT_AVAHI_SELF (object);
+ SalutAvahiSelfPrivate *priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->presence_group != NULL)
+ {
+ g_object_unref (priv->presence_group);
+ priv->presence_group = NULL;
+ }
+
+ if (priv->avatar_group != NULL)
+ {
+ g_object_unref (priv->avatar_group);
+ priv->avatar_group = NULL;
+ }
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ priv->discovery_client = NULL;
+ }
+
+ if (G_OBJECT_CLASS (salut_avahi_self_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_avahi_self_parent_class)->dispose (object);
+}
+
+SalutAvahiSelf *
+salut_avahi_self_new (SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client,
+ const gchar *nickname,
+ const gchar *first_name,
+ const gchar *last_name,
+ const gchar *jid,
+ const gchar *email,
+ const gchar *published_name,
+ const GArray *olpc_key,
+ const gchar *olpc_color)
+{
+ return g_object_new (SALUT_TYPE_AVAHI_SELF,
+ "connection", connection,
+ "discovery-client", discovery_client,
+ "nickname", nickname,
+ "first-name", first_name,
+ "last-name", last_name,
+ "jid", jid,
+ "email", email,
+ "published-name", published_name,
+#ifdef ENABLE_OLPC
+ "olpc-key", olpc_key,
+ "olpc-color", olpc_color,
+#endif
+ NULL);
+}
diff --git a/salut/src/avahi-self.h b/salut/src/avahi-self.h
new file mode 100644
index 000000000..4ebe942c5
--- /dev/null
+++ b/salut/src/avahi-self.h
@@ -0,0 +1,69 @@
+/*
+ * avahi-self.h - Header for SalutAvahiSelf
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#ifndef __SALUT_AVAHI_SELF_H__
+#define __SALUT_AVAHI_SELF_H__
+
+#include <glib-object.h>
+
+#include "self.h"
+#include "connection.h"
+#include "avahi-discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutAvahiSelf SalutAvahiSelf;
+typedef struct _SalutAvahiSelfClass SalutAvahiSelfClass;
+typedef struct _SalutAvahiSelfPrivate SalutAvahiSelfPrivate;
+
+struct _SalutAvahiSelfClass {
+ SalutSelfClass parent_class;
+};
+
+struct _SalutAvahiSelf {
+ SalutSelf parent;
+
+ SalutAvahiSelfPrivate *priv;
+};
+
+
+GType salut_avahi_self_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_AVAHI_SELF \
+ (salut_avahi_self_get_type ())
+#define SALUT_AVAHI_SELF(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_AVAHI_SELF, SalutAvahiSelf))
+#define SALUT_AVAHI_SELF_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_AVAHI_SELF, SalutAvahiSelfClass))
+#define SALUT_IS_AVAHI_SELF(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_AVAHI_SELF))
+#define SALUT_IS_AVAHI_SELF_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_AVAHI_SELF))
+#define SALUT_AVAHI_SELF_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_AVAHI_SELF, SalutAvahiSelfClass))
+
+SalutAvahiSelf * salut_avahi_self_new (SalutConnection *connection,
+ SalutAvahiDiscoveryClient *discovery_client, const gchar *nickname,
+ const gchar *first_name, const gchar *last_name, const gchar *jid,
+ const gchar *email, const gchar *published_name, const GArray *olpc_key,
+ const gchar *olpc_color);
+
+#endif /* #ifndef __SALUT_AVAHI_SELF_H__*/
diff --git a/salut/src/capabilities.c b/salut/src/capabilities.c
new file mode 100644
index 000000000..620b5e674
--- /dev/null
+++ b/salut/src/capabilities.c
@@ -0,0 +1,62 @@
+/*
+ * capabilities.c - Connection.Interface.Capabilities constants and utilities
+ * Copyright (C) 2005-2008 Collabora Ltd.
+ * Copyright (C) 2005-2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "capabilities.h"
+
+#include <wocky/wocky-namespaces.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/channel-manager.h>
+
+#include "namespaces.h"
+
+typedef struct _Feature Feature;
+
+struct _Feature
+{
+ enum {
+ FEATURE_FIXED,
+ FEATURE_OPTIONAL,
+ } feature_type;
+ gchar *ns;
+};
+
+static const Feature self_advertised_features[] =
+{
+ { FEATURE_FIXED, WOCKY_XMPP_NS_SI},
+ { FEATURE_FIXED, WOCKY_TELEPATHY_NS_TUBES},
+ { FEATURE_FIXED, WOCKY_XMPP_NS_IQ_OOB},
+ { FEATURE_FIXED, WOCKY_XMPP_NS_X_OOB},
+ { FEATURE_FIXED, NS_TP_FT_METADATA},
+
+ { 0, NULL}
+};
+
+GabbleCapabilitySet *
+salut_dup_self_advertised_caps (void)
+{
+ GabbleCapabilitySet *ret = gabble_capability_set_new ();
+ const Feature *i;
+
+ for (i = self_advertised_features; NULL != i->ns; i++)
+ gabble_capability_set_add (ret, i->ns);
+
+ return ret;
+}
diff --git a/salut/src/capabilities.h b/salut/src/capabilities.h
new file mode 100644
index 000000000..b4c096f0e
--- /dev/null
+++ b/salut/src/capabilities.h
@@ -0,0 +1,30 @@
+/*
+ * capabilities.h - Capabilities constants and utilities
+ * Copyright (C) 2005-2008 Collabora Ltd.
+ * Copyright (C) 2005-2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_CAPABILITIES__H__
+#define __SALUT_CAPABILITIES__H__
+
+#include <glib-object.h>
+#include <salut/capability-set.h>
+
+GabbleCapabilitySet *salut_dup_self_advertised_caps (void);
+
+#endif /* __SALUT_CAPABILITIES__H__ */
+
diff --git a/salut/src/capability-set.c b/salut/src/capability-set.c
new file mode 100644
index 000000000..fe6b730f3
--- /dev/null
+++ b/salut/src/capability-set.c
@@ -0,0 +1,753 @@
+/*
+ * capabilities.c - Connection.Interface.Capabilities constants and utilities
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <salut/capabilities.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/handle-repo.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/util.h>
+
+#define DEBUG_FLAG DEBUG_PRESENCE
+#include "debug.h"
+#include "namespaces.h"
+
+typedef struct _Feature Feature;
+
+struct _Feature
+{
+ enum {
+ FEATURE_FIXED,
+ FEATURE_OPTIONAL,
+ FEATURE_OLPC
+ } feature_type;
+ gchar *ns;
+};
+
+static const Feature self_advertised_features[] =
+{
+ { FEATURE_FIXED, NS_GOOGLE_FEAT_SESSION },
+ { FEATURE_FIXED, NS_JINGLE_TRANSPORT_RAWUDP },
+ { FEATURE_FIXED, NS_JINGLE015 },
+ { FEATURE_FIXED, NS_JINGLE032 },
+ { FEATURE_FIXED, NS_CHAT_STATES },
+ { FEATURE_FIXED, NS_NICK },
+ { FEATURE_FIXED, NS_NICK "+notify" },
+ { FEATURE_FIXED, NS_SI },
+ { FEATURE_FIXED, NS_TUBES },
+ { FEATURE_FIXED, NS_BYTESTREAMS },
+ { FEATURE_OPTIONAL, NS_FILE_TRANSFER },
+ { FEATURE_OPTIONAL, NS_TP_FT_METADATA },
+
+ { FEATURE_OPTIONAL, NS_GOOGLE_TRANSPORT_P2P },
+ { FEATURE_OPTIONAL, NS_JINGLE_TRANSPORT_ICEUDP },
+
+ { FEATURE_OPTIONAL, NS_GOOGLE_FEAT_SHARE },
+ { FEATURE_OPTIONAL, NS_GOOGLE_FEAT_VOICE },
+ { FEATURE_OPTIONAL, NS_GOOGLE_FEAT_VIDEO },
+ { FEATURE_OPTIONAL, NS_JINGLE_DESCRIPTION_AUDIO },
+ { FEATURE_OPTIONAL, NS_JINGLE_DESCRIPTION_VIDEO },
+ { FEATURE_OPTIONAL, NS_JINGLE_RTP },
+ { FEATURE_OPTIONAL, NS_JINGLE_RTP_AUDIO },
+ { FEATURE_OPTIONAL, NS_JINGLE_RTP_VIDEO },
+
+ { FEATURE_OLPC, NS_OLPC_BUDDY_PROPS "+notify" },
+ { FEATURE_OLPC, NS_OLPC_ACTIVITIES "+notify" },
+ { FEATURE_OLPC, NS_OLPC_CURRENT_ACTIVITY "+notify" },
+ { FEATURE_OLPC, NS_OLPC_ACTIVITY_PROPS "+notify" },
+
+ { FEATURE_OPTIONAL, NS_GEOLOC "+notify" },
+
+ { 0, NULL }
+};
+
+static const Feature quirks[] = {
+ { 0, QUIRK_OMITS_CONTENT_CREATORS },
+ { 0, NULL }
+};
+
+static GabbleCapabilitySet *legacy_caps = NULL;
+static GabbleCapabilitySet *share_v1_caps = NULL;
+static GabbleCapabilitySet *voice_v1_caps = NULL;
+static GabbleCapabilitySet *video_v1_caps = NULL;
+static GabbleCapabilitySet *any_audio_caps = NULL;
+static GabbleCapabilitySet *any_video_caps = NULL;
+static GabbleCapabilitySet *any_audio_video_caps = NULL;
+static GabbleCapabilitySet *any_google_av_caps = NULL;
+static GabbleCapabilitySet *any_jingle_av_caps = NULL;
+static GabbleCapabilitySet *any_transport_caps = NULL;
+static GabbleCapabilitySet *fixed_caps = NULL;
+static GabbleCapabilitySet *geoloc_caps = NULL;
+static GabbleCapabilitySet *olpc_caps = NULL;
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_legacy (void)
+{
+ return legacy_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_bundle_share_v1 (void)
+{
+ return share_v1_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_bundle_voice_v1 (void)
+{
+ return voice_v1_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_bundle_video_v1 (void)
+{
+ return video_v1_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_any_audio (void)
+{
+ return any_audio_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_any_video (void)
+{
+ return any_video_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_any_audio_video (void)
+{
+ return any_audio_video_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_any_google_av (void)
+{
+ return any_google_av_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_any_jingle_av (void)
+{
+ return any_jingle_av_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_any_transport (void)
+{
+ return any_transport_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_fixed_caps (void)
+{
+ return fixed_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_geoloc_notify (void)
+{
+ return geoloc_caps;
+}
+
+const GabbleCapabilitySet *
+gabble_capabilities_get_olpc_notify (void)
+{
+ return olpc_caps;
+}
+
+static gboolean
+omits_content_creators (WockyNode *identity)
+{
+ const gchar *name, *suffix;
+ gchar *end;
+ int ver;
+
+ name = wocky_node_get_attribute (identity, "name");
+
+ if (name == NULL)
+ return FALSE;
+
+#define PREFIX "Telepathy Gabble 0.7."
+
+ if (!g_str_has_prefix (name, PREFIX))
+ return FALSE;
+
+ suffix = name + strlen (PREFIX);
+ ver = strtol (suffix, &end, 10);
+
+ if (*end != '\0')
+ return FALSE;
+
+ /* Gabble versions since 0.7.16 did not send the creator='' attribute for
+ * contents. The bug is fixed in 0.7.29.
+ */
+ if (ver >= 16 && ver < 29)
+ {
+ DEBUG ("contact is using '%s' which omits 'creator'", name);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static gsize feature_handles_refcount = 0;
+/* The handles in this repository are not really handles in the tp-spec sense
+ * of the word; we're just using it as a convenient implementation of a
+ * refcounted string pool. Their string values are either XMPP namespaces,
+ * or "quirk" pseudo-namespaces starting with QUIRK_PREFIX_CHAR (like
+ * QUIRK_OMITS_CONTENT_CREATORS). */
+static TpHandleRepoIface *feature_handles = NULL;
+
+void
+gabble_capabilities_init (gpointer conn)
+{
+ DEBUG ("%p", conn);
+
+ if (feature_handles_refcount++ == 0)
+ {
+ const Feature *feat;
+
+ g_assert (feature_handles == NULL);
+ /* TpDynamicHandleRepo wants a handle type, which isn't relevant here
+ * (we're just using it as a string pool). Use an arbitrary handle type
+ * to shut it up. */
+ feature_handles = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_CONTACT,
+ NULL, NULL);
+
+ /* make the pre-cooked bundles */
+
+ legacy_caps = gabble_capability_set_new ();
+
+ for (feat = self_advertised_features; feat->ns != NULL; feat++)
+ {
+ gabble_capability_set_add (legacy_caps, feat->ns);
+ }
+
+ share_v1_caps = gabble_capability_set_new ();
+ gabble_capability_set_add (share_v1_caps, NS_GOOGLE_FEAT_SHARE);
+
+ voice_v1_caps = gabble_capability_set_new ();
+ gabble_capability_set_add (voice_v1_caps, NS_GOOGLE_FEAT_VOICE);
+
+ video_v1_caps = gabble_capability_set_new ();
+ gabble_capability_set_add (video_v1_caps, NS_GOOGLE_FEAT_VIDEO);
+
+ any_audio_caps = gabble_capability_set_new ();
+ gabble_capability_set_add (any_audio_caps, NS_JINGLE_RTP_AUDIO);
+ gabble_capability_set_add (any_audio_caps, NS_JINGLE_DESCRIPTION_AUDIO);
+ gabble_capability_set_add (any_audio_caps, NS_GOOGLE_FEAT_VOICE);
+
+ any_video_caps = gabble_capability_set_new ();
+ gabble_capability_set_add (any_video_caps, NS_JINGLE_RTP_VIDEO);
+ gabble_capability_set_add (any_video_caps, NS_JINGLE_DESCRIPTION_VIDEO);
+ gabble_capability_set_add (any_video_caps, NS_GOOGLE_FEAT_VIDEO);
+
+ any_audio_video_caps = gabble_capability_set_copy (any_audio_caps);
+ gabble_capability_set_update (any_audio_video_caps, any_video_caps);
+
+ any_google_av_caps = gabble_capability_set_new ();
+ gabble_capability_set_add (any_google_av_caps, NS_GOOGLE_FEAT_VOICE);
+ gabble_capability_set_add (any_google_av_caps, NS_GOOGLE_FEAT_VIDEO);
+
+ any_jingle_av_caps = gabble_capability_set_copy (any_audio_caps);
+ gabble_capability_set_update (any_jingle_av_caps, any_video_caps);
+ gabble_capability_set_exclude (any_jingle_av_caps, any_google_av_caps);
+
+ any_transport_caps = gabble_capability_set_new ();
+ gabble_capability_set_add (any_transport_caps, NS_GOOGLE_TRANSPORT_P2P);
+ gabble_capability_set_add (any_transport_caps, NS_JINGLE_TRANSPORT_ICEUDP);
+ gabble_capability_set_add (any_transport_caps, NS_JINGLE_TRANSPORT_RAWUDP);
+
+ fixed_caps = gabble_capability_set_new ();
+
+ for (feat = self_advertised_features; feat->ns != NULL; feat++)
+ {
+ if (feat->feature_type == FEATURE_FIXED)
+ gabble_capability_set_add (fixed_caps, feat->ns);
+ }
+
+ geoloc_caps = gabble_capability_set_new ();
+ gabble_capability_set_add (geoloc_caps, NS_GEOLOC "+notify");
+
+ olpc_caps = gabble_capability_set_new ();
+
+ for (feat = self_advertised_features; feat->ns != NULL; feat++)
+ {
+ if (feat->feature_type == FEATURE_OLPC)
+ gabble_capability_set_add (olpc_caps, feat->ns);
+ }
+ }
+
+ g_assert (feature_handles != NULL);
+}
+
+void
+gabble_capabilities_finalize (gpointer conn)
+{
+ DEBUG ("%p", conn);
+
+ g_assert (feature_handles_refcount > 0);
+
+ if (--feature_handles_refcount == 0)
+ {
+ gabble_capability_set_free (legacy_caps);
+ gabble_capability_set_free (share_v1_caps);
+ gabble_capability_set_free (voice_v1_caps);
+ gabble_capability_set_free (video_v1_caps);
+ gabble_capability_set_free (any_audio_caps);
+ gabble_capability_set_free (any_video_caps);
+ gabble_capability_set_free (any_audio_video_caps);
+ gabble_capability_set_free (any_google_av_caps);
+ gabble_capability_set_free (any_jingle_av_caps);
+ gabble_capability_set_free (any_transport_caps);
+ gabble_capability_set_free (fixed_caps);
+ gabble_capability_set_free (geoloc_caps);
+ gabble_capability_set_free (olpc_caps);
+
+ legacy_caps = NULL;
+ share_v1_caps = NULL;
+ voice_v1_caps = NULL;
+ video_v1_caps = NULL;
+ any_audio_caps = NULL;
+ any_video_caps = NULL;
+ any_audio_video_caps = NULL;
+ any_google_av_caps = NULL;
+ any_jingle_av_caps = NULL;
+ any_transport_caps = NULL;
+ fixed_caps = NULL;
+ geoloc_caps = NULL;
+ olpc_caps = NULL;
+
+ tp_clear_object (&feature_handles);
+ }
+}
+
+struct _GabbleCapabilitySet {
+ TpHandleSet *handles;
+};
+
+GabbleCapabilitySet *
+gabble_capability_set_new (void)
+{
+ GabbleCapabilitySet *ret = g_slice_new0 (GabbleCapabilitySet);
+
+ g_assert (feature_handles != NULL);
+ ret->handles = tp_handle_set_new (feature_handles);
+ return ret;
+}
+
+GabbleCapabilitySet *
+gabble_capability_set_new_from_stanza (WockyNode *query_result)
+{
+ GabbleCapabilitySet *ret;
+ const gchar *var;
+ GSList *ni;
+
+ g_return_val_if_fail (query_result != NULL, NULL);
+
+ ret = gabble_capability_set_new ();
+
+ for (ni = query_result->children; ni != NULL; ni = g_slist_next (ni))
+ {
+ WockyNode *child = ni->data;
+
+ if (!tp_strdiff (child->name, "identity"))
+ {
+ if (omits_content_creators (child))
+ gabble_capability_set_add (ret, QUIRK_OMITS_CONTENT_CREATORS);
+
+ continue;
+ }
+
+ if (tp_strdiff (child->name, "feature"))
+ continue;
+
+ var = wocky_node_get_attribute (child, "var");
+
+ if (NULL == var)
+ continue;
+
+ if (G_UNLIKELY (var[0] == QUIRK_PREFIX_CHAR))
+ {
+ /* I think not! (It's not allowed in XML...) */
+ continue;
+ }
+
+ /* TODO: only store namespaces we understand. */
+ gabble_capability_set_add (ret, var);
+ }
+
+ return ret;
+}
+
+GabbleCapabilitySet *
+gabble_capability_set_copy (const GabbleCapabilitySet *caps)
+{
+ GabbleCapabilitySet *ret;
+
+ g_return_val_if_fail (caps != NULL, NULL);
+
+ ret = gabble_capability_set_new ();
+
+ gabble_capability_set_update (ret, caps);
+
+ return ret;
+}
+
+void
+gabble_capability_set_update (GabbleCapabilitySet *target,
+ const GabbleCapabilitySet *source)
+{
+ TpIntSet *ret;
+ g_return_if_fail (target != NULL);
+ g_return_if_fail (source != NULL);
+
+ ret = tp_handle_set_update (target->handles,
+ tp_handle_set_peek (source->handles));
+
+ tp_intset_destroy (ret);
+}
+
+typedef struct {
+ GSList *deleted;
+ TpHandleSet *intersect_with;
+} IntersectHelper;
+
+static void
+intersect_helper (TpHandleSet *unused G_GNUC_UNUSED,
+ TpHandle handle,
+ gpointer p)
+{
+ IntersectHelper *data = p;
+
+ if (!tp_handle_set_is_member (data->intersect_with, handle))
+ data->deleted = g_slist_prepend (data->deleted, GUINT_TO_POINTER (handle));
+}
+
+void
+gabble_capability_set_intersect (GabbleCapabilitySet *target,
+ const GabbleCapabilitySet *source)
+{
+ IntersectHelper data = { NULL, NULL };
+
+ g_return_if_fail (target != NULL);
+ g_return_if_fail (source != NULL);
+
+ if (target == source)
+ return;
+
+ data.intersect_with = source->handles;
+
+ tp_handle_set_foreach (target->handles, intersect_helper, &data);
+
+ while (data.deleted != NULL)
+ {
+ DEBUG ("dropping %s", tp_handle_inspect (feature_handles,
+ GPOINTER_TO_UINT (data.deleted->data)));
+ tp_handle_set_remove (target->handles,
+ GPOINTER_TO_UINT (data.deleted->data));
+ data.deleted = g_slist_delete_link (data.deleted, data.deleted);
+ }
+}
+
+static void
+remove_from_set (TpHandleSet *unused G_GNUC_UNUSED,
+ TpHandle handle,
+ gpointer handles)
+{
+ tp_handle_set_remove (handles, handle);
+}
+
+void
+gabble_capability_set_exclude (GabbleCapabilitySet *caps,
+ const GabbleCapabilitySet *removed)
+{
+ g_return_if_fail (caps != NULL);
+ g_return_if_fail (removed != NULL);
+
+ if (caps == removed)
+ {
+ gabble_capability_set_clear (caps);
+ return;
+ }
+
+ tp_handle_set_foreach (removed->handles, remove_from_set, caps->handles);
+}
+
+void
+gabble_capability_set_add (GabbleCapabilitySet *caps,
+ const gchar *cap)
+{
+ TpHandle handle;
+
+ g_return_if_fail (caps != NULL);
+ g_return_if_fail (cap != NULL);
+
+ handle = tp_handle_ensure (feature_handles, cap, NULL, NULL);
+
+ tp_handle_set_add (caps->handles, handle);
+ tp_handle_unref (feature_handles, handle);
+}
+
+gboolean
+gabble_capability_set_remove (GabbleCapabilitySet *caps,
+ const gchar *cap)
+{
+ TpHandle handle;
+
+ g_return_val_if_fail (caps != NULL, FALSE);
+ g_return_val_if_fail (cap != NULL, FALSE);
+
+ handle = tp_handle_lookup (feature_handles, cap, NULL, NULL);
+
+ if (handle == 0)
+ return FALSE;
+
+ return tp_handle_set_remove (caps->handles, handle);
+}
+
+void
+gabble_capability_set_clear (GabbleCapabilitySet *caps)
+{
+ g_return_if_fail (caps != NULL);
+
+ /* There is no tp_handle_set_clear, so do the next best thing */
+ tp_handle_set_destroy (caps->handles);
+ caps->handles = tp_handle_set_new (feature_handles);
+}
+
+void
+gabble_capability_set_free (GabbleCapabilitySet *caps)
+{
+ g_return_if_fail (caps != NULL);
+
+ tp_handle_set_destroy (caps->handles);
+ g_slice_free (GabbleCapabilitySet, caps);
+}
+
+gint
+gabble_capability_set_size (const GabbleCapabilitySet *caps)
+{
+ g_return_val_if_fail (caps != NULL, 0);
+ return tp_handle_set_size (caps->handles);
+}
+
+/* By design, this function can be used as a GabbleCapabilitySetPredicate */
+gboolean
+gabble_capability_set_has (const GabbleCapabilitySet *caps,
+ const gchar *cap)
+{
+ TpHandle handle;
+
+ g_return_val_if_fail (caps != NULL, FALSE);
+ g_return_val_if_fail (cap != NULL, FALSE);
+
+ handle = tp_handle_lookup (feature_handles, cap, NULL, NULL);
+
+ if (handle == 0)
+ {
+ /* nobody in the whole CM has this capability */
+ return FALSE;
+ }
+
+ return tp_handle_set_is_member (caps->handles, handle);
+}
+
+/* By design, this function can be used as a GabbleCapabilitySetPredicate */
+gboolean
+gabble_capability_set_has_one (const GabbleCapabilitySet *caps,
+ const GabbleCapabilitySet *alternatives)
+{
+ TpIntSetIter iter;
+
+ g_return_val_if_fail (caps != NULL, FALSE);
+ g_return_val_if_fail (alternatives != NULL, FALSE);
+
+ tp_intset_iter_init (&iter, tp_handle_set_peek (alternatives->handles));
+
+ while (tp_intset_iter_next (&iter))
+ {
+ if (tp_handle_set_is_member (caps->handles, iter.element))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* By design, this function can be used as a GabbleCapabilitySetPredicate */
+gboolean
+gabble_capability_set_at_least (const GabbleCapabilitySet *caps,
+ const GabbleCapabilitySet *query)
+{
+ TpIntSetIter iter;
+
+ g_return_val_if_fail (caps != NULL, FALSE);
+ g_return_val_if_fail (query != NULL, FALSE);
+
+ tp_intset_iter_init (&iter, tp_handle_set_peek (query->handles));
+
+ while (tp_intset_iter_next (&iter))
+ {
+ if (!tp_handle_set_is_member (caps->handles, iter.element))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+gabble_capability_set_equals (const GabbleCapabilitySet *a,
+ const GabbleCapabilitySet *b)
+{
+ g_return_val_if_fail (a != NULL, FALSE);
+ g_return_val_if_fail (b != NULL, FALSE);
+
+ return tp_intset_is_equal (tp_handle_set_peek (a->handles),
+ tp_handle_set_peek (b->handles));
+}
+
+/* Does not iterate over quirks, only real features. */
+void
+gabble_capability_set_foreach (const GabbleCapabilitySet *caps,
+ GFunc func, gpointer user_data)
+{
+ TpIntSetIter iter;
+
+ g_return_if_fail (caps != NULL);
+ g_return_if_fail (func != NULL);
+
+ tp_intset_iter_init (&iter, tp_handle_set_peek (caps->handles));
+
+ while (tp_intset_iter_next (&iter))
+ {
+ const gchar *var = tp_handle_inspect (feature_handles, iter.element);
+
+ g_return_if_fail (var != NULL);
+
+ if (var[0] != QUIRK_PREFIX_CHAR)
+ func ((gchar *) var, user_data);
+ }
+}
+
+static void
+append_intset (GString *ret,
+ const TpIntSet *cap_ints,
+ const gchar *indent)
+{
+ TpIntSetFastIter iter;
+ guint element;
+
+ tp_intset_fast_iter_init (&iter, cap_ints);
+
+ while (tp_intset_fast_iter_next (&iter, &element))
+ {
+ const gchar *var = tp_handle_inspect (feature_handles, element);
+
+ g_return_if_fail (var != NULL);
+
+ if (var[0] == QUIRK_PREFIX_CHAR)
+ {
+ g_string_append_printf (ret, "%sQuirk: %s\n", indent, var + 1);
+ }
+ else
+ {
+ g_string_append_printf (ret, "%sFeature: %s\n", indent, var);
+ }
+ }
+}
+
+gchar *
+gabble_capability_set_dump (const GabbleCapabilitySet *caps,
+ const gchar *indent)
+{
+ GString *ret;
+
+ g_return_val_if_fail (caps != NULL, NULL);
+
+ if (indent == NULL)
+ indent = "";
+
+ ret = g_string_new (indent);
+ g_string_append (ret, "--begin--\n");
+ append_intset (ret, tp_handle_set_peek (caps->handles), indent);
+ g_string_append (ret, indent);
+ g_string_append (ret, "--end--\n");
+ return g_string_free (ret, FALSE);
+}
+
+gchar *
+gabble_capability_set_dump_diff (const GabbleCapabilitySet *old_caps,
+ const GabbleCapabilitySet *new_caps,
+ const gchar *indent)
+{
+ TpIntSet *old_ints, *new_ints, *rem, *add;
+ GString *ret;
+
+ g_return_val_if_fail (old_caps != NULL, NULL);
+ g_return_val_if_fail (new_caps != NULL, NULL);
+
+ old_ints = tp_handle_set_peek (old_caps->handles);
+ new_ints = tp_handle_set_peek (new_caps->handles);
+
+ if (tp_intset_is_equal (old_ints, new_ints))
+ return g_strdup_printf ("%s--no change--", indent);
+
+ rem = tp_intset_difference (old_ints, new_ints);
+ add = tp_intset_difference (new_ints, old_ints);
+
+ ret = g_string_new ("");
+
+ if (!tp_intset_is_empty (rem))
+ {
+ g_string_append (ret, indent);
+ g_string_append (ret, "--removed--\n");
+ append_intset (ret, rem, indent);
+ }
+
+ if (!tp_intset_is_empty (add))
+ {
+ g_string_append (ret, indent);
+ g_string_append (ret, "--added--\n");
+ append_intset (ret, add, indent);
+ }
+
+ g_string_append (ret, indent);
+ g_string_append (ret, "--end--");
+
+ tp_intset_destroy (add);
+ tp_intset_destroy (rem);
+
+ return g_string_free (ret, FALSE);
+}
diff --git a/salut/src/caps-channel-manager.c b/salut/src/caps-channel-manager.c
new file mode 100644
index 000000000..c01bfd9ef
--- /dev/null
+++ b/salut/src/caps-channel-manager.c
@@ -0,0 +1,128 @@
+/*
+ * caps-channel-manager.c - interface holding capabilities functions for
+ * channel managers
+ *
+ * Copyright (C) 2008 Collabora Ltd.
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <salut/caps-channel-manager.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/channel-manager.h>
+
+
+#define DEBUG_FLAG DEBUG_PRESENCE
+#include "debug.h"
+
+GType
+gabble_caps_channel_manager_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ {
+ static const GTypeInfo info = {
+ sizeof (GabbleCapsChannelManagerIface),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ NULL, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "GabbleCapsChannelManager", &info, 0);
+
+ g_type_interface_add_prerequisite (type, TP_TYPE_CHANNEL_MANAGER);
+ }
+
+ return type;
+}
+
+/* Virtual-method wrappers */
+void
+gabble_caps_channel_manager_reset_capabilities (
+ GabbleCapsChannelManager *caps_manager)
+{
+ GabbleCapsChannelManagerIface *iface =
+ GABBLE_CAPS_CHANNEL_MANAGER_GET_INTERFACE (caps_manager);
+ GabbleCapsChannelManagerResetCapsFunc method = iface->reset_caps;
+
+ if (method != NULL)
+ {
+ method (caps_manager);
+ }
+ /* ... else assume there is no need to reset the caps */
+}
+
+void
+gabble_caps_channel_manager_get_contact_capabilities (
+ GabbleCapsChannelManager *caps_manager,
+ TpHandle handle,
+ const GabbleCapabilitySet *caps,
+ GPtrArray *arr)
+{
+ GabbleCapsChannelManagerIface *iface =
+ GABBLE_CAPS_CHANNEL_MANAGER_GET_INTERFACE (caps_manager);
+ GabbleCapsChannelManagerGetContactCapsFunc method = iface->get_contact_caps;
+
+ if (method != NULL)
+ {
+ method (caps_manager, handle, caps, arr);
+ }
+ /* ... else assume there is not caps for this kind of channels */
+}
+
+/**
+ * gabble_caps_channel_manager_represent_client:
+ * @self: a channel manager
+ * @client_name: the name of the client, for any debug messages
+ * @filters: the channel classes accepted by the client, as an array of
+ * GHashTable with string keys and GValue values
+ * @cap_tokens: the handler capability tokens supported by the client
+ * @cap_set: a set into which to merge additional XMPP capabilities
+ *
+ * Convert the capabilities of a Telepathy client into XMPP capabilities to be
+ * advertised.
+ *
+ * (The actual XMPP capabilities advertised will be the union of the XMPP
+ * capabilities of every installed client.)
+ */
+void
+gabble_caps_channel_manager_represent_client (
+ GabbleCapsChannelManager *caps_manager,
+ const gchar *client_name,
+ const GPtrArray *filters,
+ const gchar * const *cap_tokens,
+ GabbleCapabilitySet *cap_set,
+ GPtrArray *data_forms)
+{
+ GabbleCapsChannelManagerIface *iface =
+ GABBLE_CAPS_CHANNEL_MANAGER_GET_INTERFACE (caps_manager);
+ GabbleCapsChannelManagerRepresentClientFunc method = iface->represent_client;
+
+ if (method != NULL)
+ {
+ method (caps_manager, client_name, filters, cap_tokens, cap_set, data_forms);
+ }
+}
diff --git a/salut/src/caps-hash.c b/salut/src/caps-hash.c
new file mode 100644
index 000000000..caa78c0f1
--- /dev/null
+++ b/salut/src/caps-hash.c
@@ -0,0 +1,80 @@
+/*
+ * caps-hash.c - Computing verification string hash (XEP-0115 v1.5)
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Computing verification string hash (XEP-0115 v1.5)
+ *
+ * Salut does not do anything with dataforms (XEP-0128) included in
+ * capabilities. However, it needs to parse them in order to compute the hash
+ * according to XEP-0115.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <wocky/wocky-caps-hash.h>
+#include <wocky/wocky-disco-identity.h>
+#include <wocky/wocky-xep-0115-capabilities.h>
+
+#define DEBUG_FLAG SALUT_DEBUG_PRESENCE
+
+#include "debug.h"
+#include "capabilities.h"
+#include "caps-hash.h"
+#include "self.h"
+
+static void
+add_to_pointer_array_foreach (gpointer ns,
+ gpointer arr)
+{
+ g_ptr_array_add (arr, g_strdup (ns));
+}
+
+/**
+ * Compute our hash as defined by the XEP-0115.
+ *
+ * Returns: the hash. The called must free the returned hash with g_free().
+ */
+gchar *
+caps_hash_compute_from_self_presence (SalutSelf *self)
+{
+ const GabbleCapabilitySet *caps = salut_self_get_caps (self);
+ GPtrArray *features = g_ptr_array_new ();
+ GPtrArray *identities = wocky_disco_identity_array_new ();
+ const GPtrArray *dataforms =
+ wocky_xep_0115_capabilities_get_data_forms (WOCKY_XEP_0115_CAPABILITIES (self));
+ gchar *str;
+
+ gabble_capability_set_foreach (caps, add_to_pointer_array_foreach, features);
+
+ /* XEP-0030 requires at least 1 identity. We don't need more. */
+ g_ptr_array_add (identities,
+ wocky_disco_identity_new ("client", "pc",
+ NULL, PACKAGE_STRING));
+
+ str = wocky_caps_hash_compute_from_lists (features, identities,
+ (GPtrArray *) dataforms);
+
+ g_ptr_array_unref (features);
+ wocky_disco_identity_array_free (identities);
+
+ return str;
+}
+
diff --git a/salut/src/caps-hash.h b/salut/src/caps-hash.h
new file mode 100644
index 000000000..4d2ef6c83
--- /dev/null
+++ b/salut/src/caps-hash.h
@@ -0,0 +1,31 @@
+/*
+ * caps-hash.h - Headers for computing verification string hash
+ * (XEP-0115 v1.5)
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#ifndef __SALUT_CAPS_HASH_H__
+#define __SALUT_CAPS_HASH_H__
+
+#include "self.h"
+
+#include <wocky/wocky-stanza.h>
+
+gchar *caps_hash_compute_from_self_presence (SalutSelf *self);
+
+#endif /* __SALUT_CAPS_HASH_H__ */
diff --git a/salut/src/connection-contact-info.c b/salut/src/connection-contact-info.c
new file mode 100644
index 000000000..5e235fdb7
--- /dev/null
+++ b/salut/src/connection-contact-info.c
@@ -0,0 +1,375 @@
+/*
+ * connection-contact-info.c - ContactInfo implementation
+ * Copyright © 2011 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "connection-contact-info.h"
+
+#include <telepathy-glib/interfaces.h>
+/* Slightly sketchy; included for TpContactInfoFieldSpec. */
+#include <telepathy-glib/connection.h>
+#include <telepathy-glib/gtypes.h>
+
+#include "contact-manager.h"
+
+enum {
+ PROP_CONTACT_INFO_FLAGS,
+ PROP_SUPPORTED_FIELDS
+};
+
+static gchar *i_heart_the_internet[] = { "type=internet", NULL };
+
+static GPtrArray *
+get_supported_fields (void)
+{
+ static TpContactInfoFieldSpec supported_fields[] = {
+ /* We omit 'nickname' because it shows up, unmodifiably, as the alias. */
+ { "n", NULL,
+ TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 },
+ /* It's a little bit sketchy to expose 1st + ' ' + last as FN. But such
+ * are the limitations of the protocol.
+ */
+ { "fn", NULL,
+ TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 },
+ { "email", i_heart_the_internet,
+ TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 },
+ /* x-jabber is used for compatibility with Gabble */
+ { "x-jabber", NULL,
+ TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 },
+ /* Heh, we could also include the contact's IP address(es) here. */
+ { NULL }
+ };
+ static gsize supported_fields_ptr_array = 0;
+
+ if (g_once_init_enter (&supported_fields_ptr_array))
+ {
+ GPtrArray *fields = dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_FIELD_SPECS);
+ TpContactInfoFieldSpec *spec;
+
+ for (spec = supported_fields; spec->name != NULL; spec++)
+ g_ptr_array_add (fields,
+ tp_value_array_build (4,
+ G_TYPE_STRING, spec->name,
+ G_TYPE_STRV, spec->parameters,
+ G_TYPE_UINT, spec->flags,
+ G_TYPE_UINT, spec->max,
+ G_TYPE_INVALID));
+
+ g_once_init_leave (&supported_fields_ptr_array, (gsize) fields);
+ }
+
+ return (GPtrArray *) supported_fields_ptr_array;
+}
+
+static void
+salut_conn_contact_info_get_property (
+ GObject *object,
+ GQuark iface,
+ GQuark name,
+ GValue *value,
+ gpointer getter_data)
+{
+ switch (GPOINTER_TO_UINT (getter_data))
+ {
+ case PROP_CONTACT_INFO_FLAGS:
+ g_value_set_uint (value, TP_CONTACT_INFO_FLAG_PUSH);
+ break;
+ case PROP_SUPPORTED_FIELDS:
+ g_value_set_boxed (value, get_supported_fields ());
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+void
+salut_conn_contact_info_class_init (
+ SalutConnectionClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl props[] = {
+ { "ContactInfoFlags", GUINT_TO_POINTER (PROP_CONTACT_INFO_FLAGS), NULL },
+ { "SupportedFields", GUINT_TO_POINTER (PROP_SUPPORTED_FIELDS), NULL },
+ { NULL }
+ };
+
+ tp_dbus_properties_mixin_implement_interface (
+ G_OBJECT_CLASS (klass),
+ TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO,
+ salut_conn_contact_info_get_property,
+ NULL,
+ props);
+}
+
+static void
+add_singleton_field (
+ GPtrArray *contact_info,
+ const gchar *field_name,
+ gchar **parameters,
+ const gchar *value)
+{
+ const gchar *field_value[] = { value, NULL };
+
+ g_ptr_array_add (contact_info,
+ tp_value_array_build (3,
+ G_TYPE_STRING, field_name,
+ G_TYPE_STRV, parameters,
+ G_TYPE_STRV, field_value,
+ G_TYPE_INVALID));
+}
+
+static GPtrArray *
+build_contact_info (
+ const gchar *first,
+ const gchar *last,
+ const gchar *full_name,
+ const gchar *email,
+ const gchar *jid)
+{
+ GPtrArray *contact_info = dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
+
+ if (first != NULL || last != NULL)
+ {
+ const gchar *field_value[] = {
+ last != NULL ? last : "",
+ first != NULL ? first : "",
+ "",
+ "",
+ "",
+ NULL
+ };
+
+ g_ptr_array_add (contact_info,
+ tp_value_array_build (3,
+ G_TYPE_STRING, "n",
+ G_TYPE_STRV, NULL,
+ G_TYPE_STRV, field_value,
+ G_TYPE_INVALID));
+
+ g_warn_if_fail (full_name != NULL);
+ add_singleton_field (contact_info, "fn", NULL, full_name);
+ }
+
+ if (email != NULL)
+ add_singleton_field (contact_info, "email", i_heart_the_internet, email);
+
+ if (jid != NULL)
+ add_singleton_field (contact_info, "x-jabber", NULL, jid);
+
+ return contact_info;
+}
+
+static GPtrArray *
+build_contact_info_for_contact (
+ SalutContact *contact)
+{
+ g_return_val_if_fail (contact != NULL, NULL);
+
+ return build_contact_info (contact->first, contact->last, contact->full_name,
+ contact->email, contact->jid);
+}
+
+static void
+salut_conn_contact_info_fill_contact_attributes (
+ GObject *obj,
+ const GArray *contacts,
+ GHashTable *attributes_hash)
+{
+ guint i;
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ SalutContactManager *contact_manager;
+
+ g_object_get (self, "contact-manager", &contact_manager, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ GPtrArray *contact_info = NULL;
+
+ if (base->self_handle == handle)
+ {
+ /* TODO: dig contact info out of SalutSelf. There's overlap with
+ * connection parameters here … should they be DBus_Property
+ * parameters? Should we have a new flag which means “you set this on
+ * ContactInfo”? What?
+ */
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact (
+ contact_manager, handle);
+ if (contact != NULL)
+ {
+ contact_info = build_contact_info_for_contact (contact);
+ g_object_unref (contact);
+ }
+ }
+
+ if (contact_info != NULL)
+ tp_contacts_mixin_set_contact_attribute (attributes_hash,
+ handle, TP_TOKEN_CONNECTION_INTERFACE_CONTACT_INFO_INFO,
+ tp_g_value_slice_new_take_boxed (
+ TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info));
+ }
+
+ g_object_unref (contact_manager);
+}
+
+void salut_conn_contact_info_init (
+ SalutConnection *self)
+{
+ tp_contacts_mixin_add_contact_attributes_iface (
+ G_OBJECT (self),
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
+ salut_conn_contact_info_fill_contact_attributes);
+}
+
+void
+salut_conn_contact_info_changed (
+ SalutConnection *self,
+ SalutContact *contact,
+ TpHandle handle)
+{
+ GPtrArray *contact_info = build_contact_info_for_contact (contact);
+
+ tp_svc_connection_interface_contact_info_emit_contact_info_changed (self,
+ handle, contact_info);
+ g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info);
+}
+
+static void
+salut_conn_contact_info_get_contact_info (
+ TpSvcConnectionInterfaceContactInfo *iface,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *contacts_repo =
+ tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT);
+ SalutContactManager *contact_manager;
+ guint i;
+ GHashTable *ret;
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (TP_BASE_CONNECTION (iface),
+ context);
+
+ if (!tp_handles_are_valid (contacts_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ g_object_get (self, "contact-manager", &contact_manager, NULL);
+ ret = dbus_g_type_specialized_construct (TP_HASH_TYPE_CONTACT_INFO_MAP);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ SalutContact *contact = salut_contact_manager_get_contact (
+ contact_manager, handle);
+
+ if (contact != NULL)
+ {
+ g_hash_table_insert (ret, GUINT_TO_POINTER (handle),
+ build_contact_info_for_contact (contact));
+ g_object_unref (contact);
+ }
+ }
+
+ tp_svc_connection_interface_contact_info_return_from_get_contact_info (
+ context, ret);
+ g_boxed_free (TP_HASH_TYPE_CONTACT_INFO_MAP, ret);
+ g_object_unref (contact_manager);
+}
+
+static void
+salut_conn_contact_info_request_contact_info (
+ TpSvcConnectionInterfaceContactInfo *iface,
+ guint handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *contacts_repo =
+ tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (TP_BASE_CONNECTION (iface),
+ context);
+
+ if (!tp_handle_is_valid (contacts_repo, handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+ else
+ {
+ SalutContactManager *contact_manager;
+ SalutContact *contact;
+
+ g_object_get (self, "contact-manager", &contact_manager, NULL);
+ contact = salut_contact_manager_get_contact (contact_manager, handle);
+ g_object_unref (contact_manager);
+
+ if (contact != NULL)
+ {
+ GPtrArray *contact_info = build_contact_info_for_contact (contact);
+
+ tp_svc_connection_interface_contact_info_return_from_request_contact_info (
+ context, contact_info);
+ g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info);
+ }
+ else
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "No information available for '%s'",
+ tp_handle_inspect (contacts_repo, handle));
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+ }
+}
+
+static void
+salut_conn_contact_info_refresh_contact_info (
+ TpSvcConnectionInterfaceContactInfo *iface,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ /* This is a no-op on link-local XMPP: everything's always pushed to us. */
+ tp_svc_connection_interface_contact_info_return_from_refresh_contact_info (context);
+}
+
+void
+salut_conn_contact_info_iface_init (
+ gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceContactInfoClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_contact_info_implement_##x \
+ (klass, salut_conn_contact_info_##x)
+ IMPLEMENT (get_contact_info);
+ IMPLEMENT (request_contact_info);
+ IMPLEMENT (refresh_contact_info);
+#undef IMPLEMENT
+}
diff --git a/salut/src/connection-contact-info.h b/salut/src/connection-contact-info.h
new file mode 100644
index 000000000..04ece8d6a
--- /dev/null
+++ b/salut/src/connection-contact-info.h
@@ -0,0 +1,38 @@
+/*
+ * connection-contact-info.h - header for ContactInfo implementation
+ * Copyright © 2011 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef SALUT_CONNECTION_CONTACT_INFO_H
+#define SALUT_CONNECTION_CONTACT_INFO_H
+
+#include "connection.h"
+#include "contact.h"
+
+void salut_conn_contact_info_iface_init (
+ gpointer g_iface,
+ gpointer iface_data);
+void salut_conn_contact_info_class_init (
+ SalutConnectionClass *klass);
+void salut_conn_contact_info_init (
+ SalutConnection *self);
+
+void salut_conn_contact_info_changed (
+ SalutConnection *self,
+ SalutContact *contact,
+ TpHandle handle);
+
+#endif // SALUT_CONNECTION_CONTACT_INFO_H
diff --git a/salut/src/connection-manager.c b/salut/src/connection-manager.c
new file mode 100644
index 000000000..eb5d36643
--- /dev/null
+++ b/salut/src/connection-manager.c
@@ -0,0 +1,202 @@
+/*
+ * connection-manager.c - Source for SalutConnectionManager
+ * Copyright (C) 2005 Nokia Corporation
+ * Copyright (C) 2006 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "connection-manager.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <dbus/dbus-protocol.h>
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/debug-sender.h>
+
+#include "protocol.h"
+#include "connection.h"
+#include "debug.h"
+#include "plugin-loader.h"
+
+/* properties */
+enum
+{
+ PROP_BACKEND = 1,
+ LAST_PROPERTY
+};
+
+struct _SalutConnectionManagerPrivate
+{
+ GType backend_type;
+ TpBaseProtocol *protocol;
+ TpDebugSender *debug_sender;
+};
+
+#define SALUT_CONNECTION_MANAGER_GET_PRIVATE(obj) ((obj)->priv)
+
+G_DEFINE_TYPE(SalutConnectionManager, salut_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+static void
+salut_connection_manager_init (SalutConnectionManager *self)
+{
+ SalutConnectionManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_CONNECTION_MANAGER, SalutConnectionManagerPrivate);
+
+ priv->debug_sender = tp_debug_sender_dup ();
+ g_log_set_default_handler (tp_debug_sender_log_handler, G_LOG_DOMAIN);
+
+ self->priv = priv;
+}
+
+static void
+salut_connection_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutConnectionManager *self = SALUT_CONNECTION_MANAGER (object);
+ SalutConnectionManagerPrivate *priv = SALUT_CONNECTION_MANAGER_GET_PRIVATE
+ (self);
+
+ switch (property_id)
+ {
+ case PROP_BACKEND:
+ g_value_set_gtype (value, priv->backend_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_connection_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutConnectionManager *self = SALUT_CONNECTION_MANAGER (object);
+ SalutConnectionManagerPrivate *priv = SALUT_CONNECTION_MANAGER_GET_PRIVATE
+ (self);
+
+ switch (property_id)
+ {
+ case PROP_BACKEND:
+ priv->backend_type = g_value_get_gtype (value);
+
+ if (priv->protocol != NULL)
+ g_object_set (priv->protocol,
+ "backend-type", priv->backend_type,
+ NULL);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_connection_manager_constructed (GObject *object)
+{
+ SalutConnectionManager *self = SALUT_CONNECTION_MANAGER (object);
+ TpBaseConnectionManager *base = (TpBaseConnectionManager *) self;
+ void (*constructed) (GObject *) =
+ ((GObjectClass *) salut_connection_manager_parent_class)->constructed;
+ SalutPluginLoader *loader;
+
+ if (constructed != NULL)
+ constructed (object);
+
+ self->priv->protocol = salut_protocol_new (self->priv->backend_type,
+ NULL,
+ SALUT_PROTOCOL_LOCAL_XMPP_NAME,
+ SALUT_PROTOCOL_LOCAL_XMPP_ENGLISH_NAME,
+ SALUT_PROTOCOL_LOCAL_XMPP_ICON_NAME);
+ tp_base_connection_manager_add_protocol (base, self->priv->protocol);
+
+ loader = salut_plugin_loader_dup ();
+
+ salut_plugin_loader_initialize (loader, base);
+
+ g_object_unref (loader);
+}
+
+static void
+salut_connection_manager_dispose (GObject *object)
+{
+ SalutConnectionManager *self = SALUT_CONNECTION_MANAGER (object);
+ void (*dispose) (GObject *) =
+ ((GObjectClass *) salut_connection_manager_parent_class)->dispose;
+
+ tp_clear_object (&self->priv->protocol);
+
+ if (dispose != NULL)
+ dispose (object);
+}
+
+static void
+salut_connection_manager_finalize (GObject *object)
+{
+ SalutConnectionManager *self = SALUT_CONNECTION_MANAGER (object);
+ SalutConnectionManagerPrivate *priv = self->priv;
+ void (*finalize) (GObject *) =
+ ((GObjectClass *) salut_connection_manager_parent_class)->finalize;
+
+ if (priv->debug_sender != NULL)
+ {
+ g_object_unref (priv->debug_sender);
+ priv->debug_sender = NULL;
+ }
+
+ debug_free ();
+
+ if (finalize != NULL)
+ finalize (object);
+}
+
+static void
+salut_connection_manager_class_init (
+ SalutConnectionManagerClass *salut_connection_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_connection_manager_class);
+ TpBaseConnectionManagerClass *base_cm_class =
+ TP_BASE_CONNECTION_MANAGER_CLASS(salut_connection_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_connection_manager_class,
+ sizeof (SalutConnectionManagerPrivate));
+
+ object_class->get_property = salut_connection_manager_get_property;
+ object_class->set_property = salut_connection_manager_set_property;
+ object_class->constructed = salut_connection_manager_constructed;
+ object_class->dispose = salut_connection_manager_dispose;
+ object_class->finalize = salut_connection_manager_finalize;
+
+ param_spec = g_param_spec_gtype (
+ "backend-type",
+ "backend type",
+ "a G_TYPE_GTYPE of the backend to use",
+ G_TYPE_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_BACKEND,
+ param_spec);
+
+ base_cm_class->cm_dbus_name = "salut";
+}
diff --git a/salut/src/connection-manager.h b/salut/src/connection-manager.h
new file mode 100644
index 000000000..90e67feda
--- /dev/null
+++ b/salut/src/connection-manager.h
@@ -0,0 +1,63 @@
+/*
+ * connection-manager.h - Header for SalutConnectionManager
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_CONNECTION_MANAGER_H__
+#define __SALUT_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutConnectionManager SalutConnectionManager;
+typedef struct _SalutConnectionManagerClass SalutConnectionManagerClass;
+typedef struct _SalutConnectionManagerPrivate SalutConnectionManagerPrivate;
+
+struct _SalutConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+};
+
+struct _SalutConnectionManager {
+ TpBaseConnectionManager parent;
+
+ SalutConnectionManagerPrivate *priv;
+};
+
+extern const TpCMProtocolSpec salut_protocols[];
+
+GType salut_connection_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_CONNECTION_MANAGER \
+ (salut_connection_manager_get_type ())
+#define SALUT_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_CONNECTION_MANAGER, SalutConnectionManager))
+#define SALUT_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_CONNECTION_MANAGER, SalutConnectionManagerClass))
+#define SALUT_IS_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_CONNECTION_MANAGER))
+#define SALUT_IS_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_CONNECTION_MANAGER))
+#define SALUT_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_CONNECTION_MANAGER, SalutConnectionManagerClass))
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_CONNECTION_MANAGER_H__*/
diff --git a/salut/src/connection.c b/salut/src/connection.c
new file mode 100644
index 000000000..65ce591d2
--- /dev/null
+++ b/salut/src/connection.c
@@ -0,0 +1,4110 @@
+/*
+ * connection.c - Source for SalutConnection
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "connection.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ctype.h>
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <telepathy-glib/base-contact-list.h>
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/handle-repo.h>
+#include <telepathy-glib/handle-repo-static.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/util.h>
+
+#include <salut/caps-channel-manager.h>
+
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-meta-porter.h>
+#include <wocky/wocky-data-form.h>
+#include <wocky/wocky-xep-0115-capabilities.h>
+
+#include "avahi-discovery-client.h"
+#include "capabilities.h"
+#include "caps-hash.h"
+#include "connection-contact-info.h"
+#include "contact.h"
+#include "contact-manager.h"
+#include "disco.h"
+#include "discovery-client.h"
+#include "im-manager.h"
+#include "muc-manager.h"
+#include "ft-manager.h"
+#include "contact.h"
+#include "roomlist-manager.h"
+#include "presence.h"
+#include "presence-cache.h"
+#include "self.h"
+#include "si-bytestream-manager.h"
+#include "tubes-manager.h"
+#include "util.h"
+#include "namespaces.h"
+
+#include "plugin-loader.h"
+
+#ifdef ENABLE_OLPC
+#include "olpc-activity-manager.h"
+#endif
+
+#include <extensions/extensions.h>
+
+#define DEBUG_FLAG DEBUG_CONNECTION
+#include "debug.h"
+
+#ifdef ENABLE_OLPC
+
+#define ACTIVITY_PAIR_TYPE \
+ (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, \
+ G_TYPE_INVALID))
+
+static void
+salut_connection_olpc_buddy_info_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+static void
+salut_connection_olpc_activity_properties_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+#endif
+
+static void
+salut_connection_aliasing_service_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+static void
+salut_connection_avatar_service_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+static void
+salut_conn_contact_caps_iface_init (gpointer, gpointer);
+
+static void salut_conn_future_iface_init (gpointer, gpointer);
+
+#define DISCONNECT_TIMEOUT 5
+
+G_DEFINE_TYPE_WITH_CODE(SalutConnection,
+ salut_connection,
+ TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
+ salut_connection_aliasing_service_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
+ tp_presence_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ tp_contacts_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST,
+ tp_base_contact_list_mixin_list_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ tp_presence_mixin_simple_presence_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS,
+ salut_connection_avatar_service_iface_init);
+ G_IMPLEMENT_INTERFACE
+ (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ salut_conn_contact_caps_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_INFO,
+ salut_conn_contact_info_iface_init);
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_CONNECTION_FUTURE,
+ salut_conn_future_iface_init);
+#ifdef ENABLE_OLPC
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_OLPC_BUDDY_INFO,
+ salut_connection_olpc_buddy_info_iface_init);
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_OLPC_ACTIVITY_PROPERTIES,
+ salut_connection_olpc_activity_properties_iface_init);
+#endif
+ )
+
+#ifdef ENABLE_OLPC
+static gboolean uninvite_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza, gpointer user_data);
+#endif
+
+/* properties */
+enum {
+ PROP_NICKNAME = 1,
+ PROP_FIRST_NAME,
+ PROP_LAST_NAME,
+ PROP_JID,
+ PROP_EMAIL,
+ PROP_PUBLISHED_NAME,
+ PROP_IM_MANAGER,
+ PROP_MUC_MANAGER,
+ PROP_TUBES_MANAGER,
+ PROP_ROOMLIST_MANAGER,
+ PROP_CONTACT_MANAGER,
+ PROP_SELF,
+ PROP_XCM,
+ PROP_SI_BYTESTREAM_MANAGER,
+#ifdef ENABLE_OLPC
+ PROP_OLPC_ACTIVITY_MANAGER,
+#endif
+ PROP_BACKEND,
+ PROP_DNSSD_NAME,
+ LAST_PROP
+};
+
+struct _SalutConnectionPrivate
+{
+ gboolean dispose_has_run;
+
+ /* Connection information */
+ gchar *published_name;
+ gchar *nickname;
+ gchar *first_name;
+ gchar *last_name;
+ gchar *jid;
+ gchar *email;
+#ifdef ENABLE_OLPC
+ gchar *olpc_color;
+ GArray *olpc_key;
+#endif
+
+ /* Discovery client for browsing and resolving */
+ SalutDiscoveryClient *discovery_client;
+
+ /* TpHandler for our presence on the lan */
+ SalutSelf *self;
+ gboolean self_established;
+ SalutPresenceId pre_connect_presence;
+ gchar *pre_connect_message;
+ GabbleCapabilitySet *pre_connect_caps;
+ GPtrArray *pre_connect_data_forms;
+
+ /* Contact manager */
+ SalutContactManager *contact_manager;
+
+ /* IM channel manager */
+ SalutImManager *im_manager;
+
+ /* MUC channel manager */
+ SalutMucManager *muc_manager;
+
+ /* FT channel manager */
+ SalutFtManager *ft_manager;
+
+ /* Roomlist channel manager */
+ SalutRoomlistManager *roomlist_manager;
+
+ /* Tubes channel manager */
+ SalutTubesManager *tubes_manager;
+
+ /* Bytestream manager for stream initiation (XEP-0095) */
+ SalutSiBytestreamManager *si_bytestream_manager;
+
+ /* Sidecars */
+ /* gchar *interface → SalutSidecar */
+ GHashTable *sidecars;
+
+ /* gchar *interface → GList<DBusGMethodInvocation> */
+ GHashTable *pending_sidecars;
+
+#ifdef ENABLE_OLPC
+ SalutOlpcActivityManager *olpc_activity_manager;
+ guint uninvite_handler_id;
+#endif
+
+ /* timer used when trying to properly disconnect */
+ guint disconnect_timer;
+
+ /* Backend type: avahi or dummy */
+ GType backend_type;
+
+ /* DNS-SD name, used for the avahi backend */
+ gchar *dnssd_name;
+};
+
+typedef struct _ChannelRequest ChannelRequest;
+
+struct _ChannelRequest
+{
+ DBusGMethodInvocation *context;
+ gchar *channel_type;
+ guint handle_type;
+ guint handle;
+ gboolean suppress_handler;
+};
+
+static void _salut_connection_disconnect (SalutConnection *self);
+
+static void
+salut_connection_create_handle_repos (TpBaseConnection *self,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]);
+
+static GPtrArray *
+salut_connection_create_channel_factories (TpBaseConnection *self);
+
+static GPtrArray *
+salut_connection_create_channel_managers (TpBaseConnection *self);
+
+static gchar *
+salut_connection_get_unique_connection_name (TpBaseConnection *self);
+
+static void
+salut_connection_shut_down (TpBaseConnection *self);
+
+static gboolean
+salut_connection_start_connecting (TpBaseConnection *self, GError **error);
+
+static void salut_connection_avatars_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash);
+
+static void salut_connection_aliasing_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash);
+
+static void conn_contact_capabilities_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash);
+
+static void connection_capabilities_update_cb (SalutPresenceCache *cache,
+ TpHandle handle, gpointer user_data);
+
+static void
+conn_avatars_properties_getter (GObject *object, GQuark interface, GQuark name,
+ GValue *value, gpointer getter_data);
+
+static const char *mimetypes[] = {
+ "image/png", "image/jpeg", NULL };
+
+#define AVATAR_MIN_PX 0
+#define AVATAR_REC_PX 64
+#define AVATAR_MAX_PX 0
+#define AVATAR_MAX_BYTES G_MAXUINT16
+
+static TpDBusPropertiesMixinPropImpl conn_avatars_properties[] = {
+ { "MinimumAvatarWidth", GUINT_TO_POINTER (AVATAR_MIN_PX), NULL },
+ { "RecommendedAvatarWidth", GUINT_TO_POINTER (AVATAR_REC_PX), NULL },
+ { "MaximumAvatarWidth", GUINT_TO_POINTER (AVATAR_MAX_PX), NULL },
+ { "MinimumAvatarHeight", GUINT_TO_POINTER (AVATAR_MIN_PX), NULL },
+ { "RecommendedAvatarHeight", GUINT_TO_POINTER (AVATAR_REC_PX), NULL },
+ { "MaximumAvatarHeight", GUINT_TO_POINTER (AVATAR_MAX_PX), NULL },
+ { "MaximumAvatarBytes", GUINT_TO_POINTER (AVATAR_MAX_BYTES), NULL },
+ /* special-cased - it's the only one with a non-guint value */
+ { "SupportedAvatarMIMETypes", NULL, NULL },
+ { NULL }
+};
+
+static void
+salut_connection_init (SalutConnection *obj)
+{
+ SalutConnectionPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE(obj, SALUT_TYPE_CONNECTION,
+ SalutConnectionPrivate);
+
+ obj->priv = priv;
+ obj->name = NULL;
+
+ gabble_capabilities_init (obj);
+
+ tp_presence_mixin_init ((GObject *) obj,
+ G_STRUCT_OFFSET (SalutConnection, presence_mixin));
+
+ /* create this now so channel managers can use it when created from
+ * parent->constructor */
+ obj->session = wocky_session_new_ll (NULL);
+ obj->porter = wocky_session_get_porter (obj->session);
+
+ /* allocate any data required by the object here */
+ priv->published_name = g_strdup (g_get_user_name ());
+ priv->nickname = NULL;
+ priv->first_name = NULL;
+ priv->last_name = NULL;
+ priv->jid = NULL;
+ priv->email = NULL;
+#ifdef ENABLE_OLPC
+ priv->olpc_color = NULL;
+ priv->olpc_key = NULL;
+#endif
+
+ priv->discovery_client = NULL;
+ priv->self = NULL;
+ priv->self_established = FALSE;
+
+ priv->pre_connect_presence = SALUT_PRESENCE_AVAILABLE;
+ priv->pre_connect_message = NULL;
+ priv->pre_connect_caps = NULL;
+
+ priv->contact_manager = NULL;
+}
+
+static void
+sidecars_conn_status_changed_cb (SalutConnection *conn,
+ guint status, guint reason, gpointer unused);
+
+static void
+salut_connection_constructed (GObject *obj)
+{
+ SalutConnection *self = (SalutConnection *) obj;
+ TpBaseConnection *base = (TpBaseConnection *) obj;
+
+ self->disco = salut_disco_new (self);
+ self->presence_cache = salut_presence_cache_new (self);
+ g_signal_connect (self->presence_cache, "capabilities-update", G_CALLBACK
+ (connection_capabilities_update_cb), self);
+
+ tp_contacts_mixin_init (obj,
+ G_STRUCT_OFFSET (SalutConnection, contacts_mixin));
+
+ tp_base_connection_register_with_contacts_mixin (base);
+ tp_presence_mixin_simple_presence_register_with_contacts_mixin (obj);
+ tp_base_contact_list_mixin_register_with_contacts_mixin (base);
+
+ tp_contacts_mixin_add_contact_attributes_iface (obj,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ salut_connection_avatars_fill_contact_attributes);
+
+ tp_contacts_mixin_add_contact_attributes_iface (obj,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ salut_connection_aliasing_fill_contact_attributes);
+
+ tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (self),
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ conn_contact_capabilities_fill_contact_attributes);
+
+ self->priv->sidecars = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ self->priv->pending_sidecars = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_list_free);
+
+ g_signal_connect (self, "status-changed",
+ (GCallback) sidecars_conn_status_changed_cb, NULL);
+
+ salut_conn_contact_info_init (self);
+
+ if (G_OBJECT_CLASS (salut_connection_parent_class)->constructed)
+ G_OBJECT_CLASS (salut_connection_parent_class)->constructed (obj);
+}
+
+static void
+salut_connection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutConnection *self = SALUT_CONNECTION(object);
+ SalutConnectionPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_NICKNAME:
+ g_value_set_string (value, priv->nickname);
+ break;
+ case PROP_FIRST_NAME:
+ g_value_set_string (value, priv->first_name);
+ break;
+ case PROP_LAST_NAME:
+ g_value_set_string (value, priv->last_name);
+ break;
+ case PROP_JID:
+ g_value_set_string (value, priv->jid);
+ break;
+ case PROP_EMAIL:
+ g_value_set_string (value, priv->email);
+ break;
+ case PROP_PUBLISHED_NAME:
+ g_value_set_string (value, priv->published_name);
+ break;
+ case PROP_IM_MANAGER:
+ g_value_set_object (value, priv->im_manager);
+ break;
+ case PROP_MUC_MANAGER:
+ g_value_set_object (value, priv->muc_manager);
+ break;
+ case PROP_TUBES_MANAGER:
+ g_value_set_object (value, priv->tubes_manager);
+ break;
+ case PROP_ROOMLIST_MANAGER:
+ g_value_set_object (value, priv->roomlist_manager);
+ break;
+ case PROP_CONTACT_MANAGER:
+ g_value_set_object (value, priv->contact_manager);
+ break;
+ case PROP_SELF:
+ g_value_set_object (value, priv->self);
+ break;
+ case PROP_SI_BYTESTREAM_MANAGER:
+ g_value_set_object (value, priv->si_bytestream_manager);
+ break;
+#ifdef ENABLE_OLPC
+ case PROP_OLPC_ACTIVITY_MANAGER:
+ g_value_set_object (value, priv->olpc_activity_manager);
+ break;
+#endif
+ case PROP_BACKEND:
+ g_value_set_gtype (value, priv->backend_type);
+ break;
+ case PROP_DNSSD_NAME:
+ g_value_set_string (value, priv->dnssd_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+salut_connection_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutConnection *self = SALUT_CONNECTION(object);
+ SalutConnectionPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_NICKNAME:
+ g_free (priv->nickname);
+ priv->nickname = g_value_dup_string (value);
+ break;
+ case PROP_FIRST_NAME:
+ g_free (priv->first_name);
+ priv->first_name = g_value_dup_string (value);
+ break;
+ case PROP_LAST_NAME:
+ g_free (priv->last_name);
+ priv->last_name = g_value_dup_string (value);
+ break;
+ case PROP_JID:
+ g_free (priv->jid);
+ priv->jid = g_value_dup_string (value);
+ break;
+ case PROP_EMAIL:
+ g_free (priv->email);
+ priv->email = g_value_dup_string (value);
+ break;
+ case PROP_PUBLISHED_NAME:
+ g_free (priv->published_name);
+ priv->published_name = g_value_dup_string (value);
+ break;
+ case PROP_BACKEND:
+ priv->backend_type = g_value_get_gtype (value);
+ /* Create the backend object */
+ priv->discovery_client = g_object_new (priv->backend_type,
+ "dnssd-name", priv->dnssd_name,
+ NULL);
+ g_assert (priv->discovery_client != NULL);
+ break;
+ case PROP_DNSSD_NAME:
+ priv->dnssd_name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void salut_connection_dispose (GObject *object);
+static void salut_connection_finalize (GObject *object);
+
+/* presence bits and pieces */
+
+static const TpPresenceStatusOptionalArgumentSpec presence_args[] = {
+ { "message", "s" },
+ { NULL }
+};
+
+/* keep these in the same order as SalutPresenceId... */
+static const TpPresenceStatusSpec presence_statuses[] = {
+ { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE,
+ presence_args },
+ { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, presence_args },
+ { "dnd", TP_CONNECTION_PRESENCE_TYPE_BUSY, TRUE, presence_args },
+ { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
+ { NULL }
+};
+/* ... and these too (declared in presence.h) */
+const char *salut_presence_status_txt_names[] = {
+ "avail",
+ "away",
+ "dnd",
+ "offline",
+ NULL
+};
+
+static gboolean
+is_presence_status_available (GObject *obj,
+ guint index_)
+{
+ return (index_ < SALUT_PRESENCE_OFFLINE);
+}
+
+static GHashTable *
+make_presence_opt_args (SalutPresenceId presence, const gchar *message)
+{
+ GHashTable *ret;
+ GValue *value;
+
+ /* Omit missing or empty messages from the hash table.
+ * Also, offline has no message in Salut, it wouldn't make sense. */
+ if (presence == SALUT_PRESENCE_OFFLINE || message == NULL ||
+ *message == '\0')
+ {
+ return NULL;
+ }
+
+ ret = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ value = g_slice_new0 (GValue);
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, message);
+ g_hash_table_insert (ret, "message", value);
+
+ return ret;
+}
+
+static GHashTable *
+get_contact_statuses (GObject *obj,
+ const GArray *handles,
+ GError **error)
+{
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTable *ret;
+ guint i;
+
+ if (!tp_handles_are_valid (handle_repo, handles, FALSE, error))
+ {
+ return NULL;
+ }
+
+ ret = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) tp_presence_status_free);
+
+ for (i = 0; i < handles->len; i++)
+ {
+ TpHandle handle = g_array_index (handles, TpHandle, i);
+ TpPresenceStatus *ps = tp_presence_status_new
+ (SALUT_PRESENCE_OFFLINE, NULL);
+ const gchar *message = NULL;
+
+ if (handle == base->self_handle)
+ {
+ ps->index = priv->self->status;
+ message = priv->self->status_message;
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact
+ (priv->contact_manager, handle);
+
+ if (contact != NULL)
+ {
+ ps->index = contact->status;
+ message = contact->status_message;
+ g_object_unref (contact);
+ }
+ }
+
+ ps->optional_arguments = make_presence_opt_args (ps->index, message);
+
+ g_hash_table_insert (ret, GUINT_TO_POINTER (handle), ps);
+ }
+
+ return ret;
+}
+
+static void
+set_self_presence (SalutConnection *self,
+ SalutPresenceId presence,
+ const gchar *message,
+ GError **error)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+
+ if (priv->self == NULL || !priv->self_established)
+ {
+ g_free (priv->pre_connect_message);
+ priv->pre_connect_presence = presence;
+ priv->pre_connect_message = g_strdup (message);
+ return;
+ }
+
+ if (salut_self_set_presence (priv->self, presence, message, error))
+ {
+ TpPresenceStatus ps = { priv->self->status,
+ make_presence_opt_args (priv->self->status,
+ priv->self->status_message) };
+
+ tp_presence_mixin_emit_one_presence_update ((GObject *) self,
+ base->self_handle, &ps);
+
+ if (ps.optional_arguments != NULL)
+ g_hash_table_unref (ps.optional_arguments);
+ }
+}
+
+static gboolean
+set_own_status (GObject *obj,
+ const TpPresenceStatus *status,
+ GError **error)
+{
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ GError *err = NULL;
+ const GValue *value;
+ const gchar *message = NULL;
+ SalutPresenceId presence = SALUT_PRESENCE_AVAILABLE;
+
+ if (status != NULL)
+ {
+ /* mixin has already validated the index */
+ presence = status->index;
+
+ if (status->optional_arguments != NULL)
+ {
+ value = g_hash_table_lookup (status->optional_arguments, "message");
+ if (value)
+ {
+ /* TpPresenceMixin should validate this for us, but doesn't */
+ if (!G_VALUE_HOLDS_STRING (value))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Status argument 'message' requires a string");
+ return FALSE;
+ }
+ message = g_value_get_string (value);
+ }
+ }
+ }
+
+ set_self_presence (self, presence, message, &err);
+
+ if (err != NULL)
+ {
+ *error = g_error_new_literal (TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ err->message);
+ }
+
+ return TRUE;
+}
+
+static const gchar *interfaces [] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
+ SALUT_IFACE_CONNECTION_FUTURE,
+#ifdef ENABLE_OLPC
+ SALUT_IFACE_OLPC_BUDDY_INFO,
+ SALUT_IFACE_OLPC_ACTIVITY_PROPERTIES,
+#endif
+ NULL };
+
+const gchar * const *
+salut_connection_get_implemented_interfaces (void)
+{
+ return interfaces;
+}
+
+static void
+salut_connection_class_init (SalutConnectionClass *salut_connection_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_connection_class);
+ TpBaseConnectionClass *tp_connection_class =
+ TP_BASE_CONNECTION_CLASS(salut_connection_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ conn_avatars_properties_getter,
+ NULL,
+ conn_avatars_properties,
+ },
+ { NULL }
+ };
+
+ object_class->get_property = salut_connection_get_property;
+ object_class->set_property = salut_connection_set_property;
+
+ g_type_class_add_private (salut_connection_class,
+ sizeof (SalutConnectionPrivate));
+
+ object_class->constructed = salut_connection_constructed;
+
+ object_class->dispose = salut_connection_dispose;
+ object_class->finalize = salut_connection_finalize;
+
+ tp_connection_class->create_handle_repos =
+ salut_connection_create_handle_repos;
+ tp_connection_class->create_channel_factories =
+ salut_connection_create_channel_factories;
+ tp_connection_class->create_channel_managers =
+ salut_connection_create_channel_managers;
+ tp_connection_class->get_unique_connection_name =
+ salut_connection_get_unique_connection_name;
+ tp_connection_class->shut_down =
+ salut_connection_shut_down;
+ tp_connection_class->start_connecting =
+ salut_connection_start_connecting;
+ tp_connection_class->interfaces_always_present = interfaces;
+
+ salut_connection_class->properties_mixin.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutConnectionClass, properties_mixin));
+
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutConnectionClass, presence_mixin),
+ is_presence_status_available, get_contact_statuses, set_own_status,
+ presence_statuses);
+
+ tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutConnectionClass, contacts_mixin));
+
+ salut_conn_contact_info_class_init (salut_connection_class);
+
+ tp_base_contact_list_mixin_class_init (tp_connection_class);
+
+ param_spec = g_param_spec_string ("nickname", "nickname",
+ "Nickname used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NICKNAME, param_spec);
+
+ param_spec = g_param_spec_string ("first-name", "First name",
+ "First name used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_FIRST_NAME, param_spec);
+
+ param_spec = g_param_spec_string ("last-name", "Last name",
+ "Last name used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LAST_NAME, param_spec);
+
+ param_spec = g_param_spec_string ("email", "E-mail address",
+ "E-mail address used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_EMAIL, param_spec);
+
+ param_spec = g_param_spec_string ("jid", "Jabber id",
+ "Jabber id used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_JID, param_spec);
+
+ param_spec = g_param_spec_string ("published-name", "Published name",
+ "Username used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PUBLISHED_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "im-manager",
+ "SalutImManager object",
+ "The Salut IM Manager associated with this Salut Connection",
+ SALUT_TYPE_IM_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_IM_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "muc-manager",
+ "SalutMucManager object",
+ "The Salut MUC Manager associated with this Salut Connection",
+ SALUT_TYPE_MUC_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MUC_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "tubes-manager",
+ "SalutTubesManager object",
+ "The Salut Tubes Manager associated with this Salut Connection",
+ SALUT_TYPE_TUBES_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TUBES_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "roomlist-manager",
+ "SalutRoomlistManager object",
+ "The Salut Roomlist Manager associated with this Salut Connection",
+ SALUT_TYPE_ROOMLIST_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ROOMLIST_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "contact-manager",
+ "SalutContactManager object",
+ "The Salut Contact Manager associated with this Salut Connection",
+ SALUT_TYPE_CONTACT_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "self",
+ "SalutSelf object",
+ "The Salut Self object associated with this Salut Connection",
+ SALUT_TYPE_SELF,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SELF,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "si-bytestream-manager",
+ "SalutSiBytestreamManager object",
+ "The Salut SI Bytestream Manager associated with this Salut Connection",
+ SALUT_TYPE_SI_BYTESTREAM_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SI_BYTESTREAM_MANAGER,
+ param_spec);
+
+#ifdef ENABLE_OLPC
+ param_spec = g_param_spec_object (
+ "olpc-activity-manager",
+ "SalutOlpcActivityManager object",
+ "The OLPC activity Manager associated with this Salut Connection",
+ SALUT_TYPE_OLPC_ACTIVITY_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OLPC_ACTIVITY_MANAGER,
+ param_spec);
+#endif
+
+ param_spec = g_param_spec_gtype (
+ "backend-type",
+ "backend type",
+ "a G_TYPE_GTYPE of the backend to use",
+ G_TYPE_NONE,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_BACKEND,
+ param_spec);
+
+ param_spec = g_param_spec_string ("dnssd-name", "DNS-SD name",
+ "The DNS-SD name of the protocol", "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DNSSD_NAME,
+ param_spec);
+}
+
+void
+salut_connection_dispose (GObject *object)
+{
+ SalutConnection *self = SALUT_CONNECTION (object);
+ SalutConnectionPrivate *priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (self->disco != NULL)
+ {
+ g_object_unref (self->disco);
+ self->disco = NULL;
+ }
+
+ if (self->presence_cache)
+ {
+ g_object_unref (self->presence_cache);
+ self->presence_cache = NULL;
+ }
+
+ if (priv->pre_connect_message != NULL)
+ {
+ g_free (priv->pre_connect_message);
+ priv->pre_connect_message = NULL;
+ }
+
+ if (priv->pre_connect_caps != NULL)
+ {
+ gabble_capability_set_free (priv->pre_connect_caps);
+ priv->pre_connect_caps = NULL;
+ }
+
+ if (priv->pre_connect_data_forms != NULL)
+ {
+ g_ptr_array_unref (priv->pre_connect_data_forms);
+ priv->pre_connect_data_forms = NULL;
+ }
+
+ if (priv->self)
+ {
+ g_object_unref (priv->self);
+ priv->self = NULL;
+ }
+
+#ifdef ENABLE_OLPC
+ {
+ wocky_porter_unregister_handler (self->porter, priv->uninvite_handler_id);
+ priv->uninvite_handler_id = 0;
+ }
+
+ if (priv->olpc_activity_manager != NULL)
+ {
+ g_object_unref (priv->olpc_activity_manager);
+ priv->olpc_activity_manager = NULL;
+ }
+#endif
+
+ if (self->session != NULL)
+ {
+ g_object_unref (self->session);
+ self->session = NULL;
+ self->porter = NULL;
+ }
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ g_signal_handlers_disconnect_matched (priv->discovery_client,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+ priv->discovery_client = NULL;
+ }
+
+ if (priv->si_bytestream_manager != NULL)
+ {
+ g_object_unref (priv->si_bytestream_manager);
+ priv->si_bytestream_manager = NULL;
+ }
+
+ g_warn_if_fail (g_hash_table_size (priv->sidecars) == 0);
+ tp_clear_pointer (&priv->sidecars, g_hash_table_unref);
+
+ g_warn_if_fail (g_hash_table_size (priv->pending_sidecars) == 0);
+ tp_clear_pointer (&priv->pending_sidecars, g_hash_table_unref);
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (salut_connection_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_connection_parent_class)->dispose (object);
+}
+
+void
+salut_connection_finalize (GObject *object)
+{
+ SalutConnection *self = SALUT_CONNECTION (object);
+ SalutConnectionPrivate *priv = self->priv;
+
+ /* free any data held directly by the object here */
+ tp_presence_mixin_finalize (object);
+ g_free (self->name);
+ g_free (priv->published_name);
+ g_free (priv->first_name);
+ g_free (priv->last_name);
+ g_free (priv->email);
+ g_free (priv->jid);
+#ifdef ENABLE_OLPC
+ if (priv->olpc_key != NULL)
+ g_array_unref (priv->olpc_key);
+ g_free (priv->olpc_color);
+#endif
+ g_free (priv->dnssd_name);
+
+ tp_contacts_mixin_finalize (G_OBJECT(self));
+
+ gabble_capabilities_finalize (self);
+
+ DEBUG("Finalizing connection");
+
+ G_OBJECT_CLASS (salut_connection_parent_class)->finalize (object);
+}
+
+static void
+_contact_manager_contact_status_changed (SalutConnection *self,
+ SalutContact *contact, TpHandle handle)
+{
+ TpPresenceStatus ps = { contact->status,
+ make_presence_opt_args (contact->status, contact->status_message) };
+
+ tp_presence_mixin_emit_one_presence_update ((GObject *) self, handle,
+ &ps);
+
+ if (ps.optional_arguments != NULL)
+ g_hash_table_unref (ps.optional_arguments);
+}
+
+static gboolean
+announce_self_caps (SalutConnection *self,
+ GError **error)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ gchar *caps_hash;
+ gboolean ret;
+
+ caps_hash = caps_hash_compute_from_self_presence (priv->self);
+
+ ret = salut_self_set_caps (priv->self, WOCKY_TELEPATHY_NS_CAPS, "sha-1",
+ caps_hash, error);
+
+ if (ret)
+ {
+ salut_presence_cache_learn_caps (self->presence_cache,
+ WOCKY_TELEPATHY_NS_CAPS, caps_hash, salut_self_get_caps (priv->self),
+ wocky_xep_0115_capabilities_get_data_forms (WOCKY_XEP_0115_CAPABILITIES (priv->self)));
+ }
+
+ g_free (caps_hash);
+ return ret;
+}
+
+static void
+_self_established_cb (SalutSelf *s, gpointer data)
+{
+ SalutConnection *self = SALUT_CONNECTION (data);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ TP_BASE_CONNECTION (self), TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+
+ priv->self_established = TRUE;
+
+ g_free (self->name);
+ self->name = g_strdup (s->name);
+
+ base->self_handle = tp_handle_ensure (handle_repo, self->name, NULL, NULL);
+
+ wocky_session_set_jid (self->session, self->name);
+
+ set_self_presence (self, priv->pre_connect_presence,
+ priv->pre_connect_message, &error);
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to set presence from pre-connection: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_free (priv->pre_connect_message);
+ priv->pre_connect_message = NULL;
+
+ if (!salut_contact_manager_start (priv->contact_manager, &error))
+ {
+ DEBUG ("failed to start contact manager: %s", error->message);
+ g_clear_error (&error);
+
+ tp_base_connection_change_status ( TP_BASE_CONNECTION (base),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ return;
+ }
+
+ if (!salut_roomlist_manager_start (priv->roomlist_manager, &error))
+ {
+ DEBUG ("failed to start roomlist manager: %s", error->message);
+ g_clear_error (&error);
+
+ tp_base_connection_change_status ( TP_BASE_CONNECTION (base),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ return;
+ }
+
+#ifdef ENABLE_OLPC
+ if (!salut_olpc_activity_manager_start (priv->olpc_activity_manager, &error))
+ {
+ DEBUG ("failed to start olpc activity manager: %s", error->message);
+ g_clear_error (&error);
+
+ tp_base_connection_change_status ( TP_BASE_CONNECTION (base),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ return;
+ }
+#endif
+
+ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED);
+}
+
+
+static void
+_self_failed_cb (SalutSelf *s, GError *error, gpointer data)
+{
+ SalutConnection *self = SALUT_CONNECTION (data);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+
+ /* FIXME better error handling */
+ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED);
+}
+
+static void
+discovery_client_running (SalutConnection *self)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ GError *error = NULL;
+ guint16 port;
+
+ priv->self = salut_discovery_client_create_self (priv->discovery_client,
+ self, priv->nickname, priv->first_name, priv->last_name, priv->jid,
+ priv->email, priv->published_name,
+#ifdef ENABLE_OLPC
+ priv->olpc_key, priv->olpc_color
+#else
+ NULL, NULL
+#endif
+ );
+
+ if (priv->pre_connect_caps != NULL)
+ {
+ salut_self_take_caps (priv->self, priv->pre_connect_caps);
+ priv->pre_connect_caps = NULL;
+ }
+
+ if (priv->pre_connect_data_forms != NULL)
+ {
+ salut_self_take_data_forms (priv->self, priv->pre_connect_data_forms);
+ priv->pre_connect_data_forms = NULL;
+ }
+
+ g_signal_connect (priv->self, "established",
+ G_CALLBACK(_self_established_cb), self);
+ g_signal_connect (priv->self, "failure",
+ G_CALLBACK(_self_failed_cb), self);
+
+ wocky_session_start (self->session);
+
+ port = wocky_meta_porter_get_port (WOCKY_META_PORTER (self->porter));
+
+ if (!announce_self_caps (self, &error))
+ {
+ DEBUG ("Can't announce our capabilities: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (port == 0 || !salut_self_announce (priv->self, port, &error))
+ {
+ DEBUG ("failed to announce: %s",
+ error != NULL ? error->message : "(no error message)");
+
+ tp_base_connection_change_status (
+ TP_BASE_CONNECTION (self),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+
+ g_clear_error (&error);
+ return;
+ }
+
+ /* Create the bytestream manager */
+ priv->si_bytestream_manager = salut_si_bytestream_manager_new (self,
+ salut_discovery_client_get_host_name_fqdn (priv->discovery_client));
+}
+
+static void
+_discovery_client_state_changed_cb (SalutDiscoveryClient *client,
+ SalutDiscoveryClientState state,
+ SalutConnection *self)
+{
+ SalutConnectionPrivate *priv = self->priv;
+
+ g_assert (client == priv->discovery_client);
+
+ if (state == SALUT_DISCOVERY_CLIENT_STATE_CONNECTED)
+ {
+ discovery_client_running (self);
+ }
+ else if (state == SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTED)
+ {
+ /* FIXME better error messages */
+ /* FIXME instead of full disconnect we could handle the avahi restart */
+ DEBUG ("discovery client got disconnected");
+ tp_base_connection_change_status (TP_BASE_CONNECTION (self),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ }
+}
+/* public functions */
+static void
+_salut_connection_disconnect (SalutConnection *self)
+{
+ SalutConnectionPrivate *priv = self->priv;
+
+ if (priv->self)
+ {
+ g_object_unref (priv->self);
+ priv->self = NULL;
+ }
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ g_signal_handlers_disconnect_matched (priv->discovery_client,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+ priv->discovery_client = NULL;
+ }
+}
+
+
+/* Aliasing interface */
+/**
+ * salut_connection_get_alias_flags
+ *
+ * Implements D-Bus method GetAliasFlags
+ * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing
+ *
+ */
+static void
+salut_connection_get_alias_flags (TpSvcConnectionInterfaceAliasing *self,
+ DBusGMethodInvocation *context)
+{
+ /* Aliases are set by the contacts
+ * Actually we concat the first and lastname property */
+
+ tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
+ 0);
+}
+
+static const gchar *
+salut_connection_get_alias (SalutConnection *self, TpHandle handle)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ const gchar *alias;
+
+ if (handle == base->self_handle)
+ {
+ alias = salut_self_get_alias (priv->self);
+ }
+ else
+ {
+ SalutContact *contact;
+ contact = salut_contact_manager_get_contact (priv->contact_manager,
+ handle);
+
+ if (contact == NULL)
+ {
+ alias = tp_handle_inspect (contact_repo, handle);
+ }
+ else
+ {
+ alias = salut_contact_get_alias (contact);
+ g_object_unref (contact);
+ }
+ }
+
+ return alias;
+}
+
+/**
+ * salut_connection_request_aliases
+ *
+ * Implements D-Bus method RequestAliases
+ * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing
+ *
+ */
+static void
+salut_connection_request_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ const GArray *contacts, DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ guint i;
+ const gchar **aliases;
+ GError *error = NULL;
+ TpHandleRepoIface *contact_handles =
+ tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT);
+
+ DEBUG ("Alias requested");
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_handles, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ aliases = g_new0 (const gchar *, contacts->len + 1);
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+
+ aliases[i] = salut_connection_get_alias (self, handle);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
+ aliases);
+
+ g_free (aliases);
+ return;
+}
+
+static void
+salut_connection_get_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ const GArray *contacts, DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ guint i;
+ GError *error = NULL;
+ GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+ (gchar *) salut_connection_get_alias (self, handle));
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
+ result);
+
+ g_hash_table_unref (result);
+}
+
+static void
+salut_connection_aliasing_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash)
+{
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ guint i;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ GValue *val = tp_g_value_slice_new (G_TYPE_STRING);
+
+ g_value_set_string (val, salut_connection_get_alias (self, handle));
+
+ tp_contacts_mixin_set_contact_attribute (attributes_hash, handle,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING"/alias", val);
+ }
+}
+
+/**
+ * salut_connection_get_handle_contact_capabilities
+ *
+ * Add capabilities of handle to the given GPtrArray
+ */
+static void
+salut_connection_get_handle_contact_capabilities (SalutConnection *self,
+ TpHandle handle, GPtrArray *arr)
+{
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (self);
+ TpChannelManagerIter iter;
+ TpChannelManager *manager;
+ const GabbleCapabilitySet *set;
+ SalutContact *contact = NULL;
+
+ if (handle == base_conn->self_handle)
+ {
+ if (self->priv->self == NULL)
+ return;
+
+ set = salut_self_get_caps (self->priv->self);
+ }
+ else
+ {
+ contact = salut_contact_manager_get_contact (
+ self->priv->contact_manager, handle);
+
+ if (contact == NULL)
+ return;
+
+ set = contact->caps;
+ }
+
+ tp_base_connection_channel_manager_iter_init (&iter, base_conn);
+
+ while (tp_base_connection_channel_manager_iter_next (&iter, &manager))
+ {
+ /* all channel managers must implement the capability interface */
+ g_assert (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager));
+
+ gabble_caps_channel_manager_get_contact_capabilities (
+ GABBLE_CAPS_CHANNEL_MANAGER (manager), handle, set, arr);
+ }
+
+ tp_clear_object (&contact);
+}
+
+static void
+conn_contact_capabilities_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash)
+{
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ guint i;
+ GPtrArray *array = NULL;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+
+ if (array == NULL)
+ array = g_ptr_array_new ();
+
+ salut_connection_get_handle_contact_capabilities (self, handle, array);
+
+ if (array->len > 0)
+ {
+ GValue *val = tp_g_value_slice_new (
+ TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST);
+
+ g_value_take_boxed (val, array);
+ tp_contacts_mixin_set_contact_attribute (attributes_hash,
+ handle,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES"/capabilities",
+ val);
+
+ array = NULL;
+ }
+ }
+
+ if (array != NULL)
+ g_ptr_array_unref (array);
+}
+
+static void
+salut_connection_set_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ GHashTable *aliases, DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ SalutConnectionPrivate *priv = self->priv;
+ GError *error = NULL;
+ const gchar *alias = g_hash_table_lookup (aliases,
+ GUINT_TO_POINTER (base->self_handle));
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (alias == NULL || g_hash_table_size (aliases) != 1)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "In Salut you can only set your own alias" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ DEBUG("Setting my alias to: %s", alias);
+
+ if (!salut_self_set_alias (priv->self, alias, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
+}
+
+static void
+_contact_manager_contact_alias_changed (SalutConnection *self,
+ SalutContact *contact, TpHandle handle)
+{
+ GPtrArray *aliases;
+ GValue entry = {0, };
+
+ g_value_init (&entry, TP_STRUCT_TYPE_ALIAS_PAIR);
+ g_value_take_boxed (&entry,
+ dbus_g_type_specialized_construct (TP_STRUCT_TYPE_ALIAS_PAIR));
+
+ dbus_g_type_struct_set (&entry,
+ 0, handle, 1, salut_contact_get_alias (contact), G_MAXUINT);
+ aliases = g_ptr_array_sized_new (1);
+ g_ptr_array_add (aliases, g_value_get_boxed (&entry));
+
+ DEBUG("Emitting AliasesChanged");
+
+ tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases);
+
+ g_value_unset (&entry);
+ g_ptr_array_unref (aliases);
+}
+
+static void
+salut_connection_aliasing_service_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceAliasingClass *klass =
+ (TpSvcConnectionInterfaceAliasingClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x \
+ (klass, salut_connection_##x)
+ IMPLEMENT (get_alias_flags);
+ IMPLEMENT (request_aliases);
+ IMPLEMENT (get_aliases);
+ IMPLEMENT (set_aliases);
+#undef IMPLEMENT
+}
+
+/* Avatar service implementation */
+static void
+_contact_manager_contact_avatar_changed (SalutConnection *self,
+ SalutContact *contact, TpHandle handle)
+{
+ tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+ (guint)handle, contact->avatar_token);
+}
+
+static void
+salut_connection_clear_avatar (TpSvcConnectionInterfaceAvatars *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ GError *error = NULL;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!salut_self_set_avatar (priv->self, NULL, 0, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ tp_svc_connection_interface_avatars_return_from_clear_avatar (context);
+}
+
+static void
+salut_connection_set_avatar (TpSvcConnectionInterfaceAvatars *iface,
+ const GArray *avatar, const gchar *mime_type,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ GError *error = NULL;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!salut_self_set_avatar (priv->self, (guint8 *) avatar->data,
+ avatar->len, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+ base->self_handle, priv->self->avatar_token);
+ tp_svc_connection_interface_avatars_return_from_set_avatar (context,
+ priv->self->avatar_token);
+}
+
+
+static void
+salut_connection_get_avatar_tokens (TpSvcConnectionInterfaceAvatars *iface,
+ const GArray *contacts, DBusGMethodInvocation *context)
+{
+ guint i;
+ gchar **ret;
+ GError *err = NULL;
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *handle_repo;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handles_are_valid (handle_repo, contacts, FALSE, &err))
+ {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ ret = g_new0(gchar *, contacts->len + 1);
+
+ for (i = 0; i < contacts->len ; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ if (base->self_handle == handle)
+ {
+ ret[i] = priv->self->avatar_token;
+ }
+ else
+ {
+ SalutContact *contact;
+
+ contact = salut_contact_manager_get_contact (priv->contact_manager,
+ handle);
+ if (contact != NULL)
+ {
+ ret[i] = contact->avatar_token;
+ g_object_unref (contact);
+ }
+ }
+ if (ret[i] == NULL)
+ ret[i] = "";
+ }
+
+ tp_svc_connection_interface_avatars_return_from_get_avatar_tokens (context,
+ (const gchar **)ret);
+
+ g_free (ret);
+}
+
+static void
+salut_connection_get_known_avatar_tokens (
+ TpSvcConnectionInterfaceAvatars *iface, const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ guint i;
+ GHashTable *ret;
+ GError *err = NULL;
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *handle_repo;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handles_are_valid (handle_repo, contacts, FALSE, &err))
+ {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ ret = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+
+ for (i = 0; i < contacts->len ; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ gchar *tokens = NULL;
+
+ if (base->self_handle == handle)
+ {
+ tokens = g_strdup (priv->self->avatar_token);
+ }
+ else
+ {
+ SalutContact *contact;
+ contact =
+ salut_contact_manager_get_contact (priv->contact_manager, handle);
+ if (contact != NULL)
+ {
+ if (contact->avatar_token != NULL)
+ tokens = g_strdup (contact->avatar_token);
+ else
+ /* We always know the tokens, if it's unset then it's "" */
+ tokens = g_strdup ("");
+ g_object_unref (contact);
+ }
+ }
+
+ if (tokens != NULL)
+ g_hash_table_insert (ret, GUINT_TO_POINTER (handle), tokens);
+ }
+
+ tp_svc_connection_interface_avatars_return_from_get_known_avatar_tokens (
+ context, ret);
+
+ g_hash_table_unref (ret);
+}
+
+static void
+salut_connection_avatars_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash)
+{
+ guint i;
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ SalutConnectionPrivate *priv = self->priv;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ gchar *token = NULL;
+
+ if (base->self_handle == handle)
+ {
+ token = g_strdup (priv->self->avatar_token);
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact (
+ priv->contact_manager, handle);
+ if (contact != NULL)
+ {
+ if (contact->avatar_token != NULL)
+ token = g_strdup (contact->avatar_token);
+ else
+ /* We always know the tokens, if it's unset then it's "" */
+ token = g_strdup ("");
+
+ g_object_unref (contact);
+ }
+ }
+
+ if (token != NULL)
+ {
+ GValue *val = tp_g_value_slice_new (G_TYPE_STRING);
+
+ g_value_take_string (val, token);
+
+ tp_contacts_mixin_set_contact_attribute (attributes_hash, handle,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS"/token", val);
+ }
+ }
+}
+
+
+static void
+_request_avatars_cb (SalutContact *contact, guint8 *avatar, gsize size,
+ gpointer user_data)
+{
+ GArray *arr;
+
+ if (avatar == NULL)
+ return;
+
+ arr = g_array_sized_new (FALSE, FALSE, sizeof (guint8), size);
+ arr = g_array_append_vals (arr, avatar, size);
+
+ tp_svc_connection_interface_avatars_emit_avatar_retrieved (
+ (GObject *) user_data, contact->handle,
+ contact->avatar_token, arr, "");
+
+ g_array_unref (arr);
+}
+
+static void
+salut_connection_request_avatars (
+ TpSvcConnectionInterfaceAvatars *iface,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ guint i;
+ GError *err = NULL;
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *handle_repo;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handles_are_valid (handle_repo, contacts, FALSE, &err))
+ {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ for (i = 0; i < contacts->len ; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+
+ if (base->self_handle == handle)
+ {
+ GArray *arr;
+
+ if (priv->self->avatar != NULL)
+ {
+ arr = g_array_sized_new (FALSE, FALSE, sizeof (guint8),
+ priv->self->avatar_size);
+ arr = g_array_append_vals (arr, priv->self->avatar,
+ priv->self->avatar_size);
+
+ tp_svc_connection_interface_avatars_emit_avatar_retrieved (
+ (GObject *) self, base->self_handle,
+ priv->self->avatar_token, arr, "");
+ g_array_unref (arr);
+ }
+ }
+ else
+ {
+ SalutContact *contact;
+ contact =
+ salut_contact_manager_get_contact (priv->contact_manager, handle);
+ if (contact != NULL)
+ {
+ salut_contact_get_avatar (contact, _request_avatars_cb, self);
+ g_object_unref (contact);
+ }
+ }
+ }
+
+ tp_svc_connection_interface_avatars_return_from_request_avatars (context);
+}
+
+static void
+_request_avatar_cb (SalutContact *contact, guint8 *avatar, gsize size,
+ gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+ GError *err = NULL;
+ GArray *arr;
+
+ if (size == 0)
+ {
+ err = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Unable to get avatar");
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ arr = g_array_sized_new (FALSE, FALSE, sizeof (guint8), size);
+ arr = g_array_append_vals (arr, avatar, size);
+ tp_svc_connection_interface_avatars_return_from_request_avatar (context,
+ arr, "");
+ g_array_unref (arr);
+}
+
+static void
+salut_connection_request_avatar (TpSvcConnectionInterfaceAvatars *iface,
+ guint handle, DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ SalutContact *contact;
+ GError *err = NULL;
+ TpHandleRepoIface *handle_repo;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handle_is_valid (handle_repo, handle, &err))
+ {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ if (handle == base->self_handle)
+ {
+ _request_avatar_cb (NULL, priv->self->avatar, priv->self->avatar_size,
+ context);
+ return;
+ }
+
+ contact = salut_contact_manager_get_contact (priv->contact_manager, handle);
+ if (contact == NULL || contact->avatar_token == NULL)
+ {
+ err = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "No known avatar");
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ if (contact != NULL)
+ {
+ g_object_unref (contact);
+ }
+ return;
+ }
+ salut_contact_get_avatar (contact, _request_avatar_cb, context);
+ g_object_unref (contact);
+}
+
+static void
+conn_avatars_properties_getter (GObject *object,
+ GQuark interface,
+ GQuark name,
+ GValue *value,
+ gpointer getter_data)
+{
+ GQuark q_mime_types = g_quark_from_static_string (
+ "SupportedAvatarMIMETypes");
+
+ if (name == q_mime_types)
+ {
+ g_value_set_static_boxed (value, mimetypes);
+ }
+ else
+ {
+ g_value_set_uint (value, GPOINTER_TO_UINT (getter_data));
+ }
+}
+
+static void
+salut_connection_get_avatar_requirements (
+ TpSvcConnectionInterfaceAvatars *iface, DBusGMethodInvocation *context)
+{
+ tp_svc_connection_interface_avatars_return_from_get_avatar_requirements (
+ context, mimetypes, AVATAR_MIN_PX, AVATAR_MIN_PX, AVATAR_MAX_PX,
+ AVATAR_MAX_PX, AVATAR_MAX_BYTES);
+}
+
+static void
+salut_connection_avatar_service_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceAvatarsClass *klass =
+ (TpSvcConnectionInterfaceAvatarsClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_avatars_implement_##x \
+ (klass, salut_connection_##x)
+ IMPLEMENT (get_avatar_requirements);
+ IMPLEMENT (get_avatar_tokens);
+ IMPLEMENT (get_known_avatar_tokens);
+ IMPLEMENT (request_avatar);
+ IMPLEMENT (request_avatars);
+ IMPLEMENT (set_avatar);
+ IMPLEMENT (clear_avatar);
+#undef IMPLEMENT
+}
+
+static void
+salut_free_enhanced_contact_capabilities (GPtrArray *caps)
+{
+ guint i;
+
+ for (i = 0; i < caps->len; i++)
+ {
+ GValue monster = {0, };
+
+ g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS);
+ g_value_take_boxed (&monster, g_ptr_array_index (caps, i));
+ g_value_unset (&monster);
+ }
+
+ g_ptr_array_unref (caps);
+}
+
+/**
+ * salut_connection_get_contact_capabilities
+ *
+ * Implements D-Bus method GetContactCapabilities
+ * on interface
+ * org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities
+ */
+static void
+salut_connection_get_contact_capabilities (
+ TpSvcConnectionInterfaceContactCapabilities *iface,
+ const GArray *handles,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ guint i;
+ GHashTable *ret;
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_handles, handles, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ ret = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) salut_free_enhanced_contact_capabilities);
+
+ for (i = 0; i < handles->len; i++)
+ {
+ GPtrArray *arr = g_ptr_array_new ();
+ TpHandle handle = g_array_index (handles, TpHandle, i);
+
+ salut_connection_get_handle_contact_capabilities (self, handle, arr);
+
+ g_hash_table_insert (ret, GINT_TO_POINTER (handle), arr);
+ }
+
+ tp_svc_connection_interface_contact_capabilities_return_from_get_contact_capabilities
+ (context, ret);
+
+ g_hash_table_unref (ret);
+}
+
+
+static void
+_emit_contact_capabilities_changed (SalutConnection *conn,
+ TpHandle handle)
+{
+ GPtrArray *ret = g_ptr_array_new ();
+ GHashTable *caps = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ salut_connection_get_handle_contact_capabilities (conn, handle, ret);
+ g_hash_table_insert (caps, GUINT_TO_POINTER (handle), ret);
+
+ tp_svc_connection_interface_contact_capabilities_emit_contact_capabilities_changed (
+ conn, caps);
+
+ salut_free_enhanced_contact_capabilities (ret);
+ g_hash_table_unref (caps);
+}
+
+static void
+connection_capabilities_update_cb (SalutPresenceCache *cache,
+ TpHandle handle,
+ gpointer user_data)
+{
+ SalutConnection *conn = SALUT_CONNECTION (user_data);
+
+ g_assert (SALUT_IS_CONNECTION (user_data));
+
+ _emit_contact_capabilities_changed (conn, handle);
+}
+
+static gboolean
+data_forms_equal (GPtrArray *one,
+ GPtrArray *two)
+{
+ guint i;
+
+ /* These data form lists come from the channel managers returning
+ * from represent_client so they'll be created new every time
+ * represent_client is called. As a result, we can't just look at
+ * the object pointers, like how we can in the presence cache. */
+
+ if (one->len != two->len)
+ return FALSE;
+
+ for (i = 0; i < one->len; i++)
+ {
+ WockyDataForm *form = g_ptr_array_index (one, i);
+ WockyDataFormField *type_field;
+ const gchar *type;
+ guint j;
+ gboolean found = FALSE;
+
+ type_field = g_hash_table_lookup (form->fields, "FORM_TYPE");
+ type = g_value_get_string (type_field->default_value);
+
+ for (j = 0; j < two->len; j++)
+ {
+ WockyDataForm *two_form = g_ptr_array_index (two, i);
+ WockyDataFormField *two_type;
+
+ two_type = g_hash_table_lookup (two_form->fields,
+ "FORM_TYPE");
+
+ if (!tp_strdiff (g_value_get_string (two_type->default_value), type))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * salut_connection_update_capabilities
+ *
+ * Implements D-Bus method UpdateCapabilities
+ * on interface
+ * org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities
+ */
+static void
+salut_connection_update_capabilities (
+ TpSvcConnectionInterfaceContactCapabilities *iface,
+ const GPtrArray *clients,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ SalutConnectionPrivate *priv = self->priv;
+ GabbleCapabilitySet *before = NULL, *after;
+ GPtrArray *before_forms = NULL, *after_forms;
+ TpChannelManagerIter iter;
+ TpChannelManager *manager;
+ guint i;
+ GError *error = NULL;
+
+ /* these are the caps we were advertising before UpdateCapabilities
+ * was called. we'll only have created the salut self once we've
+ * connected */
+ if (priv->self != NULL)
+ {
+ before = gabble_capability_set_copy (salut_self_get_caps (priv->self));
+ before_forms = g_ptr_array_ref (
+ (GPtrArray *) wocky_xep_0115_capabilities_get_data_forms (
+ WOCKY_XEP_0115_CAPABILITIES (priv->self)));
+ }
+
+ tp_base_connection_channel_manager_iter_init (&iter, base);
+
+ while (tp_base_connection_channel_manager_iter_next (&iter, &manager))
+ {
+ /* all channel managers must implement the capability interface */
+ g_assert (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager));
+
+ gabble_caps_channel_manager_reset_capabilities (
+ GABBLE_CAPS_CHANNEL_MANAGER (manager));
+ }
+
+ DEBUG ("enter");
+
+ /* we're going to reset our self caps to the bare caps that we
+ * advertise and then add to it after iterating the clients. */
+ after = salut_dup_self_advertised_caps ();
+ after_forms = g_ptr_array_new ();
+
+ for (i = 0; i < clients->len; i++)
+ {
+ GValueArray *va = g_ptr_array_index (clients, i);
+ const gchar *client_name = g_value_get_string (va->values + 0);
+ const GPtrArray *filters = g_value_get_boxed (va->values + 1);
+ const gchar * const * cap_tokens = g_value_get_boxed (va->values + 2);
+
+ /* We pass the client through to the caps channel managers
+ * because it allows them to update their view on which clients
+ * are still around. */
+
+ tp_base_connection_channel_manager_iter_init (&iter, base);
+
+ while (tp_base_connection_channel_manager_iter_next (&iter, &manager))
+ {
+ /* all channel managers must implement the capability interface */
+ g_assert (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager));
+
+ gabble_caps_channel_manager_represent_client (
+ GABBLE_CAPS_CHANNEL_MANAGER (manager), client_name, filters,
+ cap_tokens, after, after_forms);
+ }
+ }
+
+ if (priv->self != NULL)
+ {
+ /* we've connected and have a SalutSelf, so give the caps to it
+ * right now */
+ salut_self_take_caps (priv->self, after);
+ salut_self_take_data_forms (priv->self, after_forms);
+ }
+ else
+ {
+ if (priv->pre_connect_caps != NULL)
+ gabble_capability_set_free (priv->pre_connect_caps);
+ if (priv->pre_connect_data_forms != NULL)
+ g_ptr_array_unref (priv->pre_connect_data_forms);
+
+ priv->pre_connect_caps = after;
+ priv->pre_connect_data_forms = after_forms;
+ }
+
+ if ((before != NULL && !gabble_capability_set_equals (before, after))
+ || (before_forms != NULL && !data_forms_equal (before_forms, after_forms)))
+ {
+ if (DEBUGGING)
+ {
+ gchar *dump = gabble_capability_set_dump (after, " ");
+ DEBUG ("updated caps:\n%s", dump);
+ g_free (dump);
+ }
+
+ if (!announce_self_caps (self, &error))
+ {
+ gabble_capability_set_free (before);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ _emit_contact_capabilities_changed (self, base->self_handle);
+ }
+
+ /* after now belongs to SalutSelf, or priv->pre_connect_caps */
+ if (before != NULL)
+ gabble_capability_set_free (before);
+
+ if (before_forms != NULL)
+ g_ptr_array_unref (before_forms);
+
+ tp_svc_connection_interface_contact_capabilities_return_from_update_capabilities (
+ context);
+}
+
+static void
+salut_conn_contact_caps_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcConnectionInterfaceContactCapabilitiesClass *klass =
+ (TpSvcConnectionInterfaceContactCapabilitiesClass *) g_iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_connection_interface_contact_capabilities_implement_##x (\
+ klass, salut_connection_##x)
+ IMPLEMENT(get_contact_capabilities);
+ IMPLEMENT(update_capabilities);
+#undef IMPLEMENT
+}
+
+
+#ifdef ENABLE_OLPC
+static GValue *
+new_gvalue (GType type)
+{
+ GValue *result = g_slice_new0 (GValue);
+ g_value_init (result, type);
+ return result;
+}
+
+static GHashTable *
+get_properties_hash (const GArray *key, const gchar *color, const gchar *jid,
+ const gchar *ip4, const gchar *ip6)
+{
+ GHashTable *properties;
+ GValue *gvalue;
+
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ if (key != NULL)
+ {
+ gvalue = new_gvalue (DBUS_TYPE_G_UCHAR_ARRAY);
+ g_value_set_boxed (gvalue, key);
+ g_hash_table_insert (properties, "key", gvalue);
+ }
+
+ if (color != NULL)
+ {
+ gvalue = new_gvalue (G_TYPE_STRING);
+ g_value_set_string (gvalue, color);
+ g_hash_table_insert (properties, "color", gvalue);
+ }
+
+ if (jid != NULL)
+ {
+ gvalue = new_gvalue (G_TYPE_STRING);
+ g_value_set_string (gvalue, jid);
+ g_hash_table_insert (properties, "jid", gvalue);
+ }
+
+ if (ip4 != NULL)
+ {
+ gvalue = new_gvalue (G_TYPE_STRING);
+ g_value_set_string (gvalue, ip4);
+ g_hash_table_insert (properties, "ip4-address", gvalue);
+ }
+
+ if (ip6 != NULL)
+ {
+ gvalue = new_gvalue (G_TYPE_STRING);
+ g_value_set_string (gvalue, ip6);
+ g_hash_table_insert (properties, "ip6-address", gvalue);
+ }
+
+ return properties;
+}
+
+static void
+emit_properties_changed (SalutConnection *connection,
+ TpHandle handle,
+ const GArray *key,
+ const gchar *color,
+ const gchar *jid,
+ const gchar *ip4,
+ const gchar *ip6)
+{
+ GHashTable *properties;
+ properties = get_properties_hash (key, color, jid, ip4, ip6);
+
+ salut_svc_olpc_buddy_info_emit_properties_changed (connection,
+ handle, properties);
+
+ g_hash_table_unref (properties);
+}
+
+static void
+append_activity (SalutOlpcActivity *activity,
+ gpointer user_data)
+{
+ GPtrArray *arr = user_data;
+ GType type = ACTIVITY_PAIR_TYPE;
+ GValue gvalue = {0};
+
+ g_value_init (&gvalue, type);
+ g_value_take_boxed (&gvalue,
+ dbus_g_type_specialized_construct (type));
+
+ dbus_g_type_struct_set (&gvalue,
+ 0, activity->id,
+ 1, activity->room,
+ G_MAXUINT);
+ g_ptr_array_add (arr, g_value_get_boxed (&gvalue));
+}
+
+static void
+free_olpc_activities (GPtrArray *arr)
+{
+ GType type = ACTIVITY_PAIR_TYPE;
+ guint i;
+
+ for (i = 0; i < arr->len; i++)
+ g_boxed_free (type, arr->pdata[i]);
+
+ g_ptr_array_unref (arr);
+}
+
+static void
+_contact_manager_contact_olpc_activities_changed (SalutConnection *self,
+ SalutContact *contact,
+ TpHandle handle)
+{
+ GPtrArray *activities = g_ptr_array_new ();
+
+ DEBUG ("called for %u", handle);
+
+ salut_contact_foreach_olpc_activity (contact, append_activity, activities);
+ salut_svc_olpc_buddy_info_emit_activities_changed (self,
+ handle, activities);
+ free_olpc_activities (activities);
+}
+
+static void
+_contact_manager_contact_olpc_properties_changed (SalutConnection *self,
+ SalutContact *contact,
+ TpHandle handle)
+{
+ emit_properties_changed (self, handle, contact->olpc_key,
+ contact->olpc_color, contact->jid, contact->olpc_ip4, contact->olpc_ip6);
+}
+
+static gboolean
+check_handle (TpHandleRepoIface *handle_repo,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+
+ if (!tp_handle_is_valid (handle_repo, handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+check_contact (TpBaseConnection *base,
+ TpHandle contact,
+ DBusGMethodInvocation *context)
+{
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ base, TP_HANDLE_TYPE_CONTACT);
+
+ return check_handle (contact_repo, contact, context);
+}
+
+static gboolean
+check_room (TpBaseConnection *base,
+ TpHandle contact,
+ DBusGMethodInvocation *context)
+{
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ base, TP_HANDLE_TYPE_ROOM);
+
+ return check_handle (room_repo, contact, context);
+}
+
+static void
+salut_connection_olpc_get_properties (SalutSvcOLPCBuddyInfo *iface,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ GHashTable *properties = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!check_contact (base, handle, context))
+ return;
+
+ if (handle == base->self_handle)
+ {
+ properties = get_properties_hash (priv->self->olpc_key,
+ priv->self->olpc_color, priv->self->jid, NULL, NULL);
+ }
+ else
+ {
+ SalutContact *contact;
+ contact = salut_contact_manager_get_contact (priv->contact_manager,
+ handle);
+ if (contact == NULL)
+ {
+ /* FIXME: should this be InvalidHandle? */
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Unknown contact" };
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+ properties = get_properties_hash (contact->olpc_key, contact->olpc_color,
+ contact->jid, contact->olpc_ip4, contact->olpc_ip6);
+ g_object_unref (contact);
+ }
+
+ salut_svc_olpc_buddy_info_return_from_get_properties (context, properties);
+ g_hash_table_unref (properties);
+}
+
+
+static gboolean
+find_unknown_properties (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ gchar **valid_props = (gchar **) user_data;
+ int i;
+ for (i = 0; valid_props[i] != NULL; i++)
+ {
+ if (!tp_strdiff (key, valid_props[i]))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+salut_connection_olpc_set_properties (SalutSvcOLPCBuddyInfo *iface,
+ GHashTable *properties,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+
+ GError *error = NULL;
+ /* Only a few known properties, so handle it quite naively */
+ const gchar *known_properties[] = { "color", "key", "jid", "ip4-address",
+ "ip6-address", NULL };
+ const gchar *color = NULL;
+ const GArray *key = NULL;
+ const gchar *jid = NULL;
+ const GValue *val;
+
+ /* this function explicitly supports being called when DISCONNECTED
+ * or CONNECTING */
+
+ if (g_hash_table_find (properties, find_unknown_properties, known_properties)
+ != NULL)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unknown property given");
+ goto error;
+ }
+
+ val = (const GValue *) g_hash_table_lookup (properties, "color");
+ if (val != NULL)
+ {
+ if (G_VALUE_TYPE (val) != G_TYPE_STRING)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Color value should be of type s");
+ goto error;
+ }
+ else
+ {
+ int len;
+ gboolean correct = TRUE;
+
+ color = g_value_get_string (val);
+
+ /* be very anal about the color format */
+ len = strlen (color);
+ if (len != 15)
+ {
+ correct = FALSE;
+ }
+ else
+ {
+ int i;
+ for (i = 0 ; i < len ; i++)
+ {
+ switch (i)
+ {
+ case 0:
+ case 8:
+ correct = (color[i] == '#');
+ break;
+ case 7:
+ correct = (color[i] == ',');
+ break;
+ default:
+ correct = isxdigit (color[i]);
+ break;
+ }
+ }
+ }
+
+ if (!correct)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Color value has an incorrect format");
+ goto error;
+ }
+ }
+ }
+
+ if ((val = (const GValue *) g_hash_table_lookup (properties, "key")) != NULL)
+ {
+ if (G_VALUE_TYPE (val) != DBUS_TYPE_G_UCHAR_ARRAY)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Key value should be of type ay");
+ goto error;
+ }
+ else
+ {
+ key = g_value_get_boxed (val);
+ if (key->len == 0)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Key value of length 0 not allowed");
+ goto error;
+ }
+ }
+ }
+
+ val = g_hash_table_lookup (properties, "jid");
+ if (val != NULL)
+ {
+ if (G_VALUE_TYPE (val) != G_TYPE_STRING)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "JID value should be of type s");
+ goto error;
+ }
+
+ jid = g_value_get_string (val);
+
+ if (strchr (jid, '@') == NULL)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "JID value has an incorrect format");
+ goto error;
+ }
+ }
+
+ if (priv->self)
+ {
+ if (!salut_self_set_olpc_properties (priv->self, key, color, jid,
+ &error))
+ goto error;
+ }
+ else
+ {
+ /* queue it up for later */
+ if (key)
+ {
+ if (priv->olpc_key == NULL)
+ {
+ priv->olpc_key = g_array_sized_new (FALSE, FALSE, sizeof (guint8),
+ key->len);
+ }
+ else
+ {
+ g_array_remove_range (priv->olpc_key, 0, priv->olpc_key->len);
+ }
+ g_array_append_vals (priv->olpc_key, key->data, key->len);
+ }
+ if (color)
+ {
+ g_free (priv->olpc_color);
+ priv->olpc_color = g_strdup (color);
+ }
+ if (jid)
+ {
+ g_free (priv->jid);
+ priv->jid = g_strdup (jid);
+ }
+ }
+
+ salut_svc_olpc_buddy_info_return_from_set_properties (context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+salut_connection_olpc_get_current_activity (SalutSvcOLPCBuddyInfo *iface,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ SalutConnectionPrivate *priv = self->priv;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ DEBUG ("called for %u", handle);
+
+ if (!check_contact (base, handle, context))
+ return;
+
+ if (handle == base->self_handle)
+ {
+ DEBUG ("Returning my own cur.act.: %s -> %u",
+ priv->self->olpc_cur_act ? priv->self->olpc_cur_act : "",
+ priv->self->olpc_cur_act_room);
+ salut_svc_olpc_buddy_info_return_from_get_current_activity (context,
+ priv->self->olpc_cur_act ? priv->self->olpc_cur_act : "",
+ priv->self->olpc_cur_act_room);
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact
+ (priv->contact_manager, handle);
+
+ if (contact == NULL)
+ {
+ /* FIXME: should this be InvalidHandle? */
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Unknown contact" };
+ DEBUG ("Returning error: unknown contact");
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ DEBUG ("Returning buddy %u cur.act.: %s -> %u", handle,
+ contact->olpc_cur_act ? contact->olpc_cur_act : "",
+ contact->olpc_cur_act_room);
+ salut_svc_olpc_buddy_info_return_from_get_current_activity (context,
+ contact->olpc_cur_act ? contact->olpc_cur_act : "",
+ contact->olpc_cur_act_room);
+ g_object_unref (contact);
+ }
+}
+
+static void
+salut_connection_olpc_set_current_activity (SalutSvcOLPCBuddyInfo *iface,
+ const gchar *activity_id,
+ TpHandle room_handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ DEBUG ("called");
+
+ if (activity_id[0] == '\0')
+ {
+ if (room_handle != 0)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "If activity ID is empty, room handle must be 0" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+ }
+ else
+ {
+ if (!check_room (base, room_handle, context))
+ return;
+ }
+
+ if (!salut_self_set_olpc_current_activity (priv->self, activity_id,
+ room_handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ salut_svc_olpc_buddy_info_return_from_set_current_activity (context);
+}
+
+static void
+salut_connection_olpc_get_activities (SalutSvcOLPCBuddyInfo *iface,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GPtrArray *arr;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ DEBUG ("called for %u", handle);
+
+ if (!check_contact (base, handle, context))
+ return;
+
+ if (handle == base->self_handle)
+ {
+ arr = g_ptr_array_new ();
+ salut_self_foreach_olpc_activity (priv->self, append_activity, arr);
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact
+ (priv->contact_manager, handle);
+
+ if (contact == NULL)
+ {
+ /* FIXME: should this be InvalidHandle? */
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Unknown contact" };
+ DEBUG ("Returning error: unknown contact");
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ arr = g_ptr_array_new ();
+ salut_contact_foreach_olpc_activity (contact, append_activity, arr);
+ g_object_unref (contact);
+ }
+
+ salut_svc_olpc_buddy_info_return_from_get_activities (context, arr);
+ free_olpc_activities (arr);
+}
+
+static void
+salut_connection_olpc_set_activities (SalutSvcOLPCBuddyInfo *iface,
+ const GPtrArray *activities,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_ROOM);
+ GHashTable *room_to_act_id = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, (GDestroyNotify) g_free);
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ for (i = 0; i < activities->len; i++)
+ {
+ GValue pair = {0};
+ gchar *activity;
+ guint room_handle;
+
+ g_value_init (&pair, ACTIVITY_PAIR_TYPE);
+ g_value_set_static_boxed (&pair, g_ptr_array_index (activities, i));
+ dbus_g_type_struct_get (&pair,
+ 0, &activity,
+ 1, &room_handle,
+ G_MAXUINT);
+
+ if (activity[0] == '\0')
+ {
+ GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid empty activity ID" };
+
+ DEBUG ("%s", e.message);
+ dbus_g_method_return_error (context, &e);
+ g_free (activity);
+ goto finally;
+ }
+
+ if (!tp_handle_is_valid (room_repo, room_handle, &error))
+ {
+ DEBUG ("Invalid room handle %u: %s", room_handle, error->message);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ g_free (activity);
+ goto finally;
+ }
+
+ g_hash_table_insert (room_to_act_id, GUINT_TO_POINTER (room_handle),
+ activity);
+ }
+
+ if (!salut_self_set_olpc_activities (priv->self, room_to_act_id, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ }
+ else
+ {
+ salut_svc_olpc_buddy_info_return_from_set_activities (context);
+ }
+
+finally:
+ g_hash_table_unref (room_to_act_id);
+}
+
+static void
+salut_connection_olpc_add_activity (SalutSvcOLPCBuddyInfo *iface,
+ const gchar *id,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!salut_self_add_olpc_activity (priv->self, id, handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ }
+ else
+ {
+ salut_svc_olpc_buddy_info_return_from_set_activities (context);
+ }
+}
+
+static void
+salut_connection_olpc_buddy_info_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutSvcOLPCBuddyInfoClass *klass =
+ (SalutSvcOLPCBuddyInfoClass *) g_iface;
+#define IMPLEMENT(x) salut_svc_olpc_buddy_info_implement_##x (klass, \
+ salut_connection_olpc_##x)
+ IMPLEMENT(set_properties);
+ IMPLEMENT(get_properties);
+ IMPLEMENT(set_activities);
+ IMPLEMENT(add_activity);
+ IMPLEMENT(get_activities);
+ IMPLEMENT(set_current_activity);
+ IMPLEMENT(get_current_activity);
+#undef IMPLEMENT
+}
+
+static void
+salut_connection_act_get_properties (SalutSvcOLPCActivityProperties *iface,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_ROOM);
+ GHashTable *properties = NULL;
+ GError *error = NULL;
+ SalutOlpcActivity *activity;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handle_is_valid (room_repo, handle, &error))
+ goto error;
+
+ activity = salut_olpc_activity_manager_get_activity_by_room (
+ priv->olpc_activity_manager, handle);
+ if (activity == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Activity unknown: %u", handle);
+ goto error;
+ }
+
+ properties = salut_olpc_activity_create_properties_table (activity);
+
+ salut_svc_olpc_buddy_info_return_from_get_properties (context, properties);
+ g_hash_table_unref (properties);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static gboolean
+check_color (const gchar *color)
+{
+ int len, i;
+
+ /* be very anal about the color format */
+ len = strlen (color);
+ if (len != 15)
+ return FALSE;
+
+ for (i = 0 ; i < len ; i++)
+ {
+ switch (i)
+ {
+ case 0:
+ case 8:
+ if (color[i] != '#')
+ return FALSE;
+ break;
+ case 7:
+ if (color[i] != ',')
+ return FALSE;
+ break;
+ default:
+ if (!isxdigit (color[i]))
+ return FALSE;
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+/* returned strings are only valid as long as the hash table isn't modified */
+static gboolean
+extract_properties_from_hash (GHashTable *properties,
+ const gchar **id,
+ const gchar **color,
+ const gchar **name,
+ const gchar **type,
+ const gchar **tags,
+ gboolean *is_private,
+ GError **error)
+{
+ GValue *activity_id_val, *color_val, *activity_name_val, *activity_type_val,
+ *tags_val, *is_private_val;
+
+ /* activity ID */
+ activity_id_val = g_hash_table_lookup (properties, "id");
+ if (activity_id_val != NULL)
+ {
+ if (G_VALUE_TYPE (activity_id_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Activity ID value should be of type s");
+ return FALSE;
+ }
+
+ if (id != NULL)
+ *id = g_value_get_string (activity_id_val);
+ }
+
+ /* color */
+ color_val = g_hash_table_lookup (properties, "color");
+ if (color_val != NULL)
+ {
+ if (G_VALUE_TYPE (color_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Color value should be of type s");
+ return FALSE;
+ }
+
+ if (color != NULL)
+ {
+ *color = g_value_get_string (color_val);
+
+ if (!check_color (*color))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Color value has an incorrect format");
+ return FALSE;
+ }
+ }
+ }
+
+ /* name */
+ activity_name_val = g_hash_table_lookup (properties, "name");
+ if (activity_name_val != NULL)
+ {
+ if (G_VALUE_TYPE (activity_name_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "name value should be of type s");
+ return FALSE;
+ }
+
+ if (name != NULL)
+ *name = g_value_get_string (activity_name_val);
+ }
+
+ /* type */
+ activity_type_val = g_hash_table_lookup (properties, "type");
+ if (activity_type_val != NULL)
+ {
+ if (G_VALUE_TYPE (activity_type_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "type value should be of type s");
+ return FALSE;
+ }
+
+ if (type != NULL)
+ {
+ *type = g_value_get_string (activity_type_val);
+
+ if (*type[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "type value must be non-empty");
+ return FALSE;
+ }
+ }
+ }
+
+ /* tags */
+ tags_val = g_hash_table_lookup (properties, "tags");
+ if (tags_val != NULL)
+ {
+ if (G_VALUE_TYPE (activity_type_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "tags value should be of type s");
+ return FALSE;
+ }
+
+ if (type != NULL)
+ *tags = g_value_get_string (tags_val);
+ }
+
+ /* is_private */
+ is_private_val = g_hash_table_lookup (properties, "private");
+ if (is_private_val != NULL)
+ {
+ if (G_VALUE_TYPE (is_private_val) != G_TYPE_BOOLEAN)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "private value should be of type b");
+ return FALSE;
+ }
+
+ if (is_private != NULL)
+ *is_private = g_value_get_boolean (is_private_val);
+ }
+
+ return TRUE;
+}
+
+static void
+salut_connection_act_set_properties (SalutSvcOLPCActivityProperties *iface,
+ TpHandle handle,
+ GHashTable *properties,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GError *error = NULL;
+ const gchar *known_properties[] = { "color", "name", "type", "private",
+ "tags", NULL };
+ const gchar *color = NULL, *name = NULL, *type = NULL, *tags = NULL;
+ gboolean is_private = TRUE;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!check_room (base, handle, context))
+ return;
+
+ if (g_hash_table_find (properties, find_unknown_properties, known_properties)
+ != NULL)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unknown property given");
+ goto error;
+ }
+
+ if (!extract_properties_from_hash (properties, NULL, &color, &name, &type,
+ &tags, &is_private, &error))
+ goto error;
+
+ if (!salut_self_set_olpc_activity_properties (priv->self, handle, color,
+ name, type, tags, is_private, &error))
+ goto error;
+
+ salut_svc_olpc_activity_properties_return_from_set_properties (context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+typedef struct
+{
+ SalutContact *inviter;
+ SalutOlpcActivity *activity;
+} muc_ready_ctx;
+
+static muc_ready_ctx *
+muc_ready_ctx_new (SalutContact *inviter,
+ SalutOlpcActivity *activity)
+{
+ muc_ready_ctx *ctx = g_slice_new (muc_ready_ctx);
+ ctx->inviter = inviter;
+ g_object_ref (inviter);
+ ctx->activity = activity;
+ g_object_ref (activity);
+ return ctx;
+}
+
+static void
+muc_ready_ctx_free (muc_ready_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ g_object_unref (ctx->inviter);
+ g_object_unref (ctx->activity);
+ g_slice_free (muc_ready_ctx, ctx);
+}
+
+static void
+muc_ready_cb (SalutMucChannel *muc,
+ muc_ready_ctx *ctx)
+{
+ /* We joined the muc so have to forget about invites */
+ salut_contact_left_activity (ctx->inviter, ctx->activity);
+
+ DEBUG ("forget invite received from %s", ctx->inviter->name);
+ g_signal_handlers_disconnect_matched (muc, G_SIGNAL_MATCH_DATA, 0, 0, NULL,
+ NULL, ctx);
+ muc_ready_ctx_free (ctx);
+}
+
+static void
+muc_closed_cb (SalutMucChannel *muc,
+ muc_ready_ctx *ctx)
+{
+ /* FIXME: should we call left_private_activity here too ? */
+
+ g_signal_handlers_disconnect_matched (muc, G_SIGNAL_MATCH_DATA, 0, 0, NULL,
+ NULL, ctx);
+ muc_ready_ctx_free (ctx);
+}
+
+void
+salut_connection_olpc_observe_invitation (SalutConnection *self,
+ TpHandle room,
+ TpHandle inviter_handle,
+ WockyNode *invite_node)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ WockyNode *props_node;
+ GHashTable *properties;
+ const gchar *activity_id, *color = NULL, *activity_name = NULL,
+ *activity_type = NULL, *tags = NULL;
+ SalutContact *inviter;
+ SalutOlpcActivity *activity;
+ SalutMucChannel *muc;
+ muc_ready_ctx *ctx;
+
+ props_node = wocky_node_get_child_ns (invite_node, "properties",
+ NS_OLPC_ACTIVITY_PROPS);
+
+ if (props_node == NULL)
+ return;
+
+ inviter = salut_contact_manager_get_contact (priv->contact_manager,
+ inviter_handle);
+ if (inviter == NULL)
+ return;
+
+ properties = salut_wocky_node_extract_properties (props_node,
+ "property");
+
+ if (!extract_properties_from_hash (properties, &activity_id, &color,
+ &activity_name, &activity_type, &tags, NULL, NULL))
+ return;
+
+ activity = salut_olpc_activity_manager_got_invitation (
+ priv->olpc_activity_manager,
+ room, inviter, activity_id, activity_name, activity_type,
+ color, tags);
+
+ muc = salut_muc_manager_get_text_channel (priv->muc_manager, room);
+ g_assert (muc != NULL);
+
+ ctx = muc_ready_ctx_new (inviter, activity);
+ g_signal_connect (muc, "ready", G_CALLBACK (muc_ready_cb), ctx);
+ g_signal_connect (muc, "closed", G_CALLBACK (muc_closed_cb), ctx);
+
+ g_object_unref (muc);
+ g_hash_table_unref (properties);
+ g_object_unref (inviter);
+}
+
+static void
+salut_connection_act_get_activity (SalutSvcOLPCActivityProperties *iface,
+ const gchar *activity_id,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GError *error = NULL;
+ SalutOlpcActivity *activity;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ activity = salut_olpc_activity_manager_get_activity_by_id (
+ priv->olpc_activity_manager, activity_id);
+ if (activity == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Activity unknown: %s", activity_id);
+ goto error;
+ }
+
+ salut_svc_olpc_activity_properties_return_from_get_activity (context,
+ activity->room);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+salut_connection_olpc_activity_properties_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutSvcOLPCActivityPropertiesClass *klass =
+ (SalutSvcOLPCActivityPropertiesClass *) g_iface;
+#define IMPLEMENT(x) salut_svc_olpc_activity_properties_implement_##x \
+ (klass, salut_connection_act_##x)
+ IMPLEMENT(set_properties);
+ IMPLEMENT(get_properties);
+ IMPLEMENT(get_activity);
+#undef IMPLEMENT
+}
+#endif
+
+gchar *
+salut_normalize_non_empty (const gchar *id,
+ GError **error)
+{
+ g_return_val_if_fail (id != NULL, NULL);
+
+ if (*id == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Salut handle names may not be the empty string");
+ return NULL;
+ }
+
+ return g_strdup (id);
+}
+
+static gchar *
+handle_normalize_require_nonempty (TpHandleRepoIface *repo G_GNUC_UNUSED,
+ const gchar *id,
+ gpointer context G_GNUC_UNUSED,
+ GError **error)
+{
+ return salut_normalize_non_empty (id, error);
+}
+
+/* Connection baseclass function implementations */
+static void
+salut_connection_create_handle_repos (TpBaseConnection *self,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, handle_normalize_require_nonempty, NULL);
+
+ repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_ROOM, handle_normalize_require_nonempty, NULL);
+}
+
+static void
+_contact_manager_contact_change_cb (SalutContactManager *mgr,
+ SalutContact *contact, int changes, gpointer data)
+{
+ SalutConnection *self = SALUT_CONNECTION(data);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ TP_BASE_CONNECTION(self), TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle;
+
+ handle = tp_handle_lookup (handle_repo, contact->name, NULL, NULL);
+
+ if (changes & SALUT_CONTACT_ALIAS_CHANGED)
+ {
+ _contact_manager_contact_alias_changed (self, contact, handle);
+ }
+
+ if (changes & SALUT_CONTACT_STATUS_CHANGED)
+ {
+ _contact_manager_contact_status_changed (self, contact, handle);
+ }
+
+ if (changes & SALUT_CONTACT_AVATAR_CHANGED)
+ {
+ _contact_manager_contact_avatar_changed (self, contact, handle);
+ }
+
+ if (changes & ( SALUT_CONTACT_REAL_NAME_CHANGED
+ | SALUT_CONTACT_EMAIL_CHANGED
+ | SALUT_CONTACT_JID_CHANGED
+ ))
+ {
+ salut_conn_contact_info_changed (self, contact, handle);
+ }
+
+#ifdef ENABLE_OLPC
+ if (changes & SALUT_CONTACT_OLPC_PROPERTIES)
+ _contact_manager_contact_olpc_properties_changed (self, contact, handle);
+
+ if (changes & SALUT_CONTACT_OLPC_CURRENT_ACTIVITY)
+ salut_svc_olpc_buddy_info_emit_current_activity_changed (self,
+ handle, contact->olpc_cur_act, contact->olpc_cur_act_room);
+
+ if (changes & SALUT_CONTACT_OLPC_ACTIVITIES)
+ _contact_manager_contact_olpc_activities_changed (self, contact, handle);
+#endif
+}
+
+#ifdef ENABLE_OLPC
+static void
+_olpc_activity_manager_activity_modified_cb (SalutOlpcActivityManager *mgr,
+ SalutOlpcActivity *activity, SalutConnection *self)
+{
+ GHashTable *properties;
+
+ properties = salut_olpc_activity_create_properties_table (activity);
+ salut_svc_olpc_activity_properties_emit_activity_properties_changed (
+ self, activity->room, properties);
+
+ g_hash_table_unref (properties);
+}
+
+gboolean
+salut_connection_olpc_observe_muc_stanza (SalutConnection *self,
+ TpHandle room, TpHandle sender, WockyStanza *stanza)
+{
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+ SalutConnectionPrivate *priv = self->priv;
+ WockyNode *props_node;
+ GHashTable *properties;
+ const gchar *activity_id, *color = NULL, *activity_name = NULL,
+ *activity_type = NULL, *tags = NULL;
+ gboolean is_private = FALSE;
+ SalutOlpcActivity *activity;
+
+ props_node = wocky_node_get_child_ns (node, "properties",
+ NS_OLPC_ACTIVITY_PROPS);
+
+ if (props_node == NULL)
+ return FALSE;
+
+ activity = salut_olpc_activity_manager_get_activity_by_room (
+ priv->olpc_activity_manager, room);
+
+ if (activity == NULL)
+ {
+ DEBUG ("no activity in room %d", room);
+ return FALSE;
+ }
+
+ properties = salut_wocky_node_extract_properties (props_node,
+ "property");
+
+ if (!extract_properties_from_hash (properties, &activity_id, &color,
+ &activity_name, &activity_type, &tags, &is_private, NULL))
+ return TRUE;
+
+ salut_olpc_activity_update (activity, room, activity_id, activity_name,
+ activity_type, color, tags, is_private);
+
+ g_hash_table_unref (properties);
+
+ return TRUE;
+}
+
+static gboolean
+uninvite_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutConnection *self = SALUT_CONNECTION (user_data);
+ SalutConnectionPrivate *priv = self->priv;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self, TP_HANDLE_TYPE_ROOM);
+ WockyNode *node;
+ TpHandle room_handle;
+ const gchar *room, *activity_id;
+ SalutOlpcActivity *activity;
+ WockyNode *top_node = wocky_stanza_get_top_node (stanza);
+ SalutContact *contact = SALUT_CONTACT (wocky_stanza_get_from_contact (stanza));
+
+ node = wocky_node_get_child_ns (top_node, "uninvite",
+ NS_OLPC_ACTIVITY_PROPS);
+ g_assert (node != NULL);
+
+ room = wocky_node_get_attribute (node, "room");
+ if (room == NULL)
+ {
+ DEBUG ("No room attribute");
+ return FALSE;
+ }
+
+ room_handle = tp_handle_lookup (room_repo, room, NULL, NULL);
+ if (room_handle == 0)
+ {
+ DEBUG ("room %s unknown", room);
+ return FALSE;
+ }
+
+ activity_id = wocky_node_get_attribute (node, "id");
+ if (activity_id == NULL)
+ {
+ DEBUG ("No id attribute");
+ return FALSE;
+ }
+
+ DEBUG ("received uninvite from %s", contact->name);
+
+ activity = salut_olpc_activity_manager_get_activity_by_room (
+ priv->olpc_activity_manager, room_handle);
+
+ if (activity == NULL)
+ return FALSE;
+
+ salut_contact_left_activity (contact, activity);
+
+ return TRUE;
+}
+
+#endif
+
+static GPtrArray *
+salut_connection_create_channel_factories (TpBaseConnection *base)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+ GPtrArray *factories = g_ptr_array_sized_new (4);
+
+ /* Create the contact manager */
+ priv->contact_manager = salut_discovery_client_create_contact_manager (
+ priv->discovery_client, self);
+ g_signal_connect (priv->contact_manager, "contact-change",
+ G_CALLBACK (_contact_manager_contact_change_cb), self);
+
+#ifdef ENABLE_OLPC
+ priv->uninvite_handler_id = wocky_porter_register_handler_from_anyone (
+ self->porter,
+ WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ uninvite_stanza_callback, self,
+ '(', "uninvite",
+ ':', NS_OLPC_ACTIVITY_PROPS,
+ ')', NULL);
+
+ /* create the OLPC activity manager */
+ priv->olpc_activity_manager =
+ salut_discovery_client_create_olpc_activity_manager (
+ priv->discovery_client, self);
+ g_signal_connect (priv->olpc_activity_manager, "activity-modified",
+ G_CALLBACK (_olpc_activity_manager_activity_modified_cb), self);
+#endif
+
+ return factories;
+}
+
+#ifdef ENABLE_OLPC
+static void
+muc_channel_closed_cb (SalutMucChannel *chan,
+ SalutOlpcActivity *activity)
+{
+ SalutConnection *conn;
+ SalutConnectionPrivate *priv;
+ TpBaseConnection *base;
+ GPtrArray *activities = g_ptr_array_new ();
+
+ g_signal_handlers_disconnect_by_func (chan,
+ G_CALLBACK (muc_channel_closed_cb), activity);
+
+ g_object_get (activity,
+ "connection", &conn,
+ NULL);
+
+ priv = conn->priv;
+ base = (TpBaseConnection *) conn;
+
+ salut_self_remove_olpc_activity (priv->self, activity);
+
+ salut_self_foreach_olpc_activity (priv->self, append_activity, activities);
+ salut_svc_olpc_buddy_info_emit_activities_changed (conn, base->self_handle,
+ activities);
+ free_olpc_activities (activities);
+
+ /* we were holding a ref since the channel was opened */
+ g_object_unref (activity);
+
+ g_object_unref (conn);
+}
+
+static void
+muc_manager_new_channels_cb (TpChannelManager *channel_manager,
+ GHashTable *channels,
+ SalutConnection *conn)
+{
+ SalutConnectionPrivate *priv = conn->priv;
+ GHashTableIter iter;
+ gpointer chan;
+
+ g_hash_table_iter_init (&iter, channels);
+ while (g_hash_table_iter_next (&iter, &chan, NULL))
+ {
+ SalutOlpcActivity *activity;
+ TpHandle room_handle;
+
+ if (!SALUT_IS_MUC_CHANNEL (chan))
+ return;
+
+ g_object_get (chan,
+ "handle", &room_handle,
+ NULL);
+
+ /* ref the activity as long as we have a channel open */
+ activity = salut_olpc_activity_manager_ensure_activity_by_room (
+ priv->olpc_activity_manager,
+ room_handle);
+
+ g_signal_connect (chan, "closed", G_CALLBACK (muc_channel_closed_cb),
+ activity);
+ }
+}
+#endif
+
+static void
+add_to_array (gpointer data,
+ gpointer user_data)
+{
+ g_ptr_array_add (user_data, data);
+}
+
+static GPtrArray *
+salut_connection_create_channel_managers (TpBaseConnection *base)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+ GPtrArray *managers = g_ptr_array_sized_new (1);
+ GPtrArray *tmp;
+ SalutPluginLoader *loader;
+
+ /* FIXME: The second and third arguments depend on create_channel_factories
+ * being called before this; should telepathy-glib guarantee that or
+ * should we be defensive?
+ */
+ priv->im_manager = salut_im_manager_new (self, priv->contact_manager);
+
+ priv->ft_manager = salut_ft_manager_new (self, priv->contact_manager);
+
+ priv->muc_manager = salut_discovery_client_create_muc_manager (
+ priv->discovery_client, self);
+
+ priv->roomlist_manager = salut_discovery_client_create_roomlist_manager (
+ priv->discovery_client, self);
+
+#if 0
+ priv->tubes_manager = salut_tubes_manager_new (self, priv->contact_manager);
+#endif
+
+ g_ptr_array_add (managers, priv->im_manager);
+ g_ptr_array_add (managers, priv->contact_manager);
+ g_ptr_array_add (managers, priv->ft_manager);
+ g_ptr_array_add (managers, priv->muc_manager);
+ g_ptr_array_add (managers, priv->roomlist_manager);
+#if 0
+ g_ptr_array_add (managers, priv->tubes_manager);
+#endif
+
+#ifdef ENABLE_OLPC
+ g_signal_connect (TP_CHANNEL_MANAGER (priv->muc_manager), "new-channels",
+ G_CALLBACK (muc_manager_new_channels_cb), self);
+#endif
+
+ /* plugin channel managers */
+ loader = salut_plugin_loader_dup ();
+ tmp = salut_plugin_loader_create_channel_managers (loader, base);
+ g_object_unref (loader);
+
+ g_ptr_array_foreach (tmp, add_to_array, managers);
+ g_ptr_array_unref (tmp);
+
+ return managers;
+}
+
+
+static gchar *
+salut_connection_get_unique_connection_name (TpBaseConnection *base)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+
+ return g_strdup (priv->published_name);
+}
+
+static void
+force_close_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SalutConnection *self = SALUT_CONNECTION (user_data);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ GError *error = NULL;
+
+ if (!wocky_porter_force_close_finish (WOCKY_PORTER (source),
+ res, &error))
+ {
+ DEBUG ("force close failed: %s", error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ DEBUG ("connection properly closed (forced)");
+ }
+
+ tp_base_connection_finish_shutdown (base);
+
+ g_object_unref (self);
+}
+
+static void
+closed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SalutConnection *self = SALUT_CONNECTION (user_data);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ GError *error = NULL;
+ gboolean force_called = priv->disconnect_timer == 0;
+
+ if (priv->disconnect_timer != 0)
+ {
+ /* stop the timer */
+ g_source_remove (priv->disconnect_timer);
+ priv->disconnect_timer = 0;
+ }
+
+ if (!wocky_porter_close_finish (WOCKY_PORTER (source), res, &error))
+ {
+ DEBUG ("close failed: %s", error->message);
+
+ if (g_error_matches (error, WOCKY_PORTER_ERROR,
+ WOCKY_PORTER_ERROR_FORCIBLY_CLOSED))
+ {
+ /* Close operation has been aborted because a force_close operation
+ * has been started. tp_base_connection_finish_shutdown will be
+ * called once this force_close operation is completed so we don't
+ * do it here. */
+
+ g_error_free (error);
+ goto out;
+ }
+
+ g_error_free (error);
+ }
+ else
+ {
+ DEBUG ("connection properly closed");
+ }
+
+ if (!force_called)
+ tp_base_connection_finish_shutdown (base);
+
+out:
+ g_object_unref (self);
+}
+
+static gboolean
+disconnect_timeout_cb (gpointer data)
+{
+ SalutConnection *self = SALUT_CONNECTION (data);
+ SalutConnectionPrivate *priv = self->priv;
+
+ DEBUG ("Close operation timed out. Force closing");
+ priv->disconnect_timer = 0;
+
+ wocky_porter_force_close_async (self->porter, NULL, force_close_cb, g_object_ref (self));
+ return FALSE;
+}
+
+static void
+salut_connection_shut_down (TpBaseConnection *base)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+
+ _salut_connection_disconnect (self);
+
+ if (self->session != NULL)
+ {
+ DEBUG ("connection may still be open; closing it: %p", self);
+
+ g_assert (priv->disconnect_timer == 0);
+ priv->disconnect_timer = g_timeout_add_seconds (DISCONNECT_TIMEOUT,
+ disconnect_timeout_cb, self);
+
+ wocky_porter_close_async (self->porter, NULL, closed_cb, g_object_ref (self));
+ return;
+ }
+
+ DEBUG ("session is not alive; clean up the base connection");
+ tp_base_connection_finish_shutdown (base);
+}
+
+static gboolean
+salut_connection_start_connecting (TpBaseConnection *base, GError **error)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+ GError *client_error = NULL;
+
+ g_signal_connect (priv->discovery_client, "state-changed",
+ G_CALLBACK (_discovery_client_state_changed_cb), self);
+
+ if (!salut_discovery_client_start (priv->discovery_client, &client_error))
+ {
+ *error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Unable to initialize the avahi client: %s",
+ client_error->message);
+ DEBUG ("%s", (*error)->message);
+ g_error_free (client_error);
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ tp_base_connection_change_status (
+ TP_BASE_CONNECTION (base),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ return FALSE;
+}
+
+/* sidecar stuff */
+static gchar *
+make_sidecar_path (
+ SalutConnection *conn,
+ const gchar *sidecar_iface)
+{
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
+
+ return g_strdelimit (
+ g_strdup_printf ("%s/Sidecar/%s", base_conn->object_path, sidecar_iface),
+ ".", '/');
+}
+
+static gchar *
+connection_install_sidecar (
+ SalutConnection *conn,
+ SalutSidecar *sidecar,
+ const gchar *sidecar_iface)
+{
+ SalutConnectionPrivate *priv = conn->priv;
+ TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (
+ (TpBaseConnection *) conn);
+ gchar *path = make_sidecar_path (conn, sidecar_iface);
+
+ tp_dbus_daemon_register_object (bus, path, G_OBJECT (sidecar));
+ g_hash_table_insert (priv->sidecars, g_strdup (sidecar_iface),
+ g_object_ref (sidecar));
+
+ return path;
+}
+
+typedef struct {
+ SalutConnection *conn;
+ gchar *sidecar_iface;
+} Grr;
+
+static Grr *
+grr_new (
+ SalutConnection *conn,
+ const gchar *sidecar_iface)
+{
+ Grr *grr = g_slice_new (Grr);
+
+ grr->conn = g_object_ref (conn);
+ grr->sidecar_iface = g_strdup (sidecar_iface);
+
+ return grr;
+}
+
+static void
+grr_free (Grr *grr)
+{
+ g_object_unref (grr->conn);
+ g_free (grr->sidecar_iface);
+
+ g_slice_free (Grr, grr);
+}
+
+static void
+create_sidecar_cb (
+ GObject *loader_obj,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ SalutPluginLoader *loader = SALUT_PLUGIN_LOADER (loader_obj);
+ Grr *ctx = user_data;
+ SalutConnection *conn = ctx->conn;
+ SalutConnectionPrivate *priv = conn->priv;
+ const gchar *sidecar_iface = ctx->sidecar_iface;
+ SalutSidecar *sidecar;
+ GList *contexts;
+ GError *error = NULL;
+
+ sidecar = salut_plugin_loader_create_sidecar_finish (loader, result, &error);
+ contexts = g_hash_table_lookup (priv->pending_sidecars, sidecar_iface);
+
+ if (contexts == NULL)
+ {
+ /* We never use the empty list as a value in pending_sidecars, so this
+ * must mean we've disconnected and already returned. Jettison the
+ * sidecar!
+ */
+ DEBUG ("creating sidecar %s %s after connection closed; jettisoning!",
+ sidecar_iface, (sidecar != NULL ? "succeeded" : "failed"));
+ goto out;
+ }
+
+ if (sidecar != NULL)
+ {
+ const gchar *actual_iface = salut_sidecar_get_interface (sidecar);
+
+ if (tp_strdiff (ctx->sidecar_iface, actual_iface))
+ {
+ /* TODO: maybe this lives in the loader? It knows what the plugin is
+ * called. */
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "A buggy plugin created a %s sidecar when asked to create %s",
+ actual_iface, ctx->sidecar_iface);
+ }
+ }
+ else /* sidecar == NULL */
+ {
+ /* If creating the sidecar failed, 'error' should have been set */
+ g_return_if_fail (error != NULL);
+ }
+
+ if (error == NULL)
+ {
+ gchar *path = connection_install_sidecar (ctx->conn, sidecar,
+ ctx->sidecar_iface);
+ GHashTable *props = salut_sidecar_get_immutable_properties (sidecar);
+ GList *l;
+
+ for (l = contexts; l != NULL; l = l->next)
+ salut_svc_connection_future_return_from_ensure_sidecar (l->data,
+ path, props);
+
+ g_hash_table_unref (props);
+ g_free (path);
+ }
+ else
+ {
+ g_list_foreach (contexts, (GFunc) dbus_g_method_return_error, error);
+ }
+
+ g_hash_table_remove (ctx->conn->priv->pending_sidecars, ctx->sidecar_iface);
+
+out:
+ tp_clear_object (&sidecar);
+ g_clear_error (&error);
+
+ grr_free (ctx);
+}
+
+static void
+salut_connection_ensure_sidecar (
+ SalutSvcConnectionFUTURE *iface,
+ const gchar *sidecar_iface,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *conn = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = conn->priv;
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
+ SalutSidecar *sidecar;
+ gpointer key, value;
+ GError *error = NULL;
+
+ if (base_conn->status == TP_CONNECTION_STATUS_DISCONNECTED)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_DISCONNECTED,
+ "This connection has already disconnected" };
+
+ DEBUG ("already disconnected, declining request for %s", sidecar_iface);
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ if (!tp_dbus_check_valid_interface_name (sidecar_iface, &error))
+ {
+ error->domain = TP_ERRORS;
+ error->code = TP_ERROR_INVALID_ARGUMENT;
+ DEBUG ("%s is malformed: %s", sidecar_iface, error->message);
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ sidecar = g_hash_table_lookup (priv->sidecars, sidecar_iface);
+
+ if (sidecar != NULL)
+ {
+ gchar *path = make_sidecar_path (conn, sidecar_iface);
+ GHashTable *props = salut_sidecar_get_immutable_properties (sidecar);
+
+ DEBUG ("sidecar %s already exists at %s", sidecar_iface, path);
+ salut_svc_connection_future_return_from_ensure_sidecar (context, path,
+ props);
+
+ g_free (path);
+ g_hash_table_unref (props);
+ return;
+ }
+
+ if (g_hash_table_lookup_extended (priv->pending_sidecars, sidecar_iface,
+ &key, &value))
+ {
+ GList *contexts = value;
+
+ DEBUG ("already awaiting %s, joining a queue of %u", sidecar_iface,
+ g_list_length (contexts));
+
+ contexts = g_list_prepend (contexts, context);
+ g_hash_table_steal (priv->pending_sidecars, key);
+ g_hash_table_insert (priv->pending_sidecars, key, contexts);
+ return;
+ }
+
+ DEBUG ("enqueuing first request for %s", sidecar_iface);
+ g_hash_table_insert (priv->pending_sidecars, g_strdup (sidecar_iface),
+ g_list_prepend (NULL, context));
+
+ if (base_conn->status == TP_CONNECTION_STATUS_CONNECTED)
+ {
+ SalutPluginLoader *loader = salut_plugin_loader_dup ();
+
+ DEBUG ("requesting %s from the plugin loader", sidecar_iface);
+ salut_plugin_loader_create_sidecar_async (loader, sidecar_iface, conn,
+ conn->session, create_sidecar_cb, grr_new (conn, sidecar_iface));
+ g_object_unref (loader);
+ }
+ else
+ {
+ DEBUG ("not yet connected; waiting.");
+ }
+}
+
+static void
+sidecars_conn_status_changed_cb (
+ SalutConnection *conn,
+ guint status,
+ guint reason,
+ gpointer unused)
+{
+ SalutConnectionPrivate *priv = conn->priv;
+ TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (
+ (TpBaseConnection *) conn);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+ {
+ g_hash_table_iter_init (&iter, priv->sidecars);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ DEBUG ("removing %s from the bus", salut_sidecar_get_interface (value));
+ tp_dbus_daemon_unregister_object (bus, G_OBJECT (value));
+ }
+
+ g_hash_table_iter_init (&iter, priv->pending_sidecars);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const gchar *sidecar_iface = key;
+ GList *contexts = value;
+ GError *error = g_error_new (TP_ERRORS, TP_ERROR_CANCELLED,
+ "Disconnected before %s could be created", sidecar_iface);
+
+ DEBUG ("failing all %u requests for %s", g_list_length (contexts),
+ sidecar_iface);
+ g_list_foreach (contexts, (GFunc) dbus_g_method_return_error, error);
+ g_error_free (error);
+ }
+
+ g_hash_table_remove_all (priv->sidecars);
+ g_hash_table_remove_all (priv->pending_sidecars);
+ }
+ else if (status == TP_CONNECTION_STATUS_CONNECTED)
+ {
+ SalutPluginLoader *loader = salut_plugin_loader_dup ();
+
+ DEBUG ("connected; requesting sidecars from plugins");
+ g_hash_table_iter_init (&iter, priv->pending_sidecars);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ const gchar *sidecar_iface = key;
+
+ DEBUG ("requesting %s from the plugin loader", sidecar_iface);
+ salut_plugin_loader_create_sidecar_async (loader, sidecar_iface, conn,
+ conn->session, create_sidecar_cb, grr_new (conn, sidecar_iface));
+ }
+
+ g_object_unref (loader);
+ }
+}
+
+static void
+salut_conn_future_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutSvcConnectionFUTUREClass *klass = g_iface;
+
+#define IMPLEMENT(x) \
+ salut_svc_connection_future_implement_##x (\
+ klass, salut_connection_##x)
+ IMPLEMENT(ensure_sidecar);
+#undef IMPLEMENT
+}
+
+WockySession *
+salut_connection_get_session (SalutConnection *connection)
+{
+ g_return_val_if_fail (SALUT_IS_CONNECTION (connection), NULL);
+
+ return connection->session;
+}
+
+const gchar *
+salut_connection_get_name (SalutConnection *connection)
+{
+ g_return_val_if_fail (SALUT_IS_CONNECTION (connection), NULL);
+
+ return connection->name;
+}
diff --git a/salut/src/connection.h b/salut/src/connection.h
new file mode 100644
index 000000000..25c7e9514
--- /dev/null
+++ b/salut/src/connection.h
@@ -0,0 +1,97 @@
+/*
+ * connection.h - Header for SalutConnection
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_CONNECTION_H__
+#define __SALUT_CONNECTION_H__
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/presence-mixin.h>
+#include <telepathy-glib/svc-connection.h>
+
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-session.h>
+
+#include "salut/connection.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutPresenceCache SalutPresenceCache;
+typedef struct _SalutDisco SalutDisco;
+
+typedef struct _SalutConnectionPrivate SalutConnectionPrivate;
+
+struct _SalutConnectionClass {
+ TpBaseConnectionClass parent_class;
+ TpDBusPropertiesMixinClass properties_mixin;
+ TpPresenceMixinClass presence_mixin;
+ TpContactsMixinClass contacts_mixin;
+};
+
+struct _SalutConnection {
+ TpBaseConnection parent;
+ TpPresenceMixin presence_mixin;
+ TpContactsMixin contacts_mixin;
+
+ SalutPresenceCache *presence_cache;
+ SalutDisco *disco;
+
+ WockySession *session;
+ WockyPorter *porter;
+
+ /* Our name on the network */
+ gchar *name;
+
+ SalutConnectionPrivate *priv;
+};
+
+typedef enum {
+ LIST_HANDLE_PUBLISH = 1,
+ LIST_HANDLE_SUBSCRIBE,
+ LIST_HANDLE_KNOWN,
+
+ LIST_HANDLE_FIRST = LIST_HANDLE_PUBLISH,
+ LIST_HANDLE_LAST = LIST_HANDLE_KNOWN
+} SalutConnectionListHandle;
+
+#ifdef ENABLE_OLPC
+void
+salut_connection_olpc_observe_invitation (SalutConnection *connection,
+ TpHandle room, TpHandle invitor_handle, WockyNode *invite_node);
+
+gboolean
+salut_connection_olpc_observe_muc_stanza (SalutConnection *self, TpHandle room,
+ TpHandle sender, WockyStanza *stanza);
+#endif
+
+const gchar * const *salut_connection_get_implemented_interfaces (void);
+
+gchar *salut_normalize_non_empty (const gchar *id, GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_CONNECTION_H__*/
diff --git a/salut/src/contact-manager.c b/salut/src/contact-manager.c
new file mode 100644
index 000000000..ec4720345
--- /dev/null
+++ b/salut/src/contact-manager.c
@@ -0,0 +1,449 @@
+/*
+ * contact-manager.c - Source for SalutContactManager
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <salut/caps-channel-manager.h>
+
+#include "connection.h"
+#include "contact-manager.h"
+#include "signals-marshal.h"
+#include "contact.h"
+#include "enumtypes.h"
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/interfaces.h>
+
+#define DEBUG_FLAG DEBUG_CONTACTS
+#include "debug.h"
+
+static void salut_contact_manager_close_all (SalutContactManager *mgr);
+
+static void
+_contact_finalized_cb (gpointer data, GObject *old_object);
+
+G_DEFINE_TYPE_WITH_CODE(SalutContactManager, salut_contact_manager,
+ TP_TYPE_BASE_CONTACT_LIST,
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_CAPS_CHANNEL_MANAGER, NULL))
+
+/* signal enum */
+enum
+{
+ CONTACT_CHANGE,
+ ALL_FOR_NOW,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+typedef struct _SalutContactManagerPrivate SalutContactManagerPrivate;
+
+struct _SalutContactManagerPrivate
+{
+ TpHandleSet *handles;
+ gulong status_changed_id;
+ gboolean dispose_has_run;
+};
+
+#define SALUT_CONTACT_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_CONTACT_MANAGER, SalutContactManagerPrivate))
+
+static TpHandleSet *
+contact_manager_dup_contacts (TpBaseContactList *base)
+{
+ SalutContactManagerPrivate *priv = SALUT_CONTACT_MANAGER_GET_PRIVATE (base);
+
+ return tp_handle_set_copy (priv->handles);
+}
+
+static void
+contact_manager_dup_states (TpBaseContactList *base,
+ TpHandle contact,
+ TpSubscriptionState *subscribe,
+ TpSubscriptionState *publish,
+ gchar **publish_request)
+{
+ SalutContactManagerPrivate *priv = SALUT_CONTACT_MANAGER_GET_PRIVATE (base);
+
+ if (tp_handle_set_is_member (priv->handles, contact))
+ {
+ if (subscribe != NULL)
+ *subscribe = TP_SUBSCRIPTION_STATE_YES;
+
+ if (publish != NULL)
+ *publish = TP_SUBSCRIPTION_STATE_YES;
+
+ if (publish_request != NULL)
+ *publish_request = NULL;
+ }
+ else
+ {
+ if (subscribe != NULL)
+ *subscribe = TP_SUBSCRIPTION_STATE_NO;
+
+ if (publish != NULL)
+ *publish = TP_SUBSCRIPTION_STATE_NO;
+
+ if (publish_request != NULL)
+ *publish_request = NULL;
+ }
+}
+
+static void
+salut_contact_manager_init (SalutContactManager *obj)
+{
+ /* allocate any data required by the object here */
+ obj->contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+static void salut_contact_manager_constructed (GObject *obj);
+static void salut_contact_manager_dispose (GObject *object);
+static void salut_contact_manager_finalize (GObject *object);
+
+static void
+salut_contact_manager_class_init (SalutContactManagerClass *salut_contact_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_contact_manager_class);
+ TpBaseContactListClass *base_class = (TpBaseContactListClass *) object_class;
+
+ g_type_class_add_private (salut_contact_manager_class, sizeof (SalutContactManagerPrivate));
+
+ object_class->constructed = salut_contact_manager_constructed;
+ object_class->dispose = salut_contact_manager_dispose;
+ object_class->finalize = salut_contact_manager_finalize;
+
+ base_class->dup_states = contact_manager_dup_states;
+ base_class->dup_contacts = contact_manager_dup_contacts;
+ base_class->get_contact_list_persists = tp_base_contact_list_false_func;
+
+ signals[CONTACT_CHANGE] = g_signal_new ("contact-change",
+ G_OBJECT_CLASS_TYPE(salut_contact_manager_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ salut_signals_marshal_VOID__OBJECT_INT,
+ G_TYPE_NONE, 2,
+ SALUT_TYPE_CONTACT,
+ G_TYPE_INT);
+
+ signals[ALL_FOR_NOW] = g_signal_new ("all-for-now",
+ G_OBJECT_CLASS_TYPE(salut_contact_manager_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+connection_status_changed_cb (SalutConnection *conn,
+ guint status,
+ guint reason,
+ SalutContactManager *self)
+{
+ if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+ {
+ salut_contact_manager_close_all (self);
+ }
+}
+
+static void
+salut_contact_manager_constructed (GObject *obj)
+{
+ SalutContactManager *self = (SalutContactManager *) obj;
+ SalutContactManagerPrivate *priv = SALUT_CONTACT_MANAGER_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo;
+ TpBaseConnection *base_connection;
+
+ base_connection = tp_base_contact_list_get_connection (
+ (TpBaseContactList *) self, NULL);
+ self->connection = g_object_ref (base_connection);
+
+ contact_repo = tp_base_connection_get_handles (base_connection,
+ TP_HANDLE_TYPE_CONTACT);
+ priv->handles = tp_handle_set_new (contact_repo);
+
+ priv->status_changed_id = g_signal_connect (self->connection,
+ "status-changed", (GCallback) connection_status_changed_cb, self);
+
+ if (G_OBJECT_CLASS (salut_contact_manager_parent_class)->constructed)
+ G_OBJECT_CLASS (salut_contact_manager_parent_class)->constructed (obj);
+}
+
+static gboolean
+dispose_contact (gpointer key, gpointer value, gpointer object)
+{
+ SalutContact *contact = SALUT_CONTACT(value);
+ SalutContactManager *self = SALUT_CONTACT_MANAGER (object);
+
+ g_object_weak_unref (G_OBJECT(contact), _contact_finalized_cb, object);
+ g_signal_handlers_disconnect_matched (contact, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, object);
+
+ SALUT_CONTACT_MANAGER_GET_CLASS (self)->dispose_contact (self, contact);
+ return TRUE;
+}
+
+static void
+salut_contact_manager_dispose (GObject *object)
+{
+ SalutContactManager *self = SALUT_CONTACT_MANAGER (object);
+ SalutContactManagerPrivate *priv = SALUT_CONTACT_MANAGER_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG("Disposing contact manager");
+
+ priv->dispose_has_run = TRUE;
+
+ /* release any references held by the object here */
+ salut_contact_manager_close_all (self);
+
+ if (G_OBJECT_CLASS (salut_contact_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_contact_manager_parent_class)->dispose (object);
+}
+
+void
+salut_contact_manager_finalize (GObject *object)
+{
+ //SalutContactManager *self = SALUT_CONTACT_MANAGER (object);
+ //SalutContactManagerPrivate *priv = SALUT_CONTACT_MANAGER_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (salut_contact_manager_parent_class)->finalize (object);
+}
+
+static void
+contact_found_cb (SalutContact *contact, gpointer userdata)
+{
+ SalutContactManager *self = userdata;
+ SalutContactManagerPrivate *priv = SALUT_CONTACT_MANAGER_GET_PRIVATE (self);
+
+ tp_handle_set_add (priv->handles, contact->handle);
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self,
+ contact->handle);
+}
+
+static void
+contact_change_cb (SalutContact *contact, gint changes, gpointer userdata)
+{
+ SalutContactManager *mgr = SALUT_CONTACT_MANAGER (userdata);
+
+ DEBUG("Emitting contact changes for %s: %d", contact->name, changes);
+
+ g_signal_emit (mgr, signals[CONTACT_CHANGE], 0, contact, changes);
+}
+
+static void
+contact_lost_cb (SalutContact *contact, gpointer userdata)
+{
+ SalutContactManager *self = userdata;
+ SalutContactManagerPrivate *priv = SALUT_CONTACT_MANAGER_GET_PRIVATE (self);
+
+ tp_handle_set_remove (priv->handles, contact->handle);
+ tp_base_contact_list_one_contact_removed ((TpBaseContactList *) self,
+ contact->handle);
+}
+
+static gboolean
+_contact_remove_finalized (gpointer key, gpointer value, gpointer data)
+{
+ return data == value;
+}
+
+static void
+_contact_finalized_cb (gpointer data, GObject *old_object)
+{
+ SalutContactManager *mgr = SALUT_CONTACT_MANAGER(data);
+
+ g_hash_table_foreach_remove (mgr->contacts, _contact_remove_finalized,
+ old_object);
+}
+
+void
+salut_contact_manager_contact_created (SalutContactManager *self,
+ SalutContact *contact)
+{
+ g_assert (g_hash_table_lookup (self->contacts, contact->name) == NULL);
+
+ g_hash_table_insert (self->contacts, g_strdup (contact->name), contact);
+ DEBUG("Adding %s to contacts", contact->name);
+
+ g_signal_connect (contact, "found",
+ G_CALLBACK(contact_found_cb), self);
+ g_signal_connect (contact, "contact-change",
+ G_CALLBACK(contact_change_cb), self);
+ g_signal_connect (contact, "lost",
+ G_CALLBACK(contact_lost_cb), self);
+
+ g_object_weak_ref (G_OBJECT (contact), _contact_finalized_cb , self);
+}
+
+SalutContact *
+salut_contact_manager_ensure_contact (SalutContactManager *self,
+ const gchar *name)
+{
+ SalutContact *contact;
+
+ contact = g_hash_table_lookup (self->contacts, name);
+ if (contact == NULL)
+ {
+ DEBUG ("contact %s doesn't exist yet. Creating it", name);
+ contact = SALUT_CONTACT_MANAGER_GET_CLASS (self)->create_contact (self,
+ name);
+ salut_contact_manager_contact_created (self, contact);
+ }
+ else
+ {
+ g_object_ref (contact);
+ }
+
+ return contact;
+}
+
+static void
+salut_contact_manager_close_all (SalutContactManager *mgr)
+{
+ SalutContactManagerPrivate *priv =
+ SALUT_CONTACT_MANAGER_GET_PRIVATE (mgr);
+
+ SALUT_CONTACT_MANAGER_GET_CLASS (mgr)->close_all (mgr);
+
+ tp_clear_pointer (&priv->handles, tp_handle_set_destroy);
+
+ if (mgr->contacts)
+ {
+ g_hash_table_foreach_remove (mgr->contacts, dispose_contact, mgr);
+ g_hash_table_unref (mgr->contacts);
+ mgr->contacts = NULL;
+ }
+
+ if (priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (mgr->connection, priv->status_changed_id);
+ priv->status_changed_id = 0;
+ }
+ tp_clear_object (&mgr->connection);
+}
+
+static void
+salut_contact_manager_all_for_now_cb (SalutContactManager *self)
+{
+ TpBaseContactList *base = (TpBaseContactList *) self;
+
+ DEBUG ("Contact list received");
+
+ g_assert (tp_base_contact_list_get_state (base, NULL) ==
+ TP_CONTACT_LIST_STATE_WAITING);
+
+ tp_base_contact_list_set_list_received (base);
+}
+
+/* public functions */
+gboolean
+salut_contact_manager_start (SalutContactManager *self,
+ GError **error)
+{
+ TpBaseContactList *base = (TpBaseContactList *) self;
+ gboolean success;
+ GError *err = NULL;
+
+ g_assert (tp_base_contact_list_get_state (base, NULL) ==
+ TP_CONTACT_LIST_STATE_NONE);
+
+ g_signal_connect (self, "all-for-now",
+ G_CALLBACK (salut_contact_manager_all_for_now_cb), NULL);
+
+ success = SALUT_CONTACT_MANAGER_GET_CLASS (self)->start (self, &err);
+ if (success)
+ {
+ /* Wait for all-for-now */
+ tp_base_contact_list_set_list_pending (base);
+ }
+ else
+ {
+ tp_base_contact_list_set_list_failed (base, err->domain, err->code,
+ err->message);
+ }
+
+ if (err != NULL)
+ g_propagate_error (error, err);
+
+ return success;
+}
+
+SalutContact *
+salut_contact_manager_get_contact (SalutContactManager *mgr, TpHandle handle)
+{
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ TP_BASE_CONNECTION (mgr->connection), TP_HANDLE_TYPE_CONTACT);
+ const char *name = tp_handle_inspect (handle_repo, handle);
+ SalutContact *ret;
+
+ g_return_val_if_fail (name, NULL);
+
+ if (mgr->contacts == NULL)
+ return NULL;
+
+ DEBUG ("Getting contact for: %s", name);
+ ret = g_hash_table_lookup (mgr->contacts, name);
+
+ if (ret != NULL)
+ g_object_ref (ret);
+ else
+ DEBUG ("Failed to get contact for %s", name);
+
+ return ret;
+}
+
+static void
+_find_by_address (gpointer key, gpointer value, gpointer user_data)
+{
+ struct sockaddr *address =
+ (struct sockaddr *)((gpointer *) user_data)[0];
+ GList **list = (GList **)((gpointer *) user_data)[1];
+ guint size = GPOINTER_TO_UINT (((gpointer *) user_data)[2]);
+ SalutContact *contact = SALUT_CONTACT (value);
+
+ if (salut_contact_has_address (contact, address, size)) {
+ g_object_ref (contact);
+ *list = g_list_append (*list, contact);
+ }
+}
+
+/* FIXME function name is just too long */
+GList *
+salut_contact_manager_find_contacts_by_address (SalutContactManager *mgr,
+ struct sockaddr *address, guint size)
+{
+ GList *list = NULL;
+ gpointer data[3];
+
+ data[0] = address;
+ data[1] = &list;
+ data[2] = GUINT_TO_POINTER (size);
+ g_hash_table_foreach (mgr->contacts, _find_by_address, data);
+ return list;
+}
diff --git a/salut/src/contact-manager.h b/salut/src/contact-manager.h
new file mode 100644
index 000000000..5bb7ca324
--- /dev/null
+++ b/salut/src/contact-manager.h
@@ -0,0 +1,91 @@
+/*
+ * contact-manager.h - Header for SalutContactManager
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#ifndef __SALUT_CONTACT_MANAGER_H__
+#define __SALUT_CONTACT_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-contact-list.h>
+
+#include "connection.h"
+#include "contact.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutContactManager SalutContactManager;
+typedef struct _SalutContactManagerClass SalutContactManagerClass;
+
+struct _SalutContactManagerClass {
+ TpBaseContactListClass parent_class;
+
+ /* public abstract methods */
+ gboolean (*start) (SalutContactManager *self, GError **error);
+
+ /* private abstract methods */
+ SalutContact * (*create_contact) (SalutContactManager *self,
+ const gchar *name);
+ void (*dispose_contact) (SalutContactManager *self,
+ SalutContact *contact);
+ void (*close_all) (SalutContactManager *self);
+};
+
+struct _SalutContactManager {
+ TpBaseContactList parent;
+
+ /* private */
+ SalutConnection *connection;
+ GHashTable *contacts;
+};
+
+
+GType salut_contact_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_CONTACT_MANAGER \
+ (salut_contact_manager_get_type ())
+#define SALUT_CONTACT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_CONTACT_MANAGER, SalutContactManager))
+#define SALUT_CONTACT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_CONTACT_MANAGER, SalutContactManagerClass))
+#define SALUT_IS_CONTACT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_CONTACT_MANAGER))
+#define SALUT_IS_CONTACT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_CONTACT_MANAGER))
+#define SALUT_CONTACT_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_CONTACT_MANAGER, SalutContactManagerClass))
+
+gboolean salut_contact_manager_start (SalutContactManager *mgr, GError **error);
+
+SalutContact *
+salut_contact_manager_get_contact (SalutContactManager *mgr, TpHandle handle);
+
+GList *
+salut_contact_manager_find_contacts_by_address (SalutContactManager *mgr,
+ struct sockaddr *address, guint size);
+
+SalutContact * salut_contact_manager_ensure_contact (SalutContactManager *mgr,
+ const gchar *name);
+
+/* restricted methods */
+void salut_contact_manager_contact_created (SalutContactManager *self,
+ SalutContact *contact);
+
+#endif /* #ifndef __SALUT_CONTACT_MANAGER_H__*/
diff --git a/salut/src/contact.c b/salut/src/contact.c
new file mode 100644
index 000000000..c04c21c9c
--- /dev/null
+++ b/salut/src/contact.c
@@ -0,0 +1,911 @@
+/*
+ * contact.c - Source for salut_contact
+ * Copyright (C) 2005-2006 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "contact.h"
+#include "signals-marshal.h"
+#include "presence.h"
+#include "presence-cache.h"
+#include "enumtypes.h"
+
+#include <telepathy-glib/util.h>
+
+#include <wocky/wocky-xep-0115-capabilities.h>
+
+#define DEBUG_FLAG DEBUG_CONTACTS
+#include <debug.h>
+
+#define DEBUG_CONTACT(contact, format, ...) G_STMT_START { \
+ DEBUG ("Contact %s: " format, contact->name, ##__VA_ARGS__); \
+} G_STMT_END
+
+static void xep_0115_capabilities_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (SalutContact, salut_contact, WOCKY_TYPE_LL_CONTACT,
+ G_IMPLEMENT_INTERFACE (WOCKY_TYPE_XEP_0115_CAPABILITIES,
+ xep_0115_capabilities_iface_init);
+)
+
+/* properties */
+enum {
+ PROP_CONNECTION = 1,
+ PROP_NAME,
+ LAST_PROP
+};
+
+/* signal enum */
+enum
+{
+ FOUND,
+ LOST,
+ CONTACT_CHANGE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+
+typedef struct {
+ salut_contact_get_avatar_callback callback;
+ gpointer user_data;
+} AvatarRequest;
+
+struct _SalutContactPrivate
+{
+ gboolean dispose_has_run;
+ gchar *alias;
+ GList *avatar_requests;
+#ifdef ENABLE_OLPC
+ /* room handle owned by the SalutOlpcActivity -> SalutOlpcActivity */
+ GHashTable *olpc_activities;
+#endif
+ gboolean found;
+ gboolean frozen;
+ guint pending_changes;
+};
+
+static GObject *
+salut_contact_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutContact *self;
+ TpHandleRepoIface *contact_repo;
+
+ obj = G_OBJECT_CLASS (salut_contact_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_CONTACT (obj);
+
+ g_assert (self->connection != NULL);
+ g_assert (self->name != NULL);
+
+ contact_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) self->connection, TP_HANDLE_TYPE_CONTACT);
+
+ self->handle = tp_handle_ensure (contact_repo, self->name, NULL, NULL);
+
+ self->caps = gabble_capability_set_new ();
+ self->data_forms = g_ptr_array_new_with_free_func (g_object_unref);
+
+ return obj;
+}
+
+static void
+connection_status_changed_cb (SalutConnection *connection,
+ guint status,
+ guint reason,
+ SalutContact *self)
+{
+ if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+ self->connection = NULL;
+}
+
+static void
+salut_contact_constructed (GObject *obj)
+{
+ SalutContact *self = SALUT_CONTACT (obj);
+
+ if (G_OBJECT_CLASS (salut_contact_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (salut_contact_parent_class)->constructed (obj);
+
+ tp_g_signal_connect_object (self->connection,
+ "status-changed", G_CALLBACK (connection_status_changed_cb), self, 0);
+}
+
+static void
+salut_contact_init (SalutContact *obj)
+{
+ SalutContactPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj,
+ SALUT_TYPE_CONTACT, SalutContactPrivate);
+
+ obj->priv = priv;
+
+ /* allocate any data required by the object here */
+ obj->name = NULL;
+ obj->status = SALUT_PRESENCE_AVAILABLE;
+ obj->status_message = NULL;
+ obj->avatar_token = NULL;
+ obj->jid = NULL;
+#ifdef ENABLE_OLPC
+ obj->olpc_key = NULL;
+ obj->olpc_color = NULL;
+ obj->olpc_cur_act = NULL;
+ obj->olpc_cur_act_room = 0;
+ obj->olpc_ip4 = NULL;
+ obj->olpc_ip6 = NULL;
+ priv->olpc_activities = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) g_object_unref);
+#endif
+ priv->found = FALSE;
+ priv->alias = NULL;
+}
+
+static void
+salut_contact_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutContact *self = SALUT_CONTACT (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->connection);
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, self->name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_contact_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutContact *self = SALUT_CONTACT (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ self->connection = g_value_get_object (value);
+ break;
+ case PROP_NAME:
+ self->name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void salut_contact_dispose (GObject *object);
+static void salut_contact_finalize (GObject *object);
+
+static void
+salut_contact_class_init (SalutContactClass *salut_contact_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_contact_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_contact_class, sizeof (SalutContactPrivate));
+
+ object_class->constructor = salut_contact_constructor;
+ object_class->constructed = salut_contact_constructed;
+ object_class->get_property = salut_contact_get_property;
+ object_class->set_property = salut_contact_set_property;
+
+ object_class->dispose = salut_contact_dispose;
+ object_class->finalize = salut_contact_finalize;
+
+ signals[FOUND] = g_signal_new ("found",
+ G_OBJECT_CLASS_TYPE(salut_contact_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CONTACT_CHANGE] = g_signal_new ("contact-change",
+ G_OBJECT_CLASS_TYPE(salut_contact_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ signals[LOST] = g_signal_new ("lost",
+ G_OBJECT_CLASS_TYPE(salut_contact_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "The Salut Connection associated with this contact",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "name",
+ "name",
+ "The name of this contact",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NAME,
+ param_spec);
+}
+
+#ifdef ENABLE_OLPC
+static void
+disconnect_activity_signal_foreach (TpHandle room,
+ SalutOlpcActivity *activity,
+ SalutContact *self)
+{
+ g_signal_handlers_disconnect_matched (activity, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, self);
+}
+#endif
+
+void
+salut_contact_dispose (GObject *object)
+{
+ SalutContact *self = SALUT_CONTACT (object);
+ SalutContactPrivate *priv = self->priv;
+
+ DEBUG_CONTACT (self, "Disposing contact");
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+#ifdef ENABLE_OLPC
+ if (self->olpc_cur_act_room != 0 && self->connection != NULL)
+ {
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) self->connection, TP_HANDLE_TYPE_ROOM);
+
+ tp_handle_unref (room_repo, self->olpc_cur_act_room);
+ self->olpc_cur_act_room = 0;
+ }
+
+ g_hash_table_foreach (priv->olpc_activities,
+ (GHFunc) disconnect_activity_signal_foreach, self);
+ g_hash_table_unref (priv->olpc_activities);
+#endif
+
+ salut_contact_avatar_request_flush (self, NULL, 0);
+
+ /* release any references held by the object here */
+
+ if (self->handle != 0 && self->connection != NULL)
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) self->connection, TP_HANDLE_TYPE_CONTACT);
+ tp_handle_unref (contact_repo, self->handle);
+ }
+
+ if (self->data_forms != NULL)
+ {
+ g_ptr_array_unref (self->data_forms);
+ self->data_forms = NULL;
+ }
+
+ if (G_OBJECT_CLASS (salut_contact_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_contact_parent_class)->dispose (object);
+}
+
+void
+salut_contact_finalize (GObject *object)
+{
+ SalutContact *self = SALUT_CONTACT (object);
+ SalutContactPrivate *priv = self->priv;
+
+ /* free any data held directly by the object here */
+ g_free (self->name);
+ g_free (self->status_message);
+ g_free (priv->alias);
+ g_free (self->avatar_token);
+ g_free (self->first);
+ g_free (self->last);
+ g_free (self->full_name);
+ g_free (self->email);
+ g_free (self->jid);
+
+#ifdef ENABLE_OLPC
+ if (self->olpc_key != NULL)
+ {
+ g_array_unref (self->olpc_key);
+ }
+ g_free (self->olpc_color);
+ g_free (self->olpc_cur_act);
+ g_free (self->olpc_ip4);
+ g_free (self->olpc_ip6);
+#endif
+
+ G_OBJECT_CLASS (salut_contact_parent_class)->finalize (object);
+}
+
+static void
+purge_cached_avatar (SalutContact *self,
+ const gchar *token)
+{
+ SalutContactPrivate *priv = self->priv;
+
+ g_free (self->avatar_token);
+ self->avatar_token = g_strdup (token);
+
+ /* the avatar token has changed, restart retrieving the avatar if we were
+ * retrieving it */
+ if (priv->avatar_requests != NULL)
+ SALUT_CONTACT_GET_CLASS (self)->retrieve_avatar (self);
+}
+
+#ifdef ENABLE_OLPC
+typedef struct
+{
+ SalutContactOLPCActivityFunc foreach;
+ gpointer user_data;
+} foreach_olpc_activity_ctx;
+
+static void
+foreach_olpc_activity (gpointer key, gpointer value, gpointer user_data)
+{
+ foreach_olpc_activity_ctx *ctx = user_data;
+ SalutOlpcActivity *activity = value;
+
+ /* ignore activity without ID */
+ if (activity->id == NULL)
+ return;
+
+ DEBUG ("%s => %u", activity->id, activity->room);
+ (ctx->foreach) (activity, ctx->user_data);
+}
+
+void
+salut_contact_foreach_olpc_activity (SalutContact *self,
+ SalutContactOLPCActivityFunc foreach,
+ gpointer user_data)
+{
+ SalutContactPrivate *priv = self->priv;
+ foreach_olpc_activity_ctx ctx = { foreach, user_data };
+
+ DEBUG ("called");
+
+ g_hash_table_foreach (priv->olpc_activities, foreach_olpc_activity,
+ &ctx);
+
+ DEBUG ("end");
+}
+
+#endif
+
+GArray *
+salut_contact_get_addresses (SalutContact *self)
+{
+ return SALUT_CONTACT_GET_CLASS (self)->get_addresses (self);
+}
+
+gboolean
+salut_contact_has_address (SalutContact *self,
+ struct sockaddr *address,
+ guint size)
+{
+ return SALUT_CONTACT_GET_CLASS (self)->has_address (self, address, size);
+}
+
+const gchar *
+salut_contact_get_alias (SalutContact *contact)
+{
+ SalutContactPrivate *priv = contact->priv;
+ if (priv->alias == NULL)
+ {
+ return contact->name;
+ }
+ return priv->alias;
+}
+
+void
+salut_contact_avatar_request_flush (SalutContact *contact,
+ guint8 *data,
+ gsize size)
+{
+ SalutContactPrivate *priv = contact->priv;
+ GList *list, *liststart;
+ AvatarRequest *request;
+
+ liststart = priv->avatar_requests;
+ priv->avatar_requests = NULL;
+
+ for (list = liststart; list != NULL; list = g_list_next (list)) {
+ request = (AvatarRequest *) list->data;
+ request->callback (contact, data, size, request->user_data);
+ g_slice_free (AvatarRequest, request);
+ }
+ g_list_free (liststart);
+}
+
+void
+salut_contact_get_avatar (SalutContact *contact,
+ salut_contact_get_avatar_callback callback, gpointer user_data)
+{
+ SalutContactPrivate *priv = contact->priv;
+ AvatarRequest *request;
+ gboolean retrieve;
+
+ g_assert (contact != NULL);
+
+ if (contact->avatar_token == NULL)
+ {
+ DEBUG ("Avatar requestes for a contact without one (%s)", contact->name);
+ callback (contact, NULL, 0, user_data);
+ return;
+ }
+
+ DEBUG ("Requesting avatar for: %s", contact->name);
+ request = g_slice_new0 (AvatarRequest);
+ request->callback = callback;
+ request->user_data = user_data;
+ retrieve = (priv->avatar_requests == NULL);
+ priv->avatar_requests = g_list_append (priv->avatar_requests, request);
+
+ if (retrieve)
+ SALUT_CONTACT_GET_CLASS (contact)->retrieve_avatar (contact);
+}
+
+void
+salut_contact_set_capabilities (SalutContact *contact,
+ const GabbleCapabilitySet *caps,
+ const GPtrArray *data_forms)
+{
+ guint i;
+
+ gabble_capability_set_free (contact->caps);
+ contact->caps = gabble_capability_set_copy (caps);
+
+ g_ptr_array_unref (contact->data_forms);
+
+ contact->data_forms = g_ptr_array_new_with_free_func (g_object_unref);
+
+ for (i = 0; i < data_forms->len; i++)
+ {
+ GObject *form = g_ptr_array_index (data_forms, i);
+ g_ptr_array_add (contact->data_forms, g_object_ref (form));
+ }
+
+ g_signal_emit_by_name (contact, "capabilities-changed");
+}
+
+static void
+salut_contact_change (SalutContact *self, guint changes)
+{
+ SalutContactPrivate *priv = self->priv;
+
+ priv->pending_changes |= changes;
+
+ if (!priv->frozen && priv->pending_changes != 0)
+ {
+ g_signal_emit (self, signals[CONTACT_CHANGE], 0,
+ priv->pending_changes);
+ priv->pending_changes = 0;
+ return;
+ }
+}
+
+void
+salut_contact_change_real_name (
+ SalutContact *self,
+ const gchar *first,
+ const gchar *last)
+{
+ if (tp_str_empty (first))
+ first = NULL;
+
+ if (tp_str_empty (last))
+ last = NULL;
+
+ if (tp_strdiff (self->first, first) || tp_strdiff (self->last, last))
+ {
+ g_free (self->first);
+ self->first = g_strdup (first);
+ g_free (self->last);
+ self->last = g_strdup (last);
+
+ g_free (self->full_name);
+
+ if (first != NULL && last != NULL)
+ self->full_name = g_strdup_printf ("%s %s", first, last);
+ else if (first != NULL)
+ self->full_name = g_strdup (first);
+ else if (last != NULL)
+ self->full_name = g_strdup (last);
+ else
+ self->full_name = NULL;
+
+ salut_contact_change (self, SALUT_CONTACT_REAL_NAME_CHANGED);
+ }
+}
+
+void
+salut_contact_change_alias (SalutContact *self, const gchar *alias)
+{
+ SalutContactPrivate *priv = self->priv;
+
+ if (tp_strdiff (priv->alias, alias))
+ {
+ g_free (priv->alias);
+ priv->alias = g_strdup (alias);
+ salut_contact_change (self, SALUT_CONTACT_ALIAS_CHANGED);
+ }
+}
+
+void
+salut_contact_change_status (SalutContact *self, SalutPresenceId status)
+{
+ if (status != self->status && status < SALUT_PRESENCE_NR_PRESENCES)
+ {
+ self->status = status;
+ salut_contact_change (self, SALUT_CONTACT_STATUS_CHANGED);
+ }
+}
+
+void
+salut_contact_change_status_message (SalutContact *self, const gchar *message)
+{
+ if (tp_strdiff (self->status_message, message))
+ {
+ g_free (self->status_message);
+ self->status_message = g_strdup (message);
+ salut_contact_change (self, SALUT_CONTACT_STATUS_CHANGED);
+ }
+}
+
+void
+salut_contact_change_avatar_token (SalutContact *self,
+ const gchar *avatar_token)
+{
+ if (tp_strdiff (self->avatar_token, avatar_token))
+ {
+ /* Purge the cache */
+ purge_cached_avatar (self, avatar_token);
+ salut_contact_change (self, SALUT_CONTACT_AVATAR_CHANGED);
+ }
+}
+
+void
+salut_contact_change_email (SalutContact *self, gchar *email)
+{
+ if (tp_str_empty (email))
+ email = NULL;
+
+ if (tp_strdiff (self->email, email))
+ {
+ g_free (self->email);
+ self->email = g_strdup (email);
+ salut_contact_change (self, SALUT_CONTACT_EMAIL_CHANGED);
+ }
+}
+
+void
+salut_contact_change_jid (SalutContact *self, gchar *jid)
+{
+ if (tp_str_empty (jid))
+ jid = NULL;
+
+ if (tp_strdiff (self->jid, jid))
+ {
+ g_free (self->jid);
+ self->jid = g_strdup (jid);
+ salut_contact_change (self,
+ SALUT_CONTACT_JID_CHANGED
+#ifdef ENABLE_OLPC
+ | SALUT_CONTACT_OLPC_PROPERTIES
+#endif
+ );
+ }
+}
+
+void salut_contact_change_capabilities (SalutContact *self,
+ const gchar *hash,
+ const gchar *node,
+ const gchar *ver)
+{
+ if (self->connection == NULL)
+ return;
+
+ salut_presence_cache_process_caps (self->connection->presence_cache, self,
+ hash, node, ver);
+}
+
+#ifdef ENABLE_OLPC
+void
+salut_contact_change_olpc_color (SalutContact *self, const gchar *olpc_color)
+{
+ if (tp_strdiff (self->olpc_color, olpc_color))
+ {
+ g_free (self->olpc_color);
+ self->olpc_color = g_strdup (olpc_color);
+ salut_contact_change (self, SALUT_CONTACT_OLPC_PROPERTIES);
+ }
+}
+
+void
+salut_contact_change_olpc_key (SalutContact *self, GArray *olpc_key)
+{
+ if (olpc_key != NULL)
+ {
+ if (self->olpc_key == NULL || self->olpc_key->len != olpc_key->len ||
+ memcmp (self->olpc_key->data, olpc_key->data, olpc_key->len) != 0)
+ {
+ if (self->olpc_key != NULL)
+ {
+ g_array_unref (self->olpc_key);
+ }
+ self->olpc_key = g_array_sized_new (FALSE, FALSE,
+ sizeof (guint8), olpc_key->len);
+ g_array_append_vals (self->olpc_key, olpc_key->data,
+ olpc_key->len);
+ salut_contact_change (self, SALUT_CONTACT_OLPC_PROPERTIES);
+ }
+ }
+}
+
+void
+salut_contact_change_ipv4_addr (SalutContact *self, const gchar *ipv4_addr)
+{
+ if (tp_strdiff (ipv4_addr, self->olpc_ip4))
+ {
+ g_free (self->olpc_ip4);
+ self->olpc_ip4 = g_strdup (ipv4_addr);
+ salut_contact_change (self, SALUT_CONTACT_OLPC_PROPERTIES);
+ }
+
+}
+
+void
+salut_contact_change_ipv6_addr (SalutContact *self, const gchar *ipv6_addr)
+{
+ if (tp_strdiff (ipv6_addr, self->olpc_ip6))
+ {
+ g_free (self->olpc_ip6);
+ self->olpc_ip6 = g_strdup (ipv6_addr);
+ salut_contact_change (self, SALUT_CONTACT_OLPC_PROPERTIES);
+ }
+}
+
+void
+salut_contact_change_current_activity (SalutContact *self,
+ const gchar *current_activity_id,
+ const gchar *current_activity_room)
+{
+ TpHandleRepoIface *room_repo;
+ TpHandle room_handle = 0;
+
+ if (self->connection == NULL)
+ return;
+
+ room_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) self->connection, TP_HANDLE_TYPE_ROOM);
+
+ if (current_activity_room != NULL && *current_activity_room != '\0')
+ {
+ room_handle = tp_handle_ensure (room_repo, current_activity_room,
+ NULL, NULL);
+ if (room_handle == 0)
+ {
+ DEBUG ("Invalid room \"%s\" for current activity \"%s\": "
+ "ignoring", current_activity_room, current_activity_id);
+ }
+ }
+
+ if (current_activity_id == NULL || room_handle == 0)
+ {
+ DEBUG ("Unsetting current activity");
+ if (self->olpc_cur_act != NULL || self->olpc_cur_act_room != 0)
+ {
+ g_free (self->olpc_cur_act);
+ if (self->olpc_cur_act_room != 0)
+ tp_handle_unref (room_repo, self->olpc_cur_act_room);
+ self->olpc_cur_act = NULL;
+ self->olpc_cur_act_room = 0;
+ salut_contact_change (self, SALUT_CONTACT_OLPC_CURRENT_ACTIVITY);
+ }
+ if (room_handle != 0)
+ {
+ /* tp_handle_ensure gave us a ref */
+ tp_handle_unref (room_repo, room_handle);
+ }
+
+ }
+ else
+ {
+ DEBUG ("Current activity %s, room handle %d", current_activity_id,
+ room_handle);
+ if (tp_strdiff (self->olpc_cur_act, current_activity_id) ||
+ self->olpc_cur_act_room != room_handle)
+ {
+ g_free (self->olpc_cur_act);
+ if (self->olpc_cur_act_room != 0)
+ tp_handle_unref (room_repo, self->olpc_cur_act_room);
+ self->olpc_cur_act_room = room_handle;
+ self->olpc_cur_act = g_strdup (current_activity_id);
+ salut_contact_change (self, SALUT_CONTACT_OLPC_CURRENT_ACTIVITY);
+ }
+ else
+ {
+ /* tp_handle_ensure gave us a ref */
+ tp_handle_unref (room_repo, room_handle);
+ }
+ }
+}
+
+#endif
+
+void
+salut_contact_found (SalutContact *self)
+{
+ SalutContactPrivate *priv = self->priv;
+ if (priv->found)
+ return;
+
+ priv->found = TRUE;
+ g_signal_emit (self, signals[FOUND], 0);
+ /* When found everything changes */
+ g_signal_emit (self, signals[CONTACT_CHANGE], 0, 0xff);
+ priv->pending_changes = 0;
+}
+
+void
+salut_contact_lost (SalutContact *self)
+{
+ SalutContactPrivate *priv = self->priv;
+
+ self->status = SALUT_PRESENCE_OFFLINE;
+ g_free (self->status_message);
+ self->status_message = NULL;
+
+ if (!priv->found)
+ return;
+
+ DEBUG_CONTACT (self, "disappeared from the local link");
+
+ priv->found = FALSE;
+ g_signal_emit (self, signals[CONTACT_CHANGE], 0,
+ SALUT_CONTACT_STATUS_CHANGED);
+ g_signal_emit (self, signals[LOST], 0);
+}
+
+void
+salut_contact_freeze (SalutContact *self)
+{
+ SalutContactPrivate *priv = self->priv;
+
+ priv->frozen = TRUE;
+}
+
+void
+salut_contact_thaw (SalutContact *self)
+{
+ SalutContactPrivate *priv = self->priv;
+
+ if (!priv->frozen)
+ return;
+
+ priv->frozen = FALSE;
+ /* Triggers the emission of the changed signal */
+ salut_contact_change (self, 0);
+}
+
+#ifdef ENABLE_OLPC
+static void
+activity_valid_cb (SalutOlpcActivity *activity,
+ SalutContact *self)
+{
+ /* Now we can emit the ActivitiesChanged signal */
+ DEBUG ("activity in room %d (%s) is now valid", activity->room, activity->id);
+ g_signal_emit (self, signals[CONTACT_CHANGE], 0,
+ SALUT_CONTACT_OLPC_ACTIVITIES);
+}
+
+gboolean
+salut_contact_joined_activity (SalutContact *self,
+ SalutOlpcActivity *activity)
+{
+ SalutContactPrivate *priv = self->priv;
+
+ if (g_hash_table_lookup (priv->olpc_activities,
+ GUINT_TO_POINTER (activity->room)) != NULL)
+ return FALSE;
+
+ DEBUG_CONTACT (self, "joined activity %s", activity->id);
+ g_hash_table_insert (priv->olpc_activities, GUINT_TO_POINTER (activity->room),
+ activity);
+ g_object_ref (activity);
+
+ if (activity->id == NULL)
+ {
+ /* we can't emit the ActivitiesChanged signal right now as we don't have
+ * the activity ID. Thanks OLPC interface */
+ DEBUG ("activity in room %d isn't valid yet", activity->room);
+ g_signal_connect (activity, "valid", G_CALLBACK (activity_valid_cb),
+ self);
+ }
+ else
+ {
+ g_signal_emit (self, signals[CONTACT_CHANGE], 0,
+ SALUT_CONTACT_OLPC_ACTIVITIES);
+ }
+
+ return TRUE;
+}
+
+void
+salut_contact_left_activity (SalutContact *self,
+ SalutOlpcActivity *activity)
+{
+ SalutContactPrivate *priv = self->priv;
+
+ g_signal_handlers_disconnect_matched (activity, G_SIGNAL_MATCH_DATA, 0, 0,
+ NULL, NULL, self);
+
+ DEBUG_CONTACT (self, "left activity %s", activity->id);
+ if (!g_hash_table_remove (priv->olpc_activities,
+ GUINT_TO_POINTER (activity->room)))
+ return;
+
+ g_signal_emit (self, signals[CONTACT_CHANGE], 0,
+ SALUT_CONTACT_OLPC_ACTIVITIES);
+}
+#endif
+
+static const GPtrArray *
+salut_contact_get_data_forms (WockyXep0115Capabilities *caps)
+{
+ SalutContact *self = SALUT_CONTACT (caps);
+
+ return self->data_forms;
+}
+
+static void
+xep_0115_capabilities_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ WockyXep0115CapabilitiesInterface *iface = g_iface;
+
+ iface->get_data_forms = salut_contact_get_data_forms;
+}
diff --git a/salut/src/contact.h b/salut/src/contact.h
new file mode 100644
index 000000000..bfddd8872
--- /dev/null
+++ b/salut/src/contact.h
@@ -0,0 +1,196 @@
+/*
+ * contact.h - Header for SalutContact
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_CONTACT_H__
+#define __SALUT_CONTACT_H__
+
+#include <netinet/in.h>
+#include <glib-object.h>
+
+#include <telepathy-glib/handle-repo.h>
+
+#include <salut/capability-set.h>
+#include "presence.h"
+#include "connection.h"
+#ifdef ENABLE_OLPC
+#include "olpc-activity.h"
+#endif
+
+#include <wocky/wocky-ll-contact.h>
+
+G_BEGIN_DECLS
+
+enum {
+ SALUT_CONTACT_ALIAS_CHANGED = 0x1,
+ SALUT_CONTACT_STATUS_CHANGED = 0x2,
+ SALUT_CONTACT_AVATAR_CHANGED = 0x4,
+#ifdef ENABLE_OLPC
+ SALUT_CONTACT_OLPC_PROPERTIES = 0x8,
+ SALUT_CONTACT_OLPC_CURRENT_ACTIVITY = 0x10,
+ SALUT_CONTACT_OLPC_ACTIVITIES = 0x20,
+#endif /* ENABLE_OLPC */
+ SALUT_CONTACT_JID_CHANGED = 0x40,
+ SALUT_CONTACT_EMAIL_CHANGED = 0x80,
+ SALUT_CONTACT_REAL_NAME_CHANGED = 0x100,
+};
+
+typedef struct _SalutContact SalutContact;
+typedef struct _SalutContactClass SalutContactClass;
+typedef struct _SalutContactPrivate SalutContactPrivate;
+
+struct _SalutContactClass {
+ WockyLLContactClass parent_class;
+
+ /* public abstract methods */
+ GArray * (*get_addresses) (SalutContact *contact);
+ gboolean (*has_address) (SalutContact *contact,
+ struct sockaddr *address, guint size);
+
+ /* private abstract methods */
+ void (*retrieve_avatar) (SalutContact *contact);
+};
+
+struct _SalutContact {
+ WockyLLContact parent;
+ gchar *name;
+ SalutPresenceId status;
+ gchar *avatar_token;
+ gchar *status_message;
+ gchar *first;
+ gchar *last;
+ /* synthesized from first and last */
+ gchar *full_name;
+ gchar *email;
+ gchar *jid;
+
+ /* XEP-0115 Capabilities */
+ gchar *hash;
+ gchar *node;
+ gchar *ver;
+ GabbleCapabilitySet *caps;
+ GPtrArray *data_forms; /* of owned WockyDataForm*s */
+
+ TpHandle handle;
+#ifdef ENABLE_OLPC
+ GArray *olpc_key;
+ gchar *olpc_color;
+ gchar *olpc_cur_act;
+ TpHandle olpc_cur_act_room;
+ gchar *olpc_ip4;
+ gchar *olpc_ip6;
+#endif /* ENABLE_OLPC */
+
+ /* private */
+ SalutConnection *connection;
+ SalutContactPrivate *priv;
+};
+
+GType salut_contact_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_CONTACT \
+ (salut_contact_get_type ())
+#define SALUT_CONTACT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_CONTACT, SalutContact))
+#define SALUT_CONTACT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_CONTACT, SalutContactClass))
+#define SALUT_IS_CONTACT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_CONTACT))
+#define SALUT_IS_CONTACT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_CONTACT))
+#define SALUT_CONTACT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_CONTACT, SalutContactClass))
+
+typedef struct {
+ struct sockaddr_storage address;
+} salut_contact_address_t;
+
+/* Returns an array of addresses on which the contact can be found */
+GArray * salut_contact_get_addresses (SalutContact *contact);
+
+gboolean salut_contact_has_address (SalutContact *contact,
+ struct sockaddr *address,
+ guint size);
+const gchar * salut_contact_get_alias (SalutContact *contact);
+
+typedef void (*salut_contact_get_avatar_callback)(SalutContact *contact,
+ guint8 *avatar,
+ gsize size,
+ gpointer user_data);
+
+void salut_contact_get_avatar (SalutContact *contact,
+ salut_contact_get_avatar_callback callback,
+ gpointer user_data1);
+
+void salut_contact_set_capabilities (SalutContact *contact,
+ const GabbleCapabilitySet *caps,
+ const GPtrArray *data_forms);
+
+#ifdef ENABLE_OLPC
+typedef void (*SalutContactOLPCActivityFunc)
+ (SalutOlpcActivity *activity, gpointer user_data);
+
+void salut_contact_foreach_olpc_activity (SalutContact *self,
+ SalutContactOLPCActivityFunc foreach, gpointer user_data);
+
+gboolean salut_contact_joined_activity (SalutContact *self,
+ SalutOlpcActivity *activity);
+
+void salut_contact_left_activity (SalutContact *self,
+ SalutOlpcActivity *activity);
+#endif
+
+/* restricted methods */
+void salut_contact_change_real_name (SalutContact *self, const gchar *first,
+ const gchar *last);
+void salut_contact_change_alias (SalutContact *self, const gchar *alias);
+void salut_contact_change_status (SalutContact *self, SalutPresenceId);
+void salut_contact_change_status_message (SalutContact *self,
+ const gchar *message);
+void salut_contact_change_avatar_token (SalutContact *self,
+ const gchar *avatar_token);
+void salut_contact_change_email (SalutContact *self, gchar *email);
+void salut_contact_change_jid (SalutContact *self, gchar *jid);
+void salut_contact_change_capabilities (SalutContact *self,
+ const gchar *hash, const gchar *node, const gchar *ver);
+
+#ifdef ENABLE_OLPC
+void salut_contact_change_olpc_color (SalutContact *self,
+ const gchar *olpc_color);
+void salut_contact_change_olpc_key (SalutContact *self, GArray *olpc_key);
+void salut_contact_change_ipv4_addr (SalutContact *self,
+ const gchar *ipv4_addr);
+void salut_contact_change_ipv6_addr (SalutContact *self,
+ const gchar *ipv4_addr);
+void salut_contact_change_current_activity (SalutContact *self,
+ const gchar *current_activity_id, const gchar *current_activity_room);
+#endif
+
+void salut_contact_avatar_request_flush (SalutContact *self, guint8 *data,
+ gsize size);
+
+void salut_contact_found (SalutContact *self);
+void salut_contact_lost (SalutContact *self);
+
+void salut_contact_freeze (SalutContact *self);
+void salut_contact_thaw (SalutContact *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_CONTACT_H__*/
diff --git a/salut/src/debug.c b/salut/src/debug.c
new file mode 100644
index 000000000..47fdfc7f5
--- /dev/null
+++ b/salut/src/debug.c
@@ -0,0 +1,143 @@
+
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <telepathy-glib/debug.h>
+#include <telepathy-glib/debug-sender.h>
+
+#include "debug.h"
+
+#ifdef ENABLE_DEBUG
+
+static DebugFlags flags = 0;
+
+GDebugKey keys[] = {
+ { "presence", DEBUG_PRESENCE },
+ { "groups", DEBUG_GROUPS },
+ { "contacts", DEBUG_CONTACTS },
+ { "disco", DEBUG_DISCO },
+ { "properties", DEBUG_PROPERTIES },
+ { "roomlist", DEBUG_ROOMLIST },
+ { "media-channel", DEBUG_MEDIA },
+ { "im", DEBUG_IM },
+ { "muc", DEBUG_MUC },
+ { "muc-connection", DEBUG_MUC },
+ { "net", DEBUG_NET },
+ { "connection", DEBUG_CONNECTION },
+ { "self", DEBUG_SELF },
+ { "tubes", DEBUG_TUBES },
+ { "xmpp-connection-manager", DEBUG_XCM },
+ { "si-bytestream-manager", DEBUG_SI_BYTESTREAM_MGR },
+ { "discovery", DEBUG_DISCO },
+ { "olpc-activity", DEBUG_OLPC_ACTIVITY },
+ { "ft", DEBUG_FT },
+ { "plugin", DEBUG_PLUGIN },
+ { 0, },
+};
+
+void debug_set_flags_from_env ()
+{
+ guint nkeys;
+ const gchar *flags_string;
+
+ for (nkeys = 0; keys[nkeys].value; nkeys++);
+
+ flags_string = g_getenv ("SALUT_DEBUG");
+
+ tp_debug_set_flags (flags_string);
+
+ if (flags_string) {
+ debug_set_flags (g_parse_debug_string (flags_string, keys, nkeys));
+ }
+}
+
+void debug_set_flags (DebugFlags new_flags)
+{
+ flags |= new_flags;
+}
+
+gboolean debug_flag_is_set (DebugFlags flag)
+{
+ return flag & flags;
+}
+
+static GHashTable *flag_to_keys = NULL;
+
+static const gchar *
+debug_flag_to_domain (DebugFlags flag)
+{
+ if (G_UNLIKELY (flag_to_keys == NULL))
+ {
+ guint i;
+
+ flag_to_keys = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_free);
+
+ for (i = 0; keys[i].value; i++)
+ {
+ GDebugKey key = (GDebugKey) keys[i];
+ gchar *val;
+
+ val = g_strdup_printf ("%s/%s", G_LOG_DOMAIN, key.key);
+ g_hash_table_insert (flag_to_keys,
+ GUINT_TO_POINTER (key.value), val);
+ }
+ }
+
+ return g_hash_table_lookup (flag_to_keys, GUINT_TO_POINTER (flag));
+}
+
+void
+debug_free (void)
+{
+ if (flag_to_keys == NULL)
+ return;
+
+ g_hash_table_unref (flag_to_keys);
+ flag_to_keys = NULL;
+}
+
+static void
+log_to_debug_sender (DebugFlags flag,
+ const gchar *message)
+{
+ TpDebugSender *dbg;
+ GTimeVal now;
+
+ dbg = tp_debug_sender_dup ();
+
+ g_get_current_time (&now);
+
+ tp_debug_sender_add_message (dbg, &now, debug_flag_to_domain (flag),
+ G_LOG_LEVEL_DEBUG, message);
+
+ g_object_unref (dbg);
+}
+
+void debug (DebugFlags flag,
+ const gchar *format,
+ ...)
+{
+ gchar *message;
+ va_list args;
+
+ va_start (args, format);
+ message = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ log_to_debug_sender (flag, message);
+
+ if (flag & flags)
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%s", message);
+
+ g_free (message);
+}
+
+#endif /* ENABLE_DEBUG */
diff --git a/salut/src/debug.h b/salut/src/debug.h
new file mode 100644
index 000000000..49e01c7a0
--- /dev/null
+++ b/salut/src/debug.h
@@ -0,0 +1,76 @@
+
+#ifndef __DEBUG_H__
+#define __DEBUG_H__
+
+#include "config.h"
+
+#include <glib.h>
+
+#include <wocky/wocky-stanza.h>
+
+#ifdef ENABLE_DEBUG
+
+typedef enum
+{
+ DEBUG_PRESENCE = 1 << 0,
+ DEBUG_GROUPS = 1 << 1,
+ DEBUG_CAPS = 1 << 2,
+ DEBUG_CONTACTS = 1 << 3,
+ DEBUG_DISCO = 1 << 4,
+ DEBUG_PROPERTIES = 1 << 5,
+ DEBUG_ROOMLIST = 1 << 6,
+ DEBUG_MEDIA = 1 << 7,
+ DEBUG_MUC = 1 << 8,
+ DEBUG_MUC_CONNECTION = 1 << 9,
+ DEBUG_CONNECTION = 1 << 10,
+ DEBUG_IM = 1 << 11,
+ DEBUG_SI_BYTESTREAM_MGR = 1 << 12,
+ DEBUG_DIRECT_BYTESTREAM_MGR = 1 << 13,
+ DEBUG_NET = 1 << 14,
+ DEBUG_SELF = 1 << 15,
+ DEBUG_TUBES = 1 << 16,
+ DEBUG_XCM = 1 << 17,
+ DEBUG_DISCOVERY = 1 << 18,
+ DEBUG_OLPC_ACTIVITY = 1 << 19,
+ DEBUG_FT = 1 << 20,
+ DEBUG_PLUGIN = 1 << 21,
+} DebugFlags;
+
+void debug_set_flags_from_env (void);
+void debug_set_flags (DebugFlags flags);
+gboolean debug_flag_is_set (DebugFlags flag);
+void debug (DebugFlags flag, const gchar *format, ...)
+ G_GNUC_PRINTF (2, 3);
+void debug_free (void);
+
+#ifdef DEBUG_FLAG
+
+#define DEBUG(format, ...) \
+ debug (DEBUG_FLAG, "%s: " format, G_STRFUNC, ##__VA_ARGS__)
+
+#define DEBUGGING debug_flag_is_set(DEBUG_FLAG)
+
+#endif /* DEBUG_FLAG */
+
+#else /* ENABLE_DEBUG */
+
+#ifdef DEBUG_FLAG
+
+static inline void
+DEBUG (
+ const gchar *format,
+ ...)
+{
+}
+
+#define DEBUGGING 0
+
+#endif /* DEBUG_FLAG */
+
+#define debug_free() G_STMT_START { } G_STMT_END
+
+#endif /* ENABLE_DEBUG */
+
+G_END_DECLS
+
+#endif
diff --git a/salut/src/disco.c b/salut/src/disco.c
new file mode 100644
index 000000000..1c099cf2d
--- /dev/null
+++ b/salut/src/disco.c
@@ -0,0 +1,592 @@
+/*
+ * disco.c - Source for Salut service discovery
+ *
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * -- LET'S DISCO!!! \o/ \o_ _o/ /\o/\ _/o/- -\o\_ --
+ */
+
+#include "config.h"
+#include "disco.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <telepathy-glib/dbus.h>
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-data-form.h>
+#include <wocky/wocky-xep-0115-capabilities.h>
+
+#define DEBUG_FLAG DEBUG_DISCO
+
+#include "debug.h"
+#include "capabilities.h"
+#include "caps-hash.h"
+#include "connection.h"
+
+/* Properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ LAST_PROPERTY
+};
+
+G_DEFINE_TYPE(SalutDisco, salut_disco, G_TYPE_OBJECT);
+
+struct _SalutDiscoPrivate
+{
+ SalutConnection *connection;
+
+ guint caps_req_stanza_id;
+ guint caps_req_stanza_id_broken;
+
+ GList *requests;
+
+ gboolean dispose_has_run;
+};
+
+struct _SalutDiscoRequest
+{
+ SalutDisco *disco;
+
+ SalutDiscoType type;
+ SalutContact *contact;
+
+ GCancellable *cancellable;
+
+ /* uri as in XEP-0115 */
+ gchar *node;
+
+ SalutDiscoCb callback;
+ gpointer user_data;
+ GObject *bound_object;
+};
+
+GQuark
+salut_disco_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("disco-error");
+ return quark;
+}
+
+static void
+salut_disco_init (SalutDisco *obj)
+{
+ SalutDiscoPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (obj, SALUT_TYPE_DISCO, SalutDiscoPrivate);
+ obj->priv = priv;
+}
+
+static void salut_disco_constructed (GObject *obj);
+static void salut_disco_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec);
+static void salut_disco_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec);
+static void salut_disco_dispose (GObject *object);
+
+static const char *
+disco_type_to_xmlns (SalutDiscoType type)
+{
+ switch (type) {
+ case SALUT_DISCO_TYPE_INFO:
+ return WOCKY_NS_DISCO_INFO;
+ case SALUT_DISCO_TYPE_ITEMS:
+ return WOCKY_NS_DISCO_ITEMS;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return NULL;
+}
+
+static void notify_delete_request (gpointer data, GObject *obj);
+
+static void
+delete_request (SalutDiscoRequest *request)
+{
+ SalutDisco *disco = request->disco;
+ SalutDiscoPrivate *priv;
+
+ /* if we've already disposed the SalutDisco object, we should not
+ * mess around with anything referenced by that. */
+ if (disco != NULL)
+ {
+ g_assert (SALUT_IS_DISCO (disco));
+
+ priv = disco->priv;
+
+ g_assert (NULL != g_list_find (priv->requests, request));
+
+ priv->requests = g_list_remove (priv->requests, request);
+ }
+
+ if (NULL != request->bound_object)
+ {
+ g_object_weak_unref (request->bound_object, notify_delete_request,
+ request);
+ }
+
+ g_object_unref (request->contact);
+ g_object_unref (request->cancellable);
+ g_free (request->node);
+ g_slice_free (SalutDiscoRequest, request);
+}
+
+static void
+notify_delete_request (gpointer data, GObject *obj)
+{
+ SalutDiscoRequest *request = (SalutDiscoRequest *) data;
+ request->bound_object = NULL;
+
+ /* This will cause the callback to be called with the cancelled
+ error. */
+ g_cancellable_cancel (request->cancellable);
+}
+
+static void
+salut_disco_class_init (SalutDiscoClass *salut_disco_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_disco_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_disco_class, sizeof (SalutDiscoPrivate));
+
+ object_class->constructed = salut_disco_constructed;
+
+ object_class->get_property = salut_disco_get_property;
+ object_class->set_property = salut_disco_set_property;
+
+ object_class->dispose = salut_disco_dispose;
+
+ param_spec = g_param_spec_object ("connection", "SalutConnection object",
+ "Salut connection object that owns this "
+ "XMPP Discovery object.",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+}
+
+static void
+salut_disco_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutDisco *self = SALUT_DISCO (object);
+ SalutDiscoPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->connection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_disco_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutDisco *self = SALUT_DISCO (object);
+ SalutDiscoPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->connection = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+send_item_not_found (WockyPorter *porter,
+ WockyStanza *iq,
+ const gchar *node)
+{
+ WockyStanza *result;
+
+ /* Return <item-not-found>. It is possible that the remote contact
+ * requested an old version (old hash) of our capabilities. In the
+ * meantime, it will have gotten a new hash, and query the new hash
+ * anyway. */
+ result = wocky_stanza_build_iq_error (iq,
+ '(', "query",
+ ':', WOCKY_NS_DISCO_INFO,
+ '@', "node", node,
+ '(', "error",
+ '@', "type", "cancel",
+ '(', "item-not-found",
+ ':', WOCKY_XMPP_NS_STANZAS,
+ ')',
+ ')',
+ ')',
+ NULL);
+
+ DEBUG ("sending item-not-found as disco response");
+
+ wocky_porter_send_async (porter, result, NULL, NULL, NULL);
+
+ g_object_unref (result);
+}
+
+static void
+add_feature_foreach (gpointer ns,
+ gpointer result_query)
+{
+ WockyNode *feature_node;
+
+ feature_node = wocky_node_add_child (result_query, "feature");
+ wocky_node_set_attribute (feature_node, "var", ns);
+}
+
+static void
+add_data_form_foreach (gpointer data,
+ gpointer user_data)
+{
+ WockyDataForm *form = data;
+ WockyNode *query = user_data;
+
+ wocky_data_form_add_to_node (form, query);
+}
+
+static gboolean
+caps_req_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutDisco *self = SALUT_DISCO (user_data);
+ SalutDiscoPrivate *priv = self->priv;
+ WockyNode *iq, *result_iq, *query, *result_query;
+ const gchar *node;
+ const gchar *suffix;
+ SalutSelf *salut_self;
+ WockyStanza *result;
+ const GabbleCapabilitySet *caps;
+ const GPtrArray *data_forms;
+
+ iq = wocky_stanza_get_top_node (stanza);
+ query = wocky_node_get_child_ns (iq, "query", WOCKY_NS_DISCO_INFO);
+ g_assert (query != NULL);
+
+ node = wocky_node_get_attribute (query, "node");
+ if (node == NULL)
+ {
+ send_item_not_found (porter, stanza, "");
+ return TRUE;
+ }
+
+ if (!g_str_has_prefix (node, WOCKY_TELEPATHY_NS_CAPS "#"))
+ {
+ send_item_not_found (porter, stanza, node);
+ return TRUE;
+ }
+ else
+ {
+ suffix = node + strlen (WOCKY_TELEPATHY_NS_CAPS) + 1;
+ }
+
+ DEBUG ("got disco request for node %s", node);
+
+ g_object_get (priv->connection, "self", &salut_self, NULL);
+ /* Salut only supports XEP-0115 version 1.5. Bundles from old version 1.3 are
+ * not implemented. */
+
+ if (salut_self == NULL)
+ return TRUE;
+
+ if (tp_strdiff (suffix, salut_self->ver))
+ {
+ g_object_unref (salut_self);
+ return TRUE;
+ }
+
+ /* Every entity MUST have at least one identity (XEP-0030). Salut publishs
+ * one identity. If you change the identity here, you also need to change
+ * caps_hash_compute_from_self_presence(). */
+ result = wocky_stanza_build_iq_result (stanza,
+ '(', "query",
+ ':', WOCKY_NS_DISCO_INFO,
+ '@', "node", node,
+ '(', "identity",
+ '@', "category", "client",
+ '@', "name", PACKAGE_STRING,
+ /* FIXME: maybe we should add a connection property allowing to
+ * set the type attribute instead of hardcoding "pc". */
+ '@', "type", "pc",
+ ')',
+ ')',
+ NULL);
+
+ result_iq = wocky_stanza_get_top_node (result);
+ result_query = wocky_node_get_child_ns (result_iq, "query", NULL);
+
+ caps = salut_self_get_caps (salut_self);
+ gabble_capability_set_foreach (caps, add_feature_foreach, result_query);
+
+ data_forms = wocky_xep_0115_capabilities_get_data_forms (
+ WOCKY_XEP_0115_CAPABILITIES (salut_self));
+ g_ptr_array_foreach ((GPtrArray *) data_forms, add_data_form_foreach,
+ result_query);
+
+ DEBUG ("sending disco response");
+
+ wocky_porter_send_async (porter, result, NULL, NULL, NULL);
+
+ g_object_unref (result);
+ g_object_unref (salut_self);
+
+ return TRUE;
+}
+
+static void
+salut_disco_constructed (GObject *obj)
+{
+ SalutDisco *disco = SALUT_DISCO (obj);
+ SalutDiscoPrivate *priv = disco->priv;
+ WockyPorter *porter = priv->connection->porter;
+
+ if (G_OBJECT_CLASS (salut_disco_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (salut_disco_parent_class)->constructed (obj);
+
+ priv->requests = NULL;
+
+ /* receive discovery requests */
+ priv->caps_req_stanza_id = wocky_porter_register_handler_from_anyone (porter,
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ caps_req_stanza_callback, obj,
+ '(', "query",
+ ':', WOCKY_NS_DISCO_INFO,
+ ')', NULL);
+
+ /* Salut used to send disco requests with <iq type='set' ...> so we
+ * should listen for that too. */
+ priv->caps_req_stanza_id_broken =
+ wocky_porter_register_handler_from_anyone (porter,
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ caps_req_stanza_callback, obj,
+ '(', "query",
+ ':', WOCKY_NS_DISCO_INFO,
+ ')', NULL);
+}
+
+static void
+salut_disco_dispose (GObject *object)
+{
+ SalutDisco *self = SALUT_DISCO (object);
+ SalutDiscoPrivate *priv = self->priv;
+ WockyPorter *porter = priv->connection->porter;
+ GList *l;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ DEBUG ("dispose called");
+
+ wocky_porter_unregister_handler (porter, priv->caps_req_stanza_id);
+ priv->caps_req_stanza_id = 0;
+
+ wocky_porter_unregister_handler (porter,
+ priv->caps_req_stanza_id_broken);
+ priv->caps_req_stanza_id_broken = 0;
+
+ for (l = priv->requests; l != NULL; l = l->next)
+ {
+ SalutDiscoRequest *r = l->data;
+
+ r->disco = NULL;
+ g_cancellable_cancel (r->cancellable);
+ }
+
+ g_list_free (priv->requests);
+
+ if (G_OBJECT_CLASS (salut_disco_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_disco_parent_class)->dispose (object);
+}
+
+/**
+ * salut_disco_new:
+ * @conn: The #SalutConnection to use for service discovery
+ *
+ * Creates an object to use for Jabber service discovery (DISCO)
+ * There should be one of these per connection
+ */
+SalutDisco *
+salut_disco_new (SalutConnection *connection)
+{
+ SalutDisco *disco;
+
+ g_return_val_if_fail (SALUT_IS_CONNECTION (connection), NULL);
+
+ disco = SALUT_DISCO (g_object_new (SALUT_TYPE_DISCO,
+ "connection", connection,
+ NULL));
+
+ return disco;
+}
+
+static void
+disco_request_sent_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ GError *error = NULL;
+ WockyStanza *reply;
+ WockyNode *reply_node, *query_node = NULL;
+ SalutDiscoRequest *request = user_data;
+
+ reply = wocky_porter_send_iq_finish (porter, result, &error);
+
+ if (reply == NULL)
+ {
+ DEBUG ("error: %s", error->message);
+ goto out;
+ }
+
+ if (wocky_stanza_extract_errors (reply, NULL, &error, NULL, NULL))
+ goto out;
+
+ reply_node = wocky_stanza_get_top_node (reply);
+ query_node = wocky_node_get_child_ns (reply_node, "query",
+ disco_type_to_xmlns (request->type));
+
+ if (query_node == NULL)
+ {
+ error = g_error_new (SALUT_DISCO_ERROR, SALUT_DISCO_ERROR_UNKNOWN,
+ "disco response contained no <query> node");
+ goto out;
+ }
+
+out:
+ /* the cancellable is cancelled if the object given to
+ * salut_disco_request is disposed, which claims to not call the
+ * callback, so let's not. */
+ if (!g_cancellable_is_cancelled (request->cancellable))
+ {
+ request->callback (request->disco, request, request->contact, request->node,
+ query_node, error, request->user_data);
+ }
+
+ delete_request (request);
+
+ if (error != NULL)
+ g_clear_error (&error);
+}
+
+/**
+ * salut_disco_request:
+ * @self: #SalutDisco object to use for request
+ * @type: type of request
+ * @jid: Jabber ID to request on
+ * @node: node to request on @jid, or NULL
+ * @callback: #SalutDiscoCb to call on request fullfilment
+ * @object: GObject to bind request to. the callback will not be
+ * called if this object has been unrefed. NULL if not needed
+ * @error: #GError to return a telepathy error in if unable to make
+ * request, NULL if unneeded.
+ *
+ * Make a disco request on the given jid.
+ */
+SalutDiscoRequest *
+salut_disco_request (SalutDisco *self,
+ SalutDiscoType type,
+ SalutContact *contact,
+ const char *node,
+ SalutDiscoCb callback,
+ gpointer user_data,
+ GObject *object,
+ GError **error)
+{
+ SalutDiscoPrivate *priv = self->priv;
+ SalutDiscoRequest *request;
+ WockyPorter *porter = priv->connection->porter;
+ WockyStanza *stanza;
+
+ g_assert (node != NULL);
+ g_assert (strlen (node) > 0);
+
+ request = g_slice_new0 (SalutDiscoRequest);
+ request->disco = self;
+ request->type = type;
+ request->contact = g_object_ref (contact);
+ if (node)
+ request->node = g_strdup (node);
+ request->callback = callback;
+ request->user_data = user_data;
+ request->bound_object = object;
+ request->cancellable = g_cancellable_new ();
+
+ if (NULL != object)
+ g_object_weak_ref (object, notify_delete_request, request);
+
+ DEBUG ("Creating disco request %p for %s",
+ request, request->contact->name);
+
+ stanza = wocky_stanza_build_to_contact (WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_GET,
+ NULL, WOCKY_CONTACT (contact),
+ '(', "query",
+ ':', disco_type_to_xmlns (request->type),
+ '@', "node", request->node,
+ ')',
+ NULL);
+
+ wocky_porter_send_iq_async (porter, stanza, request->cancellable,
+ disco_request_sent_cb, request);
+
+ priv->requests = g_list_append (priv->requests,
+ request);
+
+ g_object_unref (stanza);
+
+ return request;
+}
+
+void
+salut_disco_cancel_request (SalutDisco *disco,
+ SalutDiscoRequest *request)
+{
+ SalutDiscoPrivate *priv;
+
+ g_return_if_fail (SALUT_IS_DISCO (disco));
+ g_return_if_fail (NULL != request);
+
+ priv = disco->priv;
+
+ g_return_if_fail (NULL != g_list_find (priv->requests, request));
+
+ g_cancellable_cancel (request->cancellable);
+}
diff --git a/salut/src/disco.h b/salut/src/disco.h
new file mode 100644
index 000000000..7eae97d16
--- /dev/null
+++ b/salut/src/disco.h
@@ -0,0 +1,103 @@
+/*
+ * disco.h - Headers for Salut service discovery
+ *
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * -- LET'S DISCO!!! \o/ \o_ _o/ /\o/\ _/o/- -\o\_ --
+ */
+
+#ifndef __SALUT_DISCO_H__
+#define __SALUT_DISCO_H__
+
+#include <glib-object.h>
+#include <wocky/wocky-stanza.h>
+
+#include "contact.h"
+#include "connection.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ SALUT_DISCO_TYPE_INFO,
+ SALUT_DISCO_TYPE_ITEMS
+} SalutDiscoType;
+
+typedef struct _SalutDiscoClass SalutDiscoClass;
+typedef struct _SalutDiscoPrivate SalutDiscoPrivate;
+typedef struct _SalutDiscoRequest SalutDiscoRequest;
+
+/**
+ * SalutDiscoError:
+ * @SALUT_DISCO_ERROR_CANCELLED: The DISCO request was cancelled
+ * @SALUT_DISCO_ERROR_TIMEOUT: The DISCO request timed out
+ * @SALUT_DISCO_ERROR_UNKNOWN: An unknown error occured
+ */
+typedef enum
+{
+ SALUT_DISCO_ERROR_CANCELLED,
+ SALUT_DISCO_ERROR_TIMEOUT,
+ SALUT_DISCO_ERROR_UNKNOWN
+} SalutDiscoError;
+
+GQuark salut_disco_error_quark (void);
+#define SALUT_DISCO_ERROR salut_disco_error_quark ()
+
+GType salut_disco_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_DISCO \
+ (salut_disco_get_type ())
+#define SALUT_DISCO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_DISCO, SalutDisco))
+#define SALUT_DISCO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_DISCO, SalutDiscoClass))
+#define SALUT_IS_DISCO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_DISCO))
+#define SALUT_IS_DISCO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_DISCO))
+#define SALUT_DISCO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_DISCO, SalutDiscoClass))
+
+struct _SalutDiscoClass {
+ GObjectClass parent_class;
+};
+
+struct _SalutDisco {
+ GObject parent;
+ SalutDiscoPrivate *priv;
+};
+
+typedef void (*SalutDiscoCb)(SalutDisco *self, SalutDiscoRequest *request,
+ SalutContact *contact, const gchar *node, WockyNode *query_result,
+ GError* error, gpointer user_data);
+
+SalutDisco *salut_disco_new (SalutConnection *connection);
+
+SalutDiscoRequest *salut_disco_request (SalutDisco *self,
+ SalutDiscoType type, SalutContact *contact, const char *node,
+ SalutDiscoCb callback, gpointer user_data, GObject *object,
+ GError **error);
+
+void salut_disco_cancel_request (SalutDisco *disco,
+ SalutDiscoRequest *request);
+
+
+G_END_DECLS
+
+#endif
diff --git a/salut/src/discovery-client.c b/salut/src/discovery-client.c
new file mode 100644
index 000000000..8ca3f01a9
--- /dev/null
+++ b/salut/src/discovery-client.c
@@ -0,0 +1,168 @@
+/*
+ * discovery-client.c - Source for SalutDiscoveryClient interface
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "discovery-client.h"
+
+#include <glib.h>
+
+gboolean
+salut_discovery_client_start (SalutDiscoveryClient *self,
+ GError **error)
+{
+ gboolean (*virtual_method)(SalutDiscoveryClient *, GError **) =
+ SALUT_DISCOVERY_CLIENT_GET_CLASS (self)->start;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, error);
+}
+
+SalutMucManager *
+salut_discovery_client_create_muc_manager (SalutDiscoveryClient *self,
+ SalutConnection *connection)
+{
+ SalutMucManager * (*virtual_method)(SalutDiscoveryClient *,
+ SalutConnection *) =
+ SALUT_DISCOVERY_CLIENT_GET_CLASS (self)->create_muc_manager;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, connection);
+}
+
+SalutRoomlistManager *
+salut_discovery_client_create_roomlist_manager (SalutDiscoveryClient *self,
+ SalutConnection *connection)
+{
+ SalutRoomlistManager * (*virtual_method)(SalutDiscoveryClient *,
+ SalutConnection *) =
+ SALUT_DISCOVERY_CLIENT_GET_CLASS (self)->create_roomlist_manager;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, connection);
+}
+
+SalutContactManager *
+salut_discovery_client_create_contact_manager (SalutDiscoveryClient *self,
+ SalutConnection *connection)
+{
+ SalutContactManager * (*virtual_method)(SalutDiscoveryClient *,
+ SalutConnection *) =
+ SALUT_DISCOVERY_CLIENT_GET_CLASS (self)->create_contact_manager;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, connection);
+}
+
+#ifdef ENABLE_OLPC
+SalutOlpcActivityManager *
+salut_discovery_client_create_olpc_activity_manager (SalutDiscoveryClient *self,
+ SalutConnection *connection)
+{
+ SalutOlpcActivityManager * (*virtual_method)(SalutDiscoveryClient *,
+ SalutConnection *) =
+ SALUT_DISCOVERY_CLIENT_GET_CLASS (self)->create_olpc_activity_manager;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, connection);
+}
+#endif
+
+SalutSelf *
+salut_discovery_client_create_self (SalutDiscoveryClient *self,
+ SalutConnection *connection,
+ const gchar *nickname,
+ const gchar *first_name,
+ const gchar *last_name,
+ const gchar *jid,
+ const gchar *email,
+ const gchar *published_name,
+ const GArray *olpc_key,
+ const gchar *olpc_color)
+{
+ SalutSelf * (*virtual_method)(SalutDiscoveryClient *, SalutConnection *,
+ const gchar *, const gchar *, const gchar *, const gchar *,
+ const gchar *, const gchar *, const GArray *, const gchar *) =
+ SALUT_DISCOVERY_CLIENT_GET_CLASS (self)->create_self;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, connection, nickname, first_name, last_name,
+ jid, email, published_name, olpc_key, olpc_color);
+}
+
+const gchar *
+salut_discovery_client_get_host_name_fqdn (
+ SalutDiscoveryClient *self)
+{
+ const gchar * (*virtual_method)( SalutDiscoveryClient *) =
+ SALUT_DISCOVERY_CLIENT_GET_CLASS (self)->get_host_name_fqdn;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self);
+}
+
+
+static void
+salut_discovery_client_base_init (gpointer klass)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ GParamSpec *param_spec;
+
+ param_spec = g_param_spec_uint (
+ "state",
+ "Client state",
+ "An enum (SalutDiscoveryClientState) signifying the current state of"
+ " this client object",
+ 0, NUM_SALUT_DISCOVERY_CLIENT_STATE - 1,
+ SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTED,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ /* Defined here so we can g_object_set this property on the
+ * discovery client without needing to define it everywhere. Now
+ * classes which implement this interface just need to override
+ * the property to use it.. */
+ param_spec = g_param_spec_string (
+ "dnssd-name", "DNS-SD name",
+ "The DNS-SD name of the protocol", "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ initialized = TRUE;
+ }
+}
+
+GType
+salut_discovery_client_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof (SalutDiscoveryClientClass),
+ salut_discovery_client_base_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE, "SalutDiscoveryClient",
+ &info, 0);
+ }
+
+ return type;
+}
diff --git a/salut/src/discovery-client.h b/salut/src/discovery-client.h
new file mode 100644
index 000000000..e741a2755
--- /dev/null
+++ b/salut/src/discovery-client.h
@@ -0,0 +1,112 @@
+/*
+ * discovery-client.h - Header for SalutDiscoveryClient interface
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_DISCOVERY_CLIENT_H__
+#define __SALUT_DISCOVERY_CLIENT_H__
+
+#include <glib-object.h>
+
+#include "muc-manager.h"
+#include "contact-manager.h"
+#include "roomlist-manager.h"
+#include "self.h"
+#ifdef ENABLE_OLPC
+#include "olpc-activity-manager.h"
+#endif
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTED,
+ SALUT_DISCOVERY_CLIENT_STATE_CONNECTING,
+ SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTING,
+ SALUT_DISCOVERY_CLIENT_STATE_CONNECTED,
+ NUM_SALUT_DISCOVERY_CLIENT_STATE
+} SalutDiscoveryClientState;
+
+typedef struct _SalutDiscoveryClient SalutDiscoveryClient;
+typedef struct _SalutDiscoveryClientClass SalutDiscoveryClientClass;
+
+struct _SalutDiscoveryClientClass
+{
+ GTypeInterface parent;
+
+ gboolean (*start) (SalutDiscoveryClient *clt, GError **error);
+ SalutMucManager * (*create_muc_manager) (SalutDiscoveryClient *clt,
+ SalutConnection *connection);
+ SalutRoomlistManager * (*create_roomlist_manager) (SalutDiscoveryClient *clt,
+ SalutConnection *connection);
+ SalutContactManager * (*create_contact_manager) (SalutDiscoveryClient *clt,
+ SalutConnection *connection);
+#ifdef ENABLE_OLPC
+ SalutOlpcActivityManager * (*create_olpc_activity_manager) (
+ SalutDiscoveryClient *clt, SalutConnection *connection);
+#endif
+ SalutSelf * (*create_self) (SalutDiscoveryClient *clt, SalutConnection *conn,
+ const gchar *nickname, const gchar *first_name, const gchar *last_name,
+ const gchar *jid, const gchar *email, const gchar *published_name,
+ const GArray *olpc_key, const gchar *olpc_color);
+
+ const gchar * (*get_host_name_fqdn) (SalutDiscoveryClient *clt);
+};
+
+GType salut_discovery_client_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_DISCOVERY_CLIENT \
+ (salut_discovery_client_get_type ())
+#define SALUT_DISCOVERY_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_DISCOVERY_CLIENT, \
+ SalutDiscoveryClient))
+#define SALUT_IS_DISCOVERY_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_DISCOVERY_CLIENT))
+#define SALUT_DISCOVERY_CLIENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SALUT_TYPE_DISCOVERY_CLIENT,\
+ SalutDiscoveryClientClass))
+
+gboolean salut_discovery_client_start (SalutDiscoveryClient *clt,
+ GError **error);
+
+SalutMucManager * salut_discovery_client_create_muc_manager (
+ SalutDiscoveryClient *clt, SalutConnection *connection);
+
+SalutRoomlistManager * salut_discovery_client_create_roomlist_manager (
+ SalutDiscoveryClient *clt, SalutConnection *connection);
+
+SalutContactManager * salut_discovery_client_create_contact_manager (
+ SalutDiscoveryClient *clt, SalutConnection *connection);
+
+#ifdef ENABLE_OLPC
+SalutOlpcActivityManager * salut_discovery_client_create_olpc_activity_manager (
+ SalutDiscoveryClient *clt, SalutConnection *connection);
+#endif
+
+SalutSelf * salut_discovery_client_create_self (
+ SalutDiscoveryClient *clt, SalutConnection *connection,
+ const gchar *nickname, const gchar *first_name, const gchar *last_name,
+ const gchar *jid, const gchar *email, const gchar *published_name,
+ const GArray *olpc_key, const gchar *olpc_color);
+
+const gchar * salut_discovery_client_get_host_name_fqdn (
+ SalutDiscoveryClient *clt);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_DISCOVERY_CLIENT_H__ */
diff --git a/salut/src/dummy-discovery-client.c b/salut/src/dummy-discovery-client.c
new file mode 100644
index 000000000..268d01c31
--- /dev/null
+++ b/salut/src/dummy-discovery-client.c
@@ -0,0 +1,134 @@
+/*
+ * dummy-discovery-client.c - Source for SalutDummyDiscoveryClient
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "dummy-discovery-client.h"
+
+#define DEBUG_FLAG DEBUG_DISCOVERY
+#include "debug.h"
+
+static void
+discovery_client_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SalutDummyDiscoveryClient,
+ salut_dummy_discovery_client,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_DISCOVERY_CLIENT,
+ discovery_client_init));
+
+/* signals */
+enum
+{
+ STATE_CHANGED,
+ LAST_SIGNAL
+};
+
+/* properties */
+enum
+{
+ PROP_STATE = 1,
+ LAST_PROPERTY
+};
+
+typedef struct _SalutDummyDiscoveryClientPrivate \
+ SalutDummyDiscoveryClientPrivate;
+struct _SalutDummyDiscoveryClientPrivate
+{
+ SalutDiscoveryClientState state;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_DUMMY_DISCOVERY_CLIENT_GET_PRIVATE(obj) \
+ ((SalutDummyDiscoveryClientPrivate *) \
+ ((SalutDummyDiscoveryClient *) obj)->priv)
+
+static void
+salut_dummy_discovery_client_init (SalutDummyDiscoveryClient *self)
+{
+ SalutDummyDiscoveryClientPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_DUMMY_DISCOVERY_CLIENT, SalutDummyDiscoveryClientPrivate);
+
+ self->priv = priv;
+ priv->dispose_has_run = FALSE;
+
+ priv->state = SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTED;
+}
+
+static void
+salut_dummy_discovery_client_dispose (GObject *object)
+{
+ SalutDummyDiscoveryClient *self = SALUT_DUMMY_DISCOVERY_CLIENT (object);
+ SalutDummyDiscoveryClientPrivate *priv =
+ SALUT_DUMMY_DISCOVERY_CLIENT_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ G_OBJECT_CLASS (salut_dummy_discovery_client_parent_class)->dispose (object);
+}
+
+static void
+salut_dummy_discovery_client_class_init (
+ SalutDummyDiscoveryClientClass *salut_dummy_discovery_client_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (
+ salut_dummy_discovery_client_class);
+
+ g_type_class_add_private (salut_dummy_discovery_client_class,
+ sizeof (SalutDummyDiscoveryClientPrivate));
+
+ object_class->dispose = salut_dummy_discovery_client_dispose;
+}
+
+/*
+ * salut_dummy_discovery_client_create_self
+ *
+ * Implements salut_discovery_client_create_self on SalutDiscoveryClient
+ */
+static SalutSelf *
+salut_dummy_discovery_client_create_self (SalutDiscoveryClient *client,
+ SalutConnection *connection,
+ const gchar *nickname,
+ const gchar *first_name,
+ const gchar *last_name,
+ const gchar *jid,
+ const gchar *email,
+ const gchar *published_name,
+ const GArray *olpc_key,
+ const gchar *olpc_color)
+{
+ return NULL;
+}
+
+static void
+discovery_client_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutDiscoveryClientClass *klass = (SalutDiscoveryClientClass *) g_iface;
+
+ klass->start = NULL;
+ klass->create_muc_manager = NULL;
+ klass->create_contact_manager = NULL;
+#ifdef ENABLE_OLPC
+ klass->create_olpc_activity_manager = NULL;
+#endif
+ klass->create_self = salut_dummy_discovery_client_create_self;
+}
diff --git a/salut/src/dummy-discovery-client.h b/salut/src/dummy-discovery-client.h
new file mode 100644
index 000000000..6b3a51f9a
--- /dev/null
+++ b/salut/src/dummy-discovery-client.h
@@ -0,0 +1,63 @@
+/*
+ * dummy-discovery-client.h - Header for SalutDummyDiscoveryClient
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_DUMMY_DISCOVERY_CLIENT_H__
+#define __SALUT_DUMMY_DISCOVERY_CLIENT_H__
+
+#include <glib-object.h>
+
+#include "discovery-client.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutDummyDiscoveryClient SalutDummyDiscoveryClient;
+typedef struct _SalutDummyDiscoveryClientClass SalutDummyDiscoveryClientClass;
+
+struct _SalutDummyDiscoveryClientClass {
+ GObjectClass parent_class;
+};
+
+struct _SalutDummyDiscoveryClient {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType salut_dummy_discovery_client_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_DUMMY_DISCOVERY_CLIENT \
+ (salut_dummy_discovery_client_get_type ())
+#define SALUT_DUMMY_DISCOVERY_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_DUMMY_DISCOVERY_CLIENT,\
+ SalutDummyDiscoveryClient))
+#define SALUT_DUMMY_DISCOVERY_CLIENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_DUMMY_DISCOVERY_CLIENT,\
+ SalutDummyDiscoveryClientClass))
+#define SALUT_IS_DUMMY_DISCOVERY_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_DUMMY_DISCOVERY_CLIENT))
+#define SALUT_IS_DUMMY_DISCOVERY_CLIENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_DUMMY_DISCOVERY_CLIENT))
+#define SALUT_DUMMY_DISCOVERY_CLIENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_DUMMY_DISCOVERY_CLIENT,\
+ SalutDummyDiscoveryClientClass))
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_DUMMY_DISCOVERY_CLIENT_H__ */
diff --git a/salut/src/file-transfer-channel.c b/salut/src/file-transfer-channel.c
new file mode 100644
index 000000000..4dc330045
--- /dev/null
+++ b/salut/src/file-transfer-channel.c
@@ -0,0 +1,1908 @@
+/*
+ * file-transfer-channel.c - Source for SalutFileTransferChannel
+ * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
+ * Copyright (C) 2005, 2007, 2008 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ * @author: Jonny Lamb <jonny.lamb@collabora.co.uk>
+ * @author: Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <glib/gstdio.h>
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define DEBUG_FLAG DEBUG_FT
+#include "debug.h"
+
+#include "file-transfer-channel.h"
+#include "signals-marshal.h"
+
+#include "connection.h"
+#include "im-manager.h"
+#include "contact.h"
+#include "namespaces.h"
+
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-meta-porter.h>
+#include <wocky/wocky-data-form.h>
+#include <wocky/wocky-namespaces.h>
+#include <gibber/gibber-file-transfer.h>
+#include <gibber/gibber-oob-file-transfer.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/gtypes.h>
+
+static void
+channel_iface_init (gpointer g_iface, gpointer iface_data);
+static void
+file_transfer_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SalutFileTransferChannel, salut_file_transfer_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_FILE_TRANSFER,
+ file_transfer_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA,
+ NULL);
+);
+
+#define CHECK_STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
+
+#define SALUT_UNDEFINED_FILE_SIZE G_MAXUINT64
+
+static const char *salut_file_transfer_channel_interfaces[] = { NULL };
+
+/* properties */
+enum
+{
+ PROP_OBJECT_PATH = 1,
+
+ /* org.freedesktop.Telepathy.Channel D-Bus properties */
+ PROP_CHANNEL_TYPE,
+ PROP_INTERFACES,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_HANDLE_TYPE,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+
+ /* org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties */
+ PROP_STATE,
+ PROP_CONTENT_TYPE,
+ PROP_FILENAME,
+ PROP_SIZE,
+ PROP_CONTENT_HASH_TYPE,
+ PROP_CONTENT_HASH,
+ PROP_DESCRIPTION,
+ PROP_DATE,
+ PROP_AVAILABLE_SOCKET_TYPES,
+ PROP_TRANSFERRED_BYTES,
+ PROP_INITIAL_OFFSET,
+ PROP_URI,
+
+ /* Chan.I.FileTransfer.Metadata */
+ PROP_SERVICE_NAME,
+ PROP_METADATA,
+
+ PROP_CONTACT,
+ PROP_CONNECTION,
+ LAST_PROPERTY
+};
+
+/* private structure */
+struct _SalutFileTransferChannelPrivate {
+ gboolean dispose_has_run;
+ gboolean closed;
+ gchar *object_path;
+ TpHandle handle;
+ SalutContact *contact;
+ SalutConnection *connection;
+ GibberFileTransfer *ft;
+ GTimeVal last_transferred_bytes_emitted;
+ guint progress_timer;
+ GValue *socket_address;
+ TpHandle initiator;
+ gboolean remote_accepted;
+ gchar *path;
+
+ /* properties */
+ TpFileTransferState state;
+ gchar *content_type;
+ gchar *filename;
+ guint64 size;
+ TpFileHashType content_hash_type;
+ gchar *content_hash;
+ gchar *description;
+ GHashTable *available_socket_types;
+ guint64 transferred_bytes;
+ guint64 initial_offset;
+ guint64 date;
+ gchar *uri;
+ gchar *service_name;
+ GHashTable *metadata;
+};
+
+static void
+salut_file_transfer_channel_do_close (SalutFileTransferChannel *self)
+{
+ if (self->priv->closed)
+ return;
+
+ DEBUG ("Emitting closed signal for %s", self->priv->object_path);
+ tp_svc_channel_emit_closed (self);
+ self->priv->closed = TRUE;
+}
+
+static void
+salut_file_transfer_channel_init (SalutFileTransferChannel *obj)
+{
+ obj->priv = G_TYPE_INSTANCE_GET_PRIVATE (obj,
+ SALUT_TYPE_FILE_TRANSFER_CHANNEL, SalutFileTransferChannelPrivate);
+
+ /* allocate any data required by the object here */
+ obj->priv->object_path = NULL;
+ obj->priv->contact = NULL;
+}
+
+static void salut_file_transfer_channel_set_state (
+ TpSvcChannelTypeFileTransfer *iface, TpFileTransferState state,
+ TpFileTransferStateChangeReason reason);
+
+static void
+contact_lost_cb (SalutContact *contact,
+ SalutFileTransferChannel *self)
+{
+ g_assert (contact == self->priv->contact);
+
+ if (self->priv->state != TP_FILE_TRANSFER_STATE_PENDING)
+ {
+ DEBUG ("%s was disconnected. Ignoring as there is still a chance to"
+ " be able to complete the transfer", contact->name);
+ return;
+ }
+
+ DEBUG ("%s was disconnected. Cancel file tranfer.", contact->name);
+ salut_file_transfer_channel_set_state (
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (self),
+ TP_FILE_TRANSFER_STATE_CANCELLED,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED);
+}
+
+static void
+salut_file_transfer_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (object);
+ TpBaseConnection *base_conn = (TpBaseConnection *) self->priv->connection;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value, tp_handle_inspect (repo,
+ self->priv->handle));
+ }
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, (self->priv->initiator ==
+ base_conn->self_handle));
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, self->priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_assert (self->priv->initiator != 0);
+ g_value_set_string (value,
+ tp_handle_inspect (repo, self->priv->initiator));
+ }
+ break;
+ case PROP_CONTACT:
+ g_value_set_object (value, self->priv->contact);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->connection);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, salut_file_transfer_channel_interfaces);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, self->priv->state);
+ break;
+ case PROP_CONTENT_TYPE:
+ g_value_set_string (value, self->priv->content_type);
+ break;
+ case PROP_FILENAME:
+ g_value_set_string (value, self->priv->filename);
+ break;
+ case PROP_SIZE:
+ g_value_set_uint64 (value, self->priv->size);
+ break;
+ case PROP_CONTENT_HASH_TYPE:
+ g_value_set_uint (value, self->priv->content_hash_type);
+ break;
+ case PROP_CONTENT_HASH:
+ g_value_set_string (value, self->priv->content_hash);
+ break;
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, self->priv->description);
+ break;
+ case PROP_AVAILABLE_SOCKET_TYPES:
+ g_value_set_boxed (value, self->priv->available_socket_types);
+ break;
+ case PROP_TRANSFERRED_BYTES:
+ g_value_set_uint64 (value, self->priv->transferred_bytes);
+ break;
+ case PROP_INITIAL_OFFSET:
+ g_value_set_uint64 (value, self->priv->initial_offset);
+ break;
+ case PROP_DATE:
+ g_value_set_uint64 (value, self->priv->date);
+ break;
+ case PROP_URI:
+ g_value_set_string (value,
+ self->priv->uri != NULL ? self->priv->uri : "");
+ break;
+ case PROP_SERVICE_NAME:
+ g_value_set_string (value,
+ self->priv->service_name != NULL ? self->priv->service_name : "");
+ break;
+ case PROP_METADATA:
+ {
+ /* We're fine with priv->metadata being NULL but dbus-glib
+ * doesn't like iterating NULL as if it was a hash table. */
+ if (self->priv->metadata == NULL)
+ {
+ g_value_take_boxed (value,
+ g_hash_table_new (g_str_hash, g_str_equal));
+ }
+ else
+ {
+ g_value_set_boxed (value, self->priv->metadata);
+ }
+ }
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, self->priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ {
+ GHashTable *props;
+
+ props = tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "State",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "ContentType",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "Filename",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "Size",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "ContentHashType",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "ContentHash",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "Description",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "Date",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "AvailableSocketTypes",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "TransferredBytes",
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "InitialOffset",
+ TP_IFACE_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA, "ServiceName",
+ TP_IFACE_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA, "Metadata",
+ NULL);
+
+ /* URI is immutable only for outgoing transfers */
+ if (self->priv->initiator == base_conn->self_handle)
+ {
+ tp_dbus_properties_mixin_fill_properties_hash (object, props,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "URI", NULL);
+ }
+
+ g_value_take_boxed (value, props);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_file_transfer_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (self->priv->object_path);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ self->priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_CONTACT:
+ self->priv->contact = g_value_dup_object (value);
+ g_signal_connect (self->priv->contact, "lost",
+ G_CALLBACK (contact_lost_cb), self);
+ break;
+ case PROP_CONNECTION:
+ self->priv->connection = g_value_get_object (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ /* 0 is the old tp-glib value of unset, TP_UNKNOWN_HANDLE_TYPE is the
+ * new version */
+ g_assert (g_value_get_uint (value) == 0
+ || g_value_get_uint (value) == TP_HANDLE_TYPE_CONTACT
+ || g_value_get_uint (value) == TP_UNKNOWN_HANDLE_TYPE);
+ break;
+ case PROP_CHANNEL_TYPE:
+ /* these properties are writable in the interface, but not actually
+ * meaningfully changeable on this channel, so we do nothing */
+ break;
+ case PROP_STATE:
+ salut_file_transfer_channel_set_state (
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (object),
+ g_value_get_uint (value),
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE);
+ break;
+ case PROP_CONTENT_TYPE:
+ g_free (self->priv->content_type);
+ self->priv->content_type = g_value_dup_string (value);
+ break;
+ case PROP_FILENAME:
+ g_free (self->priv->filename);
+ self->priv->filename = g_value_dup_string (value);
+ break;
+ case PROP_SIZE:
+ self->priv->size = g_value_get_uint64 (value);
+ break;
+ case PROP_CONTENT_HASH_TYPE:
+ self->priv->content_hash_type = g_value_get_uint (value);
+ break;
+ case PROP_CONTENT_HASH:
+ g_free (self->priv->content_hash);
+ self->priv->content_hash = g_value_dup_string (value);
+ break;
+ case PROP_DESCRIPTION:
+ g_free (self->priv->description);
+ self->priv->description = g_value_dup_string (value);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ self->priv->initiator = g_value_get_uint (value);
+ g_assert (self->priv->initiator != 0);
+ break;
+ case PROP_DATE:
+ self->priv->date = g_value_get_uint64 (value);
+ break;
+ case PROP_INITIAL_OFFSET:
+ self->priv->initial_offset = g_value_get_uint64 (value);
+ break;
+ case PROP_URI:
+ g_assert (self->priv->uri == NULL); /* construct only */
+ self->priv->uri = g_value_dup_string (value);
+ break;
+ case PROP_SERVICE_NAME:
+ g_assert (self->priv->service_name == NULL); /* construct only */
+ self->priv->service_name = g_value_dup_string (value);
+ break;
+ case PROP_METADATA:
+ self->priv->metadata = g_value_dup_boxed (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+free_array (GArray *array)
+{
+ g_array_unref (array);
+}
+
+static GObject *
+salut_file_transfer_channel_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutFileTransferChannel *self;
+ TpDBusDaemon *bus;
+ TpBaseConnection *base_conn;
+ TpHandleRepoIface *contact_repo;
+ GArray *unix_access;
+ TpSocketAccessControl access_control;
+
+ /* Parent constructor chain */
+ obj = G_OBJECT_CLASS (salut_file_transfer_channel_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_FILE_TRANSFER_CHANNEL (obj);
+
+ /* Ref our handle */
+ base_conn = TP_BASE_CONNECTION (self->priv->connection);
+
+ /* ref our porter */
+ wocky_meta_porter_hold (WOCKY_META_PORTER (self->priv->connection->porter),
+ WOCKY_CONTACT (self->priv->contact));
+
+ contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+
+ self->priv->object_path = g_strdup_printf ("%s/FileTransferChannel/%p",
+ base_conn->object_path, self);
+
+ /* Connect to the bus */
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, self->priv->object_path, obj);
+
+ /* Initialise the available socket types hash table */
+ self->priv->available_socket_types = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, (GDestroyNotify) free_array);
+
+ /* Socket_Address_Type_Unix */
+ unix_access = g_array_sized_new (FALSE, FALSE, sizeof (TpSocketAccessControl),
+ 1);
+ access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val (unix_access, access_control);
+ g_hash_table_insert (self->priv->available_socket_types,
+ GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_UNIX), unix_access);
+
+ DEBUG ("New FT channel created: %s (contact: %s, initiator: %s, "
+ "file: \"%s\", size: %" G_GUINT64_FORMAT ")",
+ self->priv->object_path,
+ tp_handle_inspect (contact_repo, self->priv->handle),
+ tp_handle_inspect (contact_repo, self->priv->initiator),
+ self->priv->filename, self->priv->size);
+
+ if (self->priv->initiator != base_conn->self_handle)
+ /* Incoming transfer, URI has to be set by the handler */
+ g_assert (self->priv->uri == NULL);
+
+ return obj;
+}
+
+static void
+salut_file_transfer_channel_dispose (GObject *object);
+static void
+salut_file_transfer_channel_finalize (GObject *object);
+
+static gboolean
+file_transfer_channel_properties_setter (GObject *object,
+ GQuark interface,
+ GQuark name,
+ const GValue *value,
+ gpointer setter_data,
+ GError **error)
+{
+ SalutFileTransferChannel *self = (SalutFileTransferChannel *) object;
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->connection);
+
+ g_return_val_if_fail (interface == TP_IFACE_QUARK_CHANNEL_TYPE_FILE_TRANSFER,
+ FALSE);
+
+ /* There is only one property with write access. So TpDBusPropertiesMixin
+ * already checked this. */
+ g_assert (name == g_quark_from_static_string ("URI"));
+
+ /* TpDBusPropertiesMixin already checked this */
+ g_assert (G_VALUE_HOLDS_STRING (value));
+
+ if (self->priv->uri != NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "URI has already be set");
+ return FALSE;
+ }
+
+ if (self->priv->initiator == base_conn->self_handle)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Channel is not an incoming transfer");
+ return FALSE;
+ }
+
+ if (self->priv->state != TP_FILE_TRANSFER_STATE_PENDING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "State is not pending; cannot set URI");
+ return FALSE;
+ }
+
+ self->priv->uri = g_value_dup_string (value);
+
+ tp_svc_channel_type_file_transfer_emit_uri_defined (self, self->priv->uri);
+
+ return TRUE;
+}
+
+static void
+salut_file_transfer_channel_class_init (
+ SalutFileTransferChannelClass *salut_file_transfer_channel_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (
+ salut_file_transfer_channel_class);
+ GParamSpec *param_spec;
+
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "TargetID", "target-id", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+
+ static TpDBusPropertiesMixinPropImpl file_props[] = {
+ { "State", "state", NULL },
+ { "ContentType", "content-type", "content-type" },
+ { "Filename", "filename", "filename" },
+ { "Size", "size", "size" },
+ { "ContentHashType", "content-hash-type", "content-hash-type" },
+ { "ContentHash", "content-hash", "content-hash" },
+ { "Description", "description", "description" },
+ { "AvailableSocketTypes", "available-socket-types", NULL },
+ { "TransferredBytes", "transferred-bytes", NULL },
+ { "InitialOffset", "initial-offset", NULL },
+ { "Date", "date", "date" },
+ { "URI", "uri", NULL },
+ { NULL }
+ };
+
+ static TpDBusPropertiesMixinPropImpl file_metadata_props[] = {
+ { "ServiceName", "service-name", NULL },
+ { "Metadata", "metadata", NULL },
+ { NULL }
+ };
+
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props
+ },
+ { TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ file_transfer_channel_properties_setter,
+ file_props
+ },
+ { TP_IFACE_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ file_metadata_props
+ }, { NULL }
+ };
+
+ g_type_class_add_private (salut_file_transfer_channel_class,
+ sizeof (SalutFileTransferChannelPrivate));
+
+ object_class->dispose = salut_file_transfer_channel_dispose;
+ object_class->finalize = salut_file_transfer_channel_finalize;
+
+ object_class->constructor = salut_file_transfer_channel_constructor;
+ object_class->get_property = salut_file_transfer_channel_get_property;
+ object_class->set_property = salut_file_transfer_channel_set_property;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_string ("target-id", "Target JID",
+ "The string obtained by inspecting this channel's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NAME);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NAME);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's bare JID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NAME);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_object ("contact",
+ "SalutContact object",
+ "Salut Contact to which this channel is dedicated",
+ SALUT_TYPE_CONTACT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
+
+ param_spec = g_param_spec_object ("connection",
+ "SalutConnection object",
+ "Salut Connection that owns the"
+ "connection for this IM channel",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NAME);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "state",
+ "TpFileTransferState state",
+ "State of the file transfer in this channel",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ param_spec = g_param_spec_string (
+ "content-type",
+ "gchar *content-type",
+ "ContentType of the file",
+ "application/octet-stream",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTENT_TYPE,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "filename",
+ "gchar *filename",
+ "Name of the file",
+ "",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_FILENAME, param_spec);
+
+ param_spec = g_param_spec_uint64 (
+ "size",
+ "guint size",
+ "Size of the file in bytes",
+ 0,
+ G_MAXUINT64,
+ SALUT_UNDEFINED_FILE_SIZE,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_SIZE, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "content-hash-type",
+ "SalutFileHashType content-hash-type",
+ "Hash type",
+ 0,
+ G_MAXUINT,
+ TP_FILE_HASH_TYPE_NONE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTENT_HASH_TYPE,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "content-hash",
+ "gchar *content-hash",
+ "Hash of the file contents",
+ "",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTENT_HASH,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "description",
+ "gchar *description",
+ "Description of the file",
+ "",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_DESCRIPTION, param_spec);
+
+ param_spec = g_param_spec_boxed (
+ "available-socket-types",
+ "SalutSupportedSocketMap available-socket-types",
+ "Available socket types",
+ dbus_g_type_get_map ("GHashTable", G_TYPE_UINT, DBUS_TYPE_G_UINT_ARRAY),
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_AVAILABLE_SOCKET_TYPES,
+ param_spec);
+
+ param_spec = g_param_spec_uint64 (
+ "transferred-bytes",
+ "guint64 transferred-bytes",
+ "Bytes transferred",
+ 0,
+ G_MAXUINT64,
+ 0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_TRANSFERRED_BYTES,
+ param_spec);
+
+ param_spec = g_param_spec_uint64 (
+ "initial-offset",
+ "guint64 initial_offset",
+ "Offset set at the beginning of the transfer",
+ 0,
+ G_MAXUINT64,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_INITIAL_OFFSET,
+ param_spec);
+
+ param_spec = g_param_spec_uint64 (
+ "date",
+ "Epoch time",
+ "the last modification time of the file being transferred",
+ 0,
+ G_MAXUINT64,
+ 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_DATE,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "uri", "URI",
+ "URI of the file being transferred",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_URI,
+ param_spec);
+
+ param_spec = g_param_spec_string ("service-name",
+ "ServiceName",
+ "The Metadata.ServiceName property of this channel",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SERVICE_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("metadata",
+ "Metadata",
+ "The Metadata.Metadata property of this channel",
+ TP_HASH_TYPE_METADATA,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_METADATA,
+ param_spec);
+
+ salut_file_transfer_channel_class->dbus_props_class.interfaces = \
+ prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutFileTransferChannelClass, dbus_props_class));
+}
+
+void
+salut_file_transfer_channel_dispose (GObject *object)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (object);
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->connection);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (self->priv->dispose_has_run)
+ return;
+
+ self->priv->dispose_has_run = TRUE;
+
+ tp_handle_unref (handle_repo, self->priv->handle);
+
+ salut_file_transfer_channel_do_close (self);
+
+ if (self->priv->progress_timer != 0)
+ {
+ g_source_remove (self->priv->progress_timer);
+ self->priv->progress_timer = 0;
+ }
+
+ if (self->priv->contact)
+ {
+ g_signal_handlers_disconnect_by_func (self->priv->contact,
+ contact_lost_cb, self);
+ g_object_unref (self->priv->contact);
+ self->priv->contact = NULL;
+ }
+
+ if (self->priv->ft != NULL)
+ {
+ g_object_unref (self->priv->ft);
+ self->priv->ft = NULL;
+ }
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (salut_file_transfer_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_file_transfer_channel_parent_class)->dispose (object);
+}
+
+static void
+salut_file_transfer_channel_finalize (GObject *object)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (object);
+
+ /* free any data held directly by the object here */
+ g_free (self->priv->object_path);
+ g_free (self->priv->filename);
+ if (self->priv->socket_address != NULL)
+ tp_g_value_slice_free (self->priv->socket_address);
+ g_free (self->priv->content_type);
+ g_free (self->priv->content_hash);
+ g_free (self->priv->description);
+ g_hash_table_unref (self->priv->available_socket_types);
+ g_free (self->priv->uri);
+ g_free (self->priv->service_name);
+ if (self->priv->metadata != NULL)
+ g_hash_table_unref (self->priv->metadata);
+
+ if (self->priv->path != NULL)
+ {
+ g_unlink (self->priv->path);
+ g_free (self->priv->path);
+ }
+
+ G_OBJECT_CLASS (salut_file_transfer_channel_parent_class)->finalize (object);
+}
+
+
+/**
+ * salut_file_transfer_channel_close
+ *
+ * Implements DBus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_file_transfer_channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (iface);
+
+ if (self->priv->state != TP_FILE_TRANSFER_STATE_COMPLETED &&
+ self->priv->state != TP_FILE_TRANSFER_STATE_CANCELLED)
+ {
+ gibber_file_transfer_cancel (self->priv->ft, 406);
+ salut_file_transfer_channel_set_state (
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (iface),
+ TP_FILE_TRANSFER_STATE_CANCELLED,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED);
+ }
+
+ salut_file_transfer_channel_do_close (SALUT_FILE_TRANSFER_CHANNEL (iface));
+ tp_svc_channel_return_from_close (context);
+}
+
+/**
+ * salut_file_transfer_channel_get_channel_type
+ *
+ * Implements DBus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_file_transfer_channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+}
+
+/**
+ * salut_file_transfer_channel_get_handle
+ *
+ * Implements DBus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_file_transfer_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (iface);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ self->priv->handle);
+}
+
+/**
+ * salut_file_transfer_channel_get_interfaces
+ *
+ * Implements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_file_transfer_channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_file_transfer_channel_interfaces);
+}
+
+static void
+channel_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
+ klass, salut_file_transfer_channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+error_cb (GibberFileTransfer *ft,
+ guint domain,
+ gint code,
+ const gchar *message,
+ SalutFileTransferChannel *self)
+{
+ TpBaseConnection *base_conn = (TpBaseConnection *) self->priv->connection;
+ gboolean receiver;
+
+ receiver = (self->priv->initiator != base_conn->self_handle);
+
+ if (domain == GIBBER_FILE_TRANSFER_ERROR && code ==
+ GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND && receiver)
+ {
+ /* Inform the sender we weren't able to retrieve the file */
+ gibber_file_transfer_cancel (self->priv->ft, 404);
+ }
+
+ salut_file_transfer_channel_set_state (
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (self),
+ TP_FILE_TRANSFER_STATE_CANCELLED,
+ receiver ?
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR :
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR);
+}
+
+static void
+ft_finished_cb (GibberFileTransfer *ft,
+ SalutFileTransferChannel *self)
+{
+ SalutFileTransferChannelPrivate *priv = self->priv;
+ WockyPorter *porter = priv->connection->porter;
+
+ salut_file_transfer_channel_set_state (
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (self),
+ TP_FILE_TRANSFER_STATE_COMPLETED,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE);
+
+ wocky_meta_porter_unhold (WOCKY_META_PORTER (porter),
+ WOCKY_CONTACT (self->priv->contact));
+}
+
+static void
+ft_remote_cancelled_cb (GibberFileTransfer *ft,
+ SalutFileTransferChannel *self)
+{
+ SalutFileTransferChannelPrivate *priv = self->priv;
+ WockyPorter *porter = priv->connection->porter;
+
+ gibber_file_transfer_cancel (ft, 406);
+ salut_file_transfer_channel_set_state (
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (self),
+ TP_FILE_TRANSFER_STATE_CANCELLED,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED);
+
+ wocky_meta_porter_unhold (WOCKY_META_PORTER (porter),
+ WOCKY_CONTACT (self->priv->contact));
+}
+
+static void
+remote_accepted_cb (GibberFileTransfer *ft,
+ SalutFileTransferChannel *self)
+{
+ self->priv->remote_accepted = TRUE;
+
+ if (self->priv->socket_address != NULL)
+ {
+ /* ProvideFile has already been called. Channel is Open */
+ tp_svc_channel_type_file_transfer_emit_initial_offset_defined (self,
+ self->priv->initial_offset);
+
+ salut_file_transfer_channel_set_state (
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (self),
+ TP_FILE_TRANSFER_STATE_OPEN,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE);
+ }
+ else
+ {
+ /* Client has to call ProvideFile to open the channel */
+ salut_file_transfer_channel_set_state (
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (self),
+ TP_FILE_TRANSFER_STATE_ACCEPTED,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE);
+ }
+
+ g_signal_connect (ft, "finished", G_CALLBACK (ft_finished_cb), self);
+}
+
+static gboolean setup_local_socket (SalutFileTransferChannel *self);
+static void ft_transferred_chunk_cb (GibberFileTransfer *ft, guint64 count,
+ SalutFileTransferChannel *self);
+
+static GList *
+add_metadata_forms (SalutFileTransferChannel *self,
+ GibberFileTransfer *ft)
+{
+ GError *error = NULL;
+ GQueue queue = G_QUEUE_INIT;
+
+ if (!tp_str_empty (self->priv->service_name))
+ {
+ WockyStanza *tmp = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_RESULT, NULL, NULL,
+ '(', "x",
+ ':', WOCKY_XMPP_NS_DATA,
+ '@', "type", "result",
+ '(', "field",
+ '@', "var", "FORM_TYPE",
+ '@', "type", "hidden",
+ '(', "value",
+ '$', NS_TP_FT_METADATA_SERVICE,
+ ')',
+ ')',
+ '(', "field",
+ '@', "var", "ServiceName",
+ '(', "value",
+ '$', self->priv->service_name,
+ ')',
+ ')',
+ ')',
+ NULL);
+ WockyNode *x = wocky_node_get_first_child (wocky_stanza_get_top_node (tmp));
+ WockyDataForm *form = wocky_data_form_new_from_node (x, &error);
+
+ if (form == NULL)
+ {
+ DEBUG ("Failed to parse form (wat): %s", error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_queue_push_tail (&queue, form);
+ }
+
+ g_object_unref (tmp);
+ }
+
+ if (self->priv->metadata != NULL
+ && g_hash_table_size (self->priv->metadata) > 0)
+ {
+ WockyStanza *tmp = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_RESULT, NULL, NULL,
+ '(', "x",
+ ':', WOCKY_XMPP_NS_DATA,
+ '@', "type", "result",
+ '(', "field",
+ '@', "var", "FORM_TYPE",
+ '@', "type", "hidden",
+ '(', "value",
+ '$', NS_TP_FT_METADATA,
+ ')',
+ ')',
+ ')',
+ NULL);
+ WockyNode *x = wocky_node_get_first_child (wocky_stanza_get_top_node (tmp));
+ WockyDataForm *form;
+ GHashTableIter iter;
+ gpointer key, val;
+
+ g_hash_table_iter_init (&iter, self->priv->metadata);
+ while (g_hash_table_iter_next (&iter, &key, &val))
+ {
+ const gchar * const *values = val;
+
+ WockyNode *field = wocky_node_add_child (x, "field");
+ wocky_node_set_attribute (field, "var", key);
+
+ for (; values != NULL && *values != NULL; values++)
+ {
+ wocky_node_add_child_with_content (field, "value", *values);
+ }
+ }
+
+ form = wocky_data_form_new_from_node (x, &error);
+
+ if (form == NULL)
+ {
+ DEBUG ("Failed to parse form (wat): %s", error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_queue_push_tail (&queue, form);
+ }
+
+ g_object_unref (tmp);
+ }
+
+ return queue.head;
+}
+
+static void
+send_file_offer (SalutFileTransferChannel *self)
+{
+ GibberFileTransfer *ft;
+
+ ft = g_object_new (GIBBER_TYPE_OOB_FILE_TRANSFER,
+ "self-id", self->priv->connection->name,
+ "peer-id", self->priv->contact->name,
+ "filename", self->priv->filename,
+ "porter", self->priv->connection->porter,
+ "contact", self->priv->contact,
+ "description", self->priv->description,
+ "content-type", self->priv->content_type,
+ NULL);
+
+ g_signal_connect (ft, "remote-accepted",
+ G_CALLBACK (remote_accepted_cb), self);
+ g_signal_connect (ft, "error", G_CALLBACK (error_cb), self);
+ g_signal_connect (ft, "cancelled", G_CALLBACK (ft_remote_cancelled_cb), self);
+
+ self->priv->ft = ft;
+
+ g_signal_connect (ft, "transferred-chunk",
+ G_CALLBACK (ft_transferred_chunk_cb), self);
+
+ gibber_file_transfer_set_size (ft, self->priv->size);
+
+ g_assert (ft->dataforms == NULL);
+ ft->dataforms = add_metadata_forms (self, ft);
+
+ gibber_file_transfer_offer (ft);
+}
+
+static void
+salut_file_transfer_channel_set_state (
+ TpSvcChannelTypeFileTransfer *iface,
+ TpFileTransferState state,
+ TpFileTransferStateChangeReason reason)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (iface);
+
+ if (self->priv->state == state)
+ return;
+
+ self->priv->state = state;
+ tp_svc_channel_type_file_transfer_emit_file_transfer_state_changed (iface,
+ state, reason);
+}
+
+static void
+emit_progress_update (SalutFileTransferChannel *self)
+{
+ TpSvcChannelTypeFileTransfer *iface = \
+ TP_SVC_CHANNEL_TYPE_FILE_TRANSFER (self);
+
+ g_get_current_time (&self->priv->last_transferred_bytes_emitted);
+
+ tp_svc_channel_type_file_transfer_emit_transferred_bytes_changed (
+ iface, self->priv->transferred_bytes);
+
+ if (self->priv->progress_timer != 0)
+ {
+ g_source_remove (self->priv->progress_timer);
+ self->priv->progress_timer = 0;
+ }
+}
+
+static gboolean
+emit_progress_update_cb (gpointer user_data)
+{
+ SalutFileTransferChannel *self = \
+ SALUT_FILE_TRANSFER_CHANNEL (user_data);
+
+ emit_progress_update (self);
+
+ return FALSE;
+}
+
+static void
+ft_transferred_chunk_cb (GibberFileTransfer *ft,
+ guint64 count,
+ SalutFileTransferChannel *self)
+{
+ GTimeVal timeval;
+ gint interval;
+
+ self->priv->transferred_bytes += count;
+
+ if (self->priv->transferred_bytes >= self->priv->size)
+ {
+ /* If the transfer has finished send an update right away */
+ emit_progress_update (self);
+ return;
+ }
+
+ if (self->priv->progress_timer != 0)
+ {
+ /* A progress update signal is already scheduled */
+ return;
+ }
+
+ /* Only emit the TransferredBytes signal if it has been one second since its
+ * last emission.
+ */
+ g_get_current_time (&timeval);
+ interval = timeval.tv_sec -
+ self->priv->last_transferred_bytes_emitted.tv_sec;
+
+ if (interval > 1)
+ {
+ /* At least more then a second apart, emit right away */
+ emit_progress_update (self);
+ return;
+ }
+
+ /* Convert interval to milliseconds and calculate it more precisely */
+ interval *= 1000;
+
+ interval += (timeval.tv_usec -
+ self->priv->last_transferred_bytes_emitted.tv_usec)/1000;
+
+ /* Protect against clock skew, if the interval is negative the worst thing
+ * that can happen is that we wait an extra second before emitting the signal
+ */
+ interval = ABS(interval);
+
+ if (interval > 1000)
+ emit_progress_update (self);
+ else
+ self->priv->progress_timer = g_timeout_add (1000 - interval,
+ emit_progress_update_cb, self);
+}
+
+static gboolean
+check_address_and_access_control (SalutFileTransferChannel *self,
+ TpSocketAddressType address_type,
+ TpSocketAccessControl access_control,
+ const GValue *access_control_param,
+ GError **error)
+{
+ GArray *access_arr;
+ guint i;
+
+ /* Do we support this AddressType? */
+ access_arr = g_hash_table_lookup (self->priv->available_socket_types,
+ GUINT_TO_POINTER (address_type));
+ if (access_arr == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "AddressType %u is not implemented", address_type);
+ return FALSE;
+ }
+
+ /* Do we support this AccessControl? */
+ for (i = 0; i < access_arr->len; i++)
+ {
+ TpSocketAccessControl control;
+
+ control = g_array_index (access_arr, TpSocketAccessControl, i);
+ if (control == access_control)
+ return TRUE;
+ }
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "AccesControl %u is not implemented with AddressType %u",
+ access_control, address_type);
+
+ return FALSE;
+}
+
+gboolean
+salut_file_transfer_channel_offer_file (SalutFileTransferChannel *self,
+ GError **error)
+{
+ g_assert (!CHECK_STR_EMPTY (self->priv->filename));
+ g_assert (self->priv->size != SALUT_UNDEFINED_FILE_SIZE);
+
+ DEBUG ("Offering file transfer");
+
+ send_file_offer (self);
+
+ return TRUE;
+}
+
+/**
+ * salut_file_transfer_channel_accept_file
+ *
+ * Implements D-Bus method AcceptFile
+ * on interface org.freedesktop.Telepathy.Channel.Type.FileTransfer
+ */
+static void
+salut_file_transfer_channel_accept_file (TpSvcChannelTypeFileTransfer *iface,
+ guint address_type,
+ guint access_control,
+ const GValue *access_control_param,
+ guint64 offset,
+ DBusGMethodInvocation *context)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (iface);
+ GError *error = NULL;
+ GibberFileTransfer *ft;
+
+ ft = self->priv->ft;
+ if (ft == NULL)
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+
+ if (self->priv->state != TP_FILE_TRANSFER_STATE_PENDING)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "State is not pending; cannot accept file");
+ dbus_g_method_return_error (context, error);
+ return;
+ }
+
+ if (!check_address_and_access_control (self, address_type, access_control,
+ access_control_param, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ g_signal_connect (ft, "finished", G_CALLBACK (ft_finished_cb), self);
+ g_signal_connect (ft, "transferred-chunk",
+ G_CALLBACK (ft_transferred_chunk_cb), self);
+ g_signal_connect (ft, "cancelled", G_CALLBACK (ft_remote_cancelled_cb), self);
+
+ if (!setup_local_socket (self))
+ {
+ DEBUG ("Could not set up local socket");
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Could not set up local socket");
+ dbus_g_method_return_error (context, error);
+ }
+
+ salut_file_transfer_channel_set_state (iface,
+ TP_FILE_TRANSFER_STATE_ACCEPTED,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_REQUESTED);
+
+ tp_svc_channel_type_file_transfer_return_from_accept_file (context,
+ self->priv->socket_address);
+
+ self->priv->initial_offset = 0;
+
+ tp_svc_channel_type_file_transfer_emit_initial_offset_defined (self,
+ self->priv->initial_offset);
+
+ salut_file_transfer_channel_set_state (iface, TP_FILE_TRANSFER_STATE_OPEN,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE);
+}
+
+/**
+ * salut_file_transfer_channel_provide_file
+ *
+ * Implements D-Bus method ProvideFile
+ * on interface org.freedesktop.Telepathy.Channel.Type.FileTransfer
+ */
+static void
+salut_file_transfer_channel_provide_file (
+ TpSvcChannelTypeFileTransfer *iface,
+ guint address_type,
+ guint access_control,
+ const GValue *access_control_param,
+ DBusGMethodInvocation *context)
+{
+ SalutFileTransferChannel *self = SALUT_FILE_TRANSFER_CHANNEL (iface);
+ TpBaseConnection *base_conn = (TpBaseConnection *) self->priv->connection;
+ GError *error = NULL;
+
+ if (self->priv->initiator != base_conn->self_handle)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Channel is not an outgoing transfer");
+ dbus_g_method_return_error (context, error);
+ return;
+ }
+
+ if (self->priv->socket_address != NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "ProvideFile has already been called for this channel");
+ dbus_g_method_return_error (context, error);
+ return;
+ }
+
+ if (!check_address_and_access_control (self, address_type, access_control,
+ access_control_param, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ if (!setup_local_socket (self))
+ {
+ DEBUG ("Could not set up local socket");
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Could not set up local socket");
+ dbus_g_method_return_error (context, error);
+ }
+
+ if (self->priv->remote_accepted)
+ {
+ /* Remote already accepted the file. Channel is Open.
+ * If not channel stay Pending. */
+ tp_svc_channel_type_file_transfer_emit_initial_offset_defined (self,
+ self->priv->initial_offset);
+
+ salut_file_transfer_channel_set_state (iface,
+ TP_FILE_TRANSFER_STATE_OPEN,
+ TP_FILE_TRANSFER_STATE_CHANGE_REASON_REQUESTED);
+ }
+
+ tp_svc_channel_type_file_transfer_return_from_provide_file (context,
+ self->priv->socket_address);
+}
+
+static void
+file_transfer_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelTypeFileTransferClass *klass =
+ (TpSvcChannelTypeFileTransferClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_type_file_transfer_implement_##x (\
+ klass, salut_file_transfer_channel_##x)
+ IMPLEMENT (accept_file);
+ IMPLEMENT (provide_file);
+#undef IMPLEMENT
+}
+
+static gchar *
+get_local_unix_socket_path (SalutFileTransferChannel *self)
+{
+ gchar *path = NULL;
+ gint32 random_int;
+ gchar *random_str;
+ struct stat buf;
+
+ while (TRUE)
+ {
+ random_int = g_random_int_range (0, G_MAXINT32);
+ random_str = g_strdup_printf ("tp-ft-%i", random_int);
+ path = g_build_filename (g_get_tmp_dir (), random_str, NULL);
+ g_free (random_str);
+
+ if (g_stat (path, &buf) != 0)
+ break;
+
+ g_free (path);
+ }
+
+ return path;
+}
+
+/*
+ * Return a GIOChannel for the local unix socket path.
+ */
+static GIOChannel *
+get_socket_channel (SalutFileTransferChannel *self)
+{
+ gint fd;
+ gchar *path;
+ size_t path_len;
+ struct sockaddr_un addr;
+ GIOChannel *io_channel;
+ GArray *array;
+
+ /* FIXME: should use the socket type and access control chosen by
+ * the user. */
+ path = get_local_unix_socket_path (self);
+
+ array = g_array_sized_new (TRUE, FALSE, sizeof (gchar), strlen (path));
+ g_array_insert_vals (array, 0, path, strlen (path));
+
+ self->priv->socket_address = tp_g_value_slice_new (
+ DBUS_TYPE_G_UCHAR_ARRAY);
+ g_value_take_boxed (self->priv->socket_address, array);
+
+ DEBUG ("local socket %s", path);
+
+ fd = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ {
+ DEBUG("socket() failed");
+ return NULL;
+ }
+
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ path_len = strlen (path);
+ strncpy (addr.sun_path, path, path_len);
+ g_unlink (path);
+
+ /* save this so we can delete the actual socket later if it exists */
+ self->priv->path = path;
+
+ if (bind (fd, (struct sockaddr*) &addr,
+ G_STRUCT_OFFSET (struct sockaddr_un, sun_path) + path_len) < 0)
+ {
+ DEBUG ("bind failed");
+ close (fd);
+ return NULL;
+ }
+
+ if (listen (fd, 1) < 0)
+ {
+ DEBUG ("listen failed");
+ close (fd);
+ return NULL;
+ }
+
+ io_channel = g_io_channel_unix_new (fd);
+ g_io_channel_set_close_on_unref (io_channel, TRUE);
+ return io_channel;
+}
+
+/*
+ * Some client is connecting to the Unix socket.
+ */
+static gboolean
+accept_local_socket_connection (GIOChannel *source,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ GibberFileTransfer *ft;
+ int new_fd;
+ struct sockaddr_un addr;
+ socklen_t addrlen;
+ GIOChannel *channel;
+
+ ft = SALUT_FILE_TRANSFER_CHANNEL (user_data)->priv->ft;
+
+ g_assert (ft != NULL);
+
+ if (condition & G_IO_IN)
+ {
+ DEBUG ("Client connected to local socket");
+
+ addrlen = sizeof (addr);
+ new_fd = accept (g_io_channel_unix_get_fd (source),
+ (struct sockaddr *) &addr, &addrlen);
+ if (new_fd < 0)
+ {
+ DEBUG ("accept() failed");
+ return FALSE;
+ }
+
+ channel = g_io_channel_unix_new (new_fd);
+ g_io_channel_set_close_on_unref (channel, TRUE);
+ g_io_channel_set_encoding (channel, NULL, NULL);
+ if (ft->direction == GIBBER_FILE_TRANSFER_DIRECTION_INCOMING)
+ gibber_file_transfer_receive (ft, channel);
+ else
+ gibber_file_transfer_send (ft, channel);
+ g_io_channel_unref (channel);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+setup_local_socket (SalutFileTransferChannel *self)
+{
+ GIOChannel *io_channel;
+
+ io_channel = get_socket_channel (self);
+ if (io_channel == NULL)
+ {
+ return FALSE;
+ }
+
+ g_io_add_watch (io_channel, G_IO_IN | G_IO_HUP,
+ accept_local_socket_connection, self);
+ g_io_channel_unref (io_channel);
+
+ return TRUE;
+}
+
+static WockyDataForm *
+find_data_form (GibberFileTransfer *ft,
+ const gchar *form_type)
+{
+ GList *l;
+
+ for (l = ft->dataforms; l != NULL; l = l->next)
+ {
+ WockyDataForm *form = l->data;
+ WockyDataFormField *field;
+
+ field = g_hash_table_lookup (form->fields, "FORM_TYPE");
+
+ if (field == NULL)
+ {
+ DEBUG ("Data form doesn't have FORM_TYPE field!");
+ continue;
+ }
+
+ /* found it! */
+ if (!tp_strdiff (field->raw_value_contents[0], form_type))
+ return form;
+ }
+
+ return NULL;
+}
+
+static gchar *
+extract_service_name (GibberFileTransfer *ft)
+{
+ WockyDataForm *form = find_data_form (ft, NS_TP_FT_METADATA_SERVICE);
+ WockyDataFormField *field;
+ gchar *service_name = NULL;
+
+ if (form == NULL)
+ return NULL;
+
+ field = g_hash_table_lookup (form->fields, "ServiceName");
+
+ if (field == NULL)
+ {
+ DEBUG ("ServiceName propery not present in data form; odd...");
+ goto out;
+ }
+
+ if (field->raw_value_contents == NULL
+ || field->raw_value_contents[0] == NULL)
+ {
+ DEBUG ("ServiceName property doesn't have a real value; odd...");
+ }
+ else
+ {
+ service_name = g_strdup (field->raw_value_contents[0]);
+ }
+
+out:
+ return service_name;
+}
+
+static GHashTable *
+extract_metadata (GibberFileTransfer *ft)
+{
+ WockyDataForm *form = find_data_form (ft, NS_TP_FT_METADATA);
+ GHashTable *metadata;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (form == NULL)
+ return NULL;
+
+ metadata = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_strfreev);
+
+ g_hash_table_iter_init (&iter, form->fields);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const gchar *var = key;
+ WockyDataFormField *field = value;
+
+ if (!tp_strdiff (var, "FORM_TYPE"))
+ continue;
+
+ g_hash_table_insert (metadata,
+ g_strdup (var),
+ g_strdupv (field->raw_value_contents));
+ }
+
+ return metadata;
+}
+
+SalutFileTransferChannel *
+salut_file_transfer_channel_new (SalutConnection *conn,
+ SalutContact *contact,
+ TpHandle handle,
+ TpHandle initiator_handle,
+ TpFileTransferState state,
+ const gchar *content_type,
+ const gchar *filename,
+ guint64 size,
+ TpFileHashType content_hash_type,
+ const gchar *content_hash,
+ const gchar *description,
+ guint64 date,
+ guint64 initial_offset,
+ const gchar *file_uri,
+ const gchar *service_name,
+ const GHashTable *metadata)
+{
+ return g_object_new (SALUT_TYPE_FILE_TRANSFER_CHANNEL,
+ "connection", conn,
+ "contact", contact,
+ "handle", handle,
+ "initiator-handle", initiator_handle,
+ "state", state,
+ "content-type", content_type,
+ "filename", filename,
+ "size", size,
+ "content-hash-type", content_hash_type,
+ "content-hash", content_hash,
+ "description", description,
+ "date", date,
+ "initial-offset", initial_offset,
+ "uri", file_uri,
+ "service-name", service_name,
+ "metadata", metadata,
+ NULL);
+}
+
+SalutFileTransferChannel *
+salut_file_transfer_channel_new_from_stanza (SalutConnection *connection,
+ SalutContact *contact,
+ TpHandle handle,
+ TpFileTransferState state,
+ WockyStanza *stanza)
+{
+ GError *error = NULL;
+ GibberFileTransfer *ft;
+ SalutFileTransferChannel *chan;
+ gchar *service_name;
+ GHashTable *metadata;
+
+ ft = gibber_file_transfer_new_from_stanza_with_from (stanza, connection->porter,
+ WOCKY_CONTACT (contact), contact->name, &error);
+
+ if (ft == NULL)
+ {
+ /* Reply with an error */
+ WockyStanza *reply;
+ GError err = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "failed to parse file offer" };
+
+ DEBUG ("%s", error->message);
+ reply = wocky_stanza_build_iq_error (stanza, NULL);
+ wocky_stanza_error_to_node (&err, wocky_stanza_get_top_node (reply));
+
+ wocky_porter_send (connection->porter, reply);
+
+ g_object_unref (reply);
+ g_clear_error (&error);
+ return NULL;
+ }
+
+ /* Metadata */
+ service_name = extract_service_name (ft);
+ metadata = extract_metadata (ft);
+
+ DEBUG ("Received file offer with id '%s'", ft->id);
+
+ chan = g_object_new (SALUT_TYPE_FILE_TRANSFER_CHANNEL,
+ "connection", connection,
+ "contact", contact,
+ "handle", handle,
+ "initiator-handle", handle,
+ "state", state,
+ "filename", ft->filename,
+ "size", gibber_file_transfer_get_size (ft),
+ "description", ft->description,
+ "content-type", ft->content_type,
+ "service-name", service_name,
+ "metadata", metadata,
+ NULL);
+
+ g_free (service_name);
+ if (metadata != NULL)
+ g_hash_table_unref (metadata);
+
+ chan->priv->ft = ft;
+
+ g_signal_connect (ft, "error", G_CALLBACK (error_cb), chan);
+
+ return chan;
+}
diff --git a/salut/src/file-transfer-channel.h b/salut/src/file-transfer-channel.h
new file mode 100644
index 000000000..f1a71b170
--- /dev/null
+++ b/salut/src/file-transfer-channel.h
@@ -0,0 +1,92 @@
+/*
+ * file-transfer-channel.h - Header for SalutFileTransferChannel
+ * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
+ * Copyright (C) 2005, 2007, 2008 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ * @author: Jonny Lamb <jonny.lamb@collabora.co.uk>
+ * @author: Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_FILE_TRANSFER_CHANNEL_H__
+#define __SALUT_FILE_TRANSFER_CHANNEL_H__
+
+#include <glib-object.h>
+#include <gibber/gibber-file-transfer.h>
+
+#include <extensions/_gen/svc.h>
+#include <extensions/_gen/interfaces.h>
+#include <extensions/_gen/enums.h>
+
+#include "contact.h"
+#include "connection.h"
+#include "contact.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutFileTransferChannel SalutFileTransferChannel;
+typedef struct _SalutFileTransferChannelClass SalutFileTransferChannelClass;
+typedef struct _SalutFileTransferChannelPrivate SalutFileTransferChannelPrivate;
+
+struct _SalutFileTransferChannelClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _SalutFileTransferChannel {
+ GObject parent;
+
+ SalutFileTransferChannelPrivate *priv;
+};
+
+GType salut_file_transfer_channel_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_FILE_TRANSFER_CHANNEL \
+ (salut_file_transfer_channel_get_type ())
+#define SALUT_FILE_TRANSFER_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_FILE_TRANSFER_CHANNEL, SalutFileTransferChannel))
+#define SALUT_FILE_TRANSFER_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_FILE_TRANSFER_CHANNEL, \
+ SalutFileTransferChannelClass))
+#define SALUT_IS_FILE_TRANSFER_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_FILE_TRANSFER_CHANNEL))
+#define SALUT_IS_FILE_TRANSFER_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_FILE_TRANSFER_CHANNEL))
+#define SALUT_FILE_TRANSFER_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_FILE_TRANSFER_CHANNEL, \
+ SalutFileTransferChannelClass))
+
+gboolean salut_file_transfer_channel_offer_file (SalutFileTransferChannel *self,
+ GError **error);
+
+SalutFileTransferChannel * salut_file_transfer_channel_new (
+ SalutConnection *conn, SalutContact *contact,
+ TpHandle handle, TpHandle initiator_handle,
+ TpFileTransferState state, const gchar *content_type,
+ const gchar *filename, guint64 size, TpFileHashType hash_type,
+ const gchar *content_hash, const gchar *description, guint64 date,
+ guint64 initial_offset, const gchar *file_uri,
+ const gchar *service_name, const GHashTable *metadata);
+
+SalutFileTransferChannel * salut_file_transfer_channel_new_from_stanza (
+ SalutConnection *connection, SalutContact *contact,
+ TpHandle handle,
+ TpFileTransferState state, WockyStanza *stanza);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_FILE_TRANSFER_CHANNEL_H__*/
diff --git a/salut/src/ft-manager.c b/salut/src/ft-manager.c
new file mode 100644
index 000000000..f4519b39b
--- /dev/null
+++ b/salut/src/ft-manager.c
@@ -0,0 +1,735 @@
+/*
+ * ft-manager.c - Source for SalutFtManager
+ * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
+ * Copyright (C) 2006, 2008 Collabora Ltd.
+ * @author: Jonny Lamb <jonny.lamb@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <wocky/wocky-namespaces.h>
+
+#include <gibber/gibber-file-transfer.h>
+
+#include "ft-manager.h"
+#include "signals-marshal.h"
+
+#include <salut/caps-channel-manager.h>
+
+#include "file-transfer-channel.h"
+#include "contact-manager.h"
+#include "presence-cache.h"
+#include "namespaces.h"
+
+#include <telepathy-glib/channel-factory-iface.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+
+#define DEBUG_FLAG DEBUG_FT
+#include "debug.h"
+
+static void
+channel_manager_iface_init (gpointer, gpointer);
+static void gabble_caps_channel_manager_iface_init (
+ GabbleCapsChannelManagerIface *);
+
+static void salut_ft_manager_channel_created (SalutFtManager *mgr,
+ SalutFileTransferChannel *chan, gpointer request_token);
+
+typedef enum
+{
+ FT_CAPA_SUPPORTED = 1,
+ FT_CAPA_UNSUPPORTED,
+} FtCapaStatus;
+
+G_DEFINE_TYPE_WITH_CODE (SalutFtManager, salut_ft_manager, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init);
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_CAPS_CHANNEL_MANAGER,
+ gabble_caps_channel_manager_iface_init))
+
+/* private structure */
+typedef struct _SalutFtManagerPrivate SalutFtManagerPrivate;
+
+struct _SalutFtManagerPrivate
+{
+ gboolean dispose_has_run;
+ SalutConnection *connection;
+ SalutContactManager *contact_manager;
+ GList *channels;
+ guint message_handler_id;
+};
+
+#define SALUT_FT_MANAGER_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_FT_MANAGER, \
+ SalutFtManagerPrivate))
+
+static void
+salut_ft_manager_init (SalutFtManager *obj)
+{
+ SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (obj);
+ priv->contact_manager = NULL;
+ priv->connection = NULL;
+
+ /* allocate any data required by the object here */
+ priv->channels = NULL;
+}
+
+static gboolean
+message_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutFtManager *self = SALUT_FT_MANAGER (user_data);
+ SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);
+ SalutFileTransferChannel *chan;
+ TpHandle handle;
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->connection);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ SalutContact *contact;
+
+ /* make sure we can support this kind of ft */
+ if (!gibber_file_transfer_is_file_offer (stanza))
+ return FALSE;
+
+ contact = SALUT_CONTACT (wocky_stanza_get_from_contact (stanza));
+
+ handle = tp_handle_lookup (handle_repo, contact->name, NULL, NULL);
+ g_assert (handle != 0);
+
+ DEBUG ("new incoming channel");
+
+ /* this can fail if the stanza isn't valid */
+ chan = salut_file_transfer_channel_new_from_stanza (priv->connection,
+ contact, handle,
+ TP_FILE_TRANSFER_STATE_PENDING, stanza);
+
+ if (chan != NULL)
+ salut_ft_manager_channel_created (self, chan, NULL);
+
+ return TRUE;
+}
+
+static void salut_ft_manager_dispose (GObject *object);
+static void salut_ft_manager_finalize (GObject *object);
+
+static void
+salut_ft_manager_class_init (SalutFtManagerClass *salut_ft_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_ft_manager_class);
+
+ g_type_class_add_private (salut_ft_manager_class,
+ sizeof (SalutFtManagerPrivate));
+
+ object_class->dispose = salut_ft_manager_dispose;
+ object_class->finalize = salut_ft_manager_finalize;
+}
+
+void
+salut_ft_manager_dispose (GObject *object)
+{
+ SalutFtManager *self = SALUT_FT_MANAGER (object);
+ SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);
+ GList *l;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->connection->porter != NULL)
+ {
+ wocky_porter_unregister_handler (priv->connection->porter,
+ priv->message_handler_id);
+ priv->message_handler_id = 0;
+ }
+
+ if (priv->contact_manager != NULL)
+ {
+ g_object_unref (priv->contact_manager);
+ priv->contact_manager = NULL;
+ }
+
+ for (l = priv->channels; l != NULL; l = g_list_next (l))
+ {
+ g_signal_handlers_disconnect_matched (l->data,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+ g_object_unref (l->data);
+ }
+
+ if (priv->channels)
+ g_list_free (priv->channels);
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (salut_ft_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_ft_manager_parent_class)->dispose (object);
+}
+
+void
+salut_ft_manager_finalize (GObject *object)
+{
+ /*SalutFtManager *self = SALUT_FT_MANAGER (object);*/
+ /*SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);*/
+
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (salut_ft_manager_parent_class)->finalize (object);
+}
+
+/* Channel Manager interface */
+
+struct foreach_data {
+ TpExportableChannelFunc func;
+ gpointer data;
+};
+
+static void
+salut_ft_manager_iface_foreach_one (gpointer value,
+ gpointer data)
+{
+ TpExportableChannel *chan;
+ struct foreach_data *f = (struct foreach_data *) data;
+
+ if (!value)
+ return;
+
+ chan = TP_EXPORTABLE_CHANNEL (value);
+
+ f->func (chan, f->data);
+}
+
+static void
+salut_ft_manager_foreach_channel (TpChannelManager *iface,
+ TpExportableChannelFunc func,
+ gpointer data)
+{
+ SalutFtManager *mgr = SALUT_FT_MANAGER (iface);
+ SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (mgr);
+ struct foreach_data f;
+ f.func = func;
+ f.data = data;
+
+ g_list_foreach (priv->channels, (GFunc) salut_ft_manager_iface_foreach_one,
+ &f);
+}
+
+static void
+file_channel_closed (SalutFtManager *self,
+ SalutFileTransferChannel *chan)
+{
+ SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);
+ TpHandle handle;
+
+ if (priv->channels)
+ {
+ g_object_get (chan, "handle", &handle, NULL);
+ DEBUG ("Removing channel with handle %d", handle);
+ priv->channels = g_list_remove (priv->channels, chan);
+ g_object_unref (chan);
+ }
+}
+
+static void
+file_channel_closed_cb (SalutFileTransferChannel *chan, gpointer user_data)
+{
+ SalutFtManager *self = SALUT_FT_MANAGER (user_data);
+
+ file_channel_closed (self, chan);
+}
+
+static void
+salut_ft_manager_channel_created (SalutFtManager *self,
+ SalutFileTransferChannel *chan,
+ gpointer request_token)
+{
+ SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);
+ GSList *requests = NULL;
+
+ g_signal_connect (chan, "closed", G_CALLBACK (file_channel_closed_cb), self);
+
+ priv->channels = g_list_append (priv->channels, chan);
+
+ if (request_token != NULL)
+ requests = g_slist_prepend (requests, request_token);
+
+ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
+ requests);
+
+ g_slist_free (requests);
+}
+
+static gboolean
+salut_ft_manager_handle_request (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutFtManager *self = SALUT_FT_MANAGER (manager);
+ SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);
+ SalutFileTransferChannel *chan;
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (priv->connection);
+ TpHandleRepoIface *contact_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle;
+ const gchar *content_type, *filename, *content_hash, *description;
+ guint64 size, date, initial_offset;
+ const gchar *file_uri, *service_name;
+ const GHashTable *metadata;
+ TpFileHashType content_hash_type;
+ GError *error = NULL;
+ gboolean valid;
+ SalutContact *contact;
+
+ DEBUG ("File transfer request");
+
+ /* We only support file transfer channels */
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType"),
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
+ return FALSE;
+
+ /* And only contact handles */
+ if (tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT)
+ return FALSE;
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandle", NULL);
+
+ /* Must be a valid contact handle */
+ if (!tp_handle_is_valid (contact_repo, handle, &error))
+ goto error;
+
+ /* Don't support opening a channel to our self handle */
+ if (handle == base_connection->self_handle)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Can't open a file transfer channel to yourself");
+ goto error;
+ }
+
+ content_type = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType");
+ if (content_type == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "ContentType property is mandatory");
+ goto error;
+ }
+
+ filename = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename");
+ if (filename == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Filename property is mandatory");
+ goto error;
+ }
+
+ size = tp_asv_get_uint64 (request_properties,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", NULL);
+ if (size == 0)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Size property is mandatory");
+ goto error;
+ }
+
+ content_hash_type = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", &valid);
+ if (!valid)
+ {
+ /* Assume File_Hash_Type_None */
+ content_hash_type = TP_FILE_HASH_TYPE_NONE;
+ }
+ else
+ {
+ if (content_hash_type >= NUM_TP_FILE_HASH_TYPES)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "%u is not a valid ContentHashType", content_hash_type);
+ goto error;
+ }
+ }
+
+ if (content_hash_type != TP_FILE_HASH_TYPE_NONE)
+ {
+ content_hash = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash");
+ if (content_hash == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "ContentHash property is mandatory if ContentHashType is "
+ "not None");
+ goto error;
+ }
+ }
+ else
+ {
+ content_hash = NULL;
+ }
+
+ description = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Description");
+
+ date = tp_asv_get_uint64 (request_properties,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", NULL);
+
+ initial_offset = tp_asv_get_uint64 (request_properties,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".InitialOffset", NULL);
+
+ file_uri = tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI);
+
+ service_name = tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME);
+
+ metadata = tp_asv_get_boxed (request_properties,
+ TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA,
+ TP_HASH_TYPE_METADATA);
+
+ if (metadata != NULL && g_hash_table_lookup ((GHashTable *) metadata, "FORM_TYPE"))
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Metadata cannot contain an item with key 'FORM_TYPE'");
+ goto error;
+ }
+
+ contact = salut_contact_manager_get_contact (priv->contact_manager, handle);
+ if (contact == NULL)
+ {
+ const gchar *name = tp_handle_inspect (contact_repo, handle);
+
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "%s is not online", name);
+
+ goto error;
+ }
+
+ if (service_name != NULL || metadata != NULL)
+ {
+ if (!gabble_capability_set_has (contact->caps, NS_TP_FT_METADATA))
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_CAPABLE,
+ "The specified contact does not support the "
+ "Metadata extension; you should ensure both ServiceName and "
+ "Metadata properties are not present in the channel "
+ "request");
+ goto error;
+ }
+ }
+
+ DEBUG ("Requested outgoing channel with contact: %s",
+ tp_handle_inspect (contact_repo, handle));
+
+ chan = salut_file_transfer_channel_new (priv->connection, contact,
+ handle, base_connection->self_handle,
+ TP_FILE_TRANSFER_STATE_PENDING, content_type, filename, size,
+ content_hash_type, content_hash, description, date, initial_offset,
+ file_uri, service_name, metadata);
+
+ g_object_unref (contact);
+
+ if (!salut_file_transfer_channel_offer_file (chan, &error))
+ {
+ /* Pretend the chan was closed so it's removed from the channels
+ * list and unreffed. */
+ file_channel_closed (self, chan);
+ goto error;
+ }
+
+ salut_ft_manager_channel_created (self, chan, request_token);
+
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+/* Keep in sync with values set in
+ * salut_ft_manager_type_foreach_channel_class */
+static const gchar * const file_transfer_channel_fixed_properties[] = {
+ TP_IFACE_CHANNEL ".ChannelType",
+ TP_IFACE_CHANNEL ".TargetHandleType",
+ NULL
+};
+
+ /* ContentHashType has to be first so we can easily skip it if needed (we
+ * currently don't as Salut doesn't support any hash mechanism) */
+#define STANDARD_PROPERTIES \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE, \
+ TP_PROP_CHANNEL_TARGET_HANDLE, \
+ TP_PROP_CHANNEL_TARGET_ID, \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE, \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME, \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH, \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DESCRIPTION, \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DATE, \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_INITIAL_OFFSET, \
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI
+
+static const gchar * const file_transfer_channel_allowed_properties[] =
+{
+ STANDARD_PROPERTIES,
+ NULL
+};
+
+static const gchar * const file_transfer_channel_allowed_properties_with_metadata_prop[] =
+{
+ STANDARD_PROPERTIES,
+ TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA,
+ NULL
+};
+
+static const gchar * const file_transfer_channel_allowed_properties_with_both_metadata_props[] =
+{
+ STANDARD_PROPERTIES,
+ TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME,
+ TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA,
+ NULL
+};
+
+static void
+salut_ft_manager_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table;
+ GValue *value;
+
+ table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType" , value);
+
+ value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", value);
+
+ func (type, table, file_transfer_channel_allowed_properties_with_both_metadata_props,
+ user_data);
+
+ g_hash_table_unref (table);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = salut_ft_manager_foreach_channel;
+ iface->type_foreach_channel_class =
+ salut_ft_manager_type_foreach_channel_class;
+ iface->request_channel = salut_ft_manager_handle_request;
+ iface->create_channel = salut_ft_manager_handle_request;
+ iface->ensure_channel = salut_ft_manager_handle_request;
+}
+
+/* public functions */
+SalutFtManager *
+salut_ft_manager_new (SalutConnection *connection,
+ SalutContactManager *contact_manager)
+{
+ SalutFtManager *ret = NULL;
+ SalutFtManagerPrivate *priv;
+
+ g_assert (connection != NULL);
+
+ ret = g_object_new (SALUT_TYPE_FT_MANAGER, NULL);
+ priv = SALUT_FT_MANAGER_GET_PRIVATE (ret);
+
+ priv->contact_manager = contact_manager;
+ g_object_ref (contact_manager);
+
+ priv->connection = connection;
+
+ priv->message_handler_id = wocky_porter_register_handler_from_anyone (
+ priv->connection->porter,
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ message_stanza_callback, ret, NULL);
+
+ return ret;
+}
+
+static void
+add_file_transfer_channel_class (GPtrArray *arr,
+ gboolean include_metadata_properties,
+ const gchar *service_name_str)
+{
+ GValue monster = {0, };
+ GHashTable *fixed_properties;
+ GValue *channel_type_value;
+ GValue *target_handle_type_value;
+ GValue *service_name_value;
+ const gchar * const *allowed_properties;
+
+ g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS);
+ g_value_take_boxed (&monster,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS));
+
+ fixed_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ channel_type_value = tp_g_value_slice_new_static_string (
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+ g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType",
+ channel_type_value);
+
+ target_handle_type_value = tp_g_value_slice_new_uint (
+ TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".TargetHandleType",
+ target_handle_type_value);
+
+ if (service_name_str != NULL)
+ {
+ service_name_value = tp_g_value_slice_new_string (service_name_str);
+ g_hash_table_insert (fixed_properties,
+ TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME,
+ service_name_value);
+ }
+
+ if (include_metadata_properties)
+ {
+ if (service_name_str == NULL)
+ allowed_properties = file_transfer_channel_allowed_properties_with_both_metadata_props;
+ else
+ allowed_properties = file_transfer_channel_allowed_properties_with_metadata_prop;
+ }
+ else
+ {
+ allowed_properties = file_transfer_channel_allowed_properties;
+ }
+
+ dbus_g_type_struct_set (&monster,
+ 0, fixed_properties,
+ 1, allowed_properties,
+ G_MAXUINT);
+
+ g_hash_table_unref (fixed_properties);
+
+ g_ptr_array_add (arr, g_value_get_boxed (&monster));
+}
+
+static void
+get_contact_caps_foreach (gpointer data,
+ gpointer user_data)
+{
+ const gchar *ns = data;
+ GPtrArray *arr = user_data;
+
+ if (!g_str_has_prefix (ns, NS_TP_FT_METADATA "#"))
+ return;
+
+ add_file_transfer_channel_class (arr, TRUE,
+ ns + strlen (NS_TP_FT_METADATA "#"));
+}
+
+static void
+salut_ft_manager_get_contact_caps_from_set (
+ GabbleCapsChannelManager *iface,
+ TpHandle handle,
+ const GabbleCapabilitySet *caps,
+ GPtrArray *arr)
+{
+ /* If we don't receive any capabilities info (QUIRK_NOT_XEP_CAPABILITIES)
+ * we assume FT is supported to ensure interoperability with other clients */
+ if (gabble_capability_set_has (caps, WOCKY_XMPP_NS_IQ_OOB) ||
+ gabble_capability_set_has (caps, WOCKY_XMPP_NS_X_OOB) ||
+ gabble_capability_set_has (caps, QUIRK_NOT_XEP_CAPABILITIES))
+ {
+ add_file_transfer_channel_class (arr,
+ gabble_capability_set_has (caps, NS_TP_FT_METADATA), NULL);
+ }
+
+ gabble_capability_set_foreach (caps, get_contact_caps_foreach, arr);
+}
+
+static void
+salut_ft_manager_represent_client (
+ GabbleCapsChannelManager *iface G_GNUC_UNUSED,
+ const gchar *client_name,
+ const GPtrArray *filters,
+ const gchar * const *cap_tokens G_GNUC_UNUSED,
+ GabbleCapabilitySet *cap_set,
+ GPtrArray *data_forms)
+{
+ guint i;
+
+ for (i = 0; i < filters->len; i++)
+ {
+ GHashTable *channel_class = g_ptr_array_index (filters, i);
+ const gchar *service_name;
+ gchar *ns;
+
+ if (tp_strdiff (tp_asv_get_string (channel_class,
+ TP_IFACE_CHANNEL ".ChannelType"),
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
+ continue;
+
+ if (tp_asv_get_uint32 (channel_class,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL)
+ != TP_HANDLE_TYPE_CONTACT)
+ continue;
+
+ DEBUG ("client %s supports file transfer", client_name);
+ gabble_capability_set_add (cap_set, WOCKY_XMPP_NS_IQ_OOB);
+ gabble_capability_set_add (cap_set, WOCKY_XMPP_NS_X_OOB);
+ gabble_capability_set_add (cap_set, NS_TP_FT_METADATA);
+
+ /* now look at service names */
+
+ /* capabilities mean being able to RECEIVE said kinds of
+ * FTs. hence, skip Requested=true (locally initiated) channel
+ * classes */
+ if (tp_asv_get_boolean (channel_class,
+ TP_PROP_CHANNEL_REQUESTED, FALSE))
+ continue;
+
+ service_name = tp_asv_get_string (channel_class,
+ TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME);
+
+ if (service_name == NULL)
+ continue;
+
+ ns = g_strconcat (NS_TP_FT_METADATA "#", service_name, NULL);
+
+ DEBUG ("%s: adding capability %s", client_name, ns);
+ gabble_capability_set_add (cap_set, ns);
+ g_free (ns);
+ }
+}
+
+static void
+gabble_caps_channel_manager_iface_init (GabbleCapsChannelManagerIface *iface)
+{
+ iface->get_contact_caps = salut_ft_manager_get_contact_caps_from_set;
+ iface->represent_client = salut_ft_manager_represent_client;
+}
diff --git a/salut/src/ft-manager.h b/salut/src/ft-manager.h
new file mode 100644
index 000000000..d04806d26
--- /dev/null
+++ b/salut/src/ft-manager.h
@@ -0,0 +1,66 @@
+/*
+ * ft-manager.h - Header for SalutFtManager
+ * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
+ * Copyright (C) 2006, 2008 Collabora Ltd.
+ * @author: Jonny Lamb <jonny.lamb@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_FT_MANAGER_H__
+#define __SALUT_FT_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <connection.h>
+#include <im-manager.h>
+
+#include <extensions/_gen/interfaces.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutFtManager SalutFtManager;
+typedef struct _SalutFtManagerClass SalutFtManagerClass;
+
+struct _SalutFtManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _SalutFtManager {
+ GObject parent;
+};
+
+GType salut_ft_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_FT_MANAGER \
+ (salut_ft_manager_get_type ())
+#define SALUT_FT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_FT_MANAGER, SalutFtManager))
+#define SALUT_FT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_FT_MANAGER, SalutFtManagerClass))
+#define SALUT_IS_FT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_FT_MANAGER))
+#define SALUT_IS_FT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_FT_MANAGER))
+#define SALUT_FT_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_FT_MANAGER, SalutFtManagerClass))
+
+SalutFtManager *salut_ft_manager_new (SalutConnection *connection,
+ SalutContactManager *contact_manager);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_FT_MANAGER_H__*/
diff --git a/salut/src/gabble_namespaces.h b/salut/src/gabble_namespaces.h
new file mode 100644
index 000000000..d8e145f25
--- /dev/null
+++ b/salut/src/gabble_namespaces.h
@@ -0,0 +1,27 @@
+/*
+ * namespaces.h — namespace constants definitions that can be used by telepathy-gabble plugins
+ * Copyright © 2009 Collabora Ltd.
+ * Copyright © 2009 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef GABBLE_PLUGINS_NAMESPACES_H
+#define GABBLE_PLUGINS_NAMESPACES_H
+
+#define NS_CAPS "http://jabber.org/protocol/caps"
+#define NS_GABBLE_CAPS "http://telepathy.freedesktop.org/caps"
+
+#endif
diff --git a/salut/src/im-channel.c b/salut/src/im-channel.c
new file mode 100644
index 000000000..722f1bae8
--- /dev/null
+++ b/salut/src/im-channel.c
@@ -0,0 +1,738 @@
+/*
+ * im-channel.c - Source for SalutImChannel
+ * Copyright (C) 2005-2008,2010 Collabora Ltd.
+ * @author: Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "im-channel.h"
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <dbus/dbus-glib.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-generic.h>
+
+#include <gibber/gibber-linklocal-transport.h>
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-meta-porter.h>
+
+#define DEBUG_FLAG DEBUG_IM
+#include "debug.h"
+#include "connection.h"
+#include "contact.h"
+#include "signals-marshal.h"
+#include "util.h"
+#include "text-helper.h"
+
+static void channel_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SalutImChannel, salut_im_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT,
+ tp_message_mixin_text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES,
+ tp_message_mixin_messages_iface_init);
+);
+
+static const gchar *salut_im_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES,
+ NULL
+};
+
+/* properties */
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_CONTACT,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_TARGET_ID,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_REQUESTED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_CHANNEL_DESTROYED,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutImChannelPrivate SalutImChannelPrivate;
+
+struct _SalutImChannelPrivate
+{
+ gboolean dispose_has_run;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+ SalutContact *contact;
+ SalutConnection *connection;
+ guint message_handler_id;
+ gboolean closed;
+};
+
+#define SALUT_IM_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((o), SALUT_TYPE_IM_CHANNEL, SalutImChannelPrivate))
+
+static void
+salut_im_channel_do_close (SalutImChannel *self)
+{
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (self);
+ WockyPorter *porter = priv->connection->porter;
+
+ if (priv->closed)
+ return;
+ priv->closed = TRUE;
+
+ wocky_porter_unregister_handler (porter,
+ priv->message_handler_id);
+ priv->message_handler_id = 0;
+
+ wocky_meta_porter_unhold (WOCKY_META_PORTER (porter),
+ WOCKY_CONTACT (priv->contact));
+
+ DEBUG ("Emitting closed signal for %s", priv->object_path);
+ tp_svc_channel_emit_closed (self);
+}
+
+static void
+salut_im_channel_init (SalutImChannel *obj)
+{
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (obj);
+ /* allocate any data required by the object here */
+ priv->object_path = NULL;
+ priv->contact = NULL;
+}
+
+
+static void
+salut_im_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutImChannel *chan = SALUT_IM_CHANNEL (object);
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (chan);
+ TpBaseConnection *base_conn = (TpBaseConnection *) priv->connection;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, priv->handle);
+ break;
+ case PROP_CONTACT:
+ g_value_set_object (value, priv->contact);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->connection);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_static_boxed (value, salut_im_channel_interfaces);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->handle));
+ }
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_assert (priv->initiator != 0);
+ g_value_set_string (value,
+ tp_handle_inspect (repo, priv->initiator));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, (priv->initiator == base_conn->self_handle));
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ /* TODO: this should be FALSE if there are still pending messages, so
+ * the channel manager can respawn the channel.
+ */
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessagePartSupportFlags",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "DeliveryReportingSupport",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "SupportedContentTypes",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessageTypes",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_im_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutImChannel *chan = SALUT_IM_CHANNEL (object);
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (chan);
+ const gchar *tmp;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ priv->initiator = g_value_get_uint (value);
+ g_assert (priv->initiator != 0);
+ break;
+ case PROP_CONTACT:
+ priv->contact = g_value_dup_object (value);
+ break;
+ case PROP_CONNECTION:
+ priv->connection = g_value_get_object (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ /* 0 is the old tp-glib value of unset, TP_UNKNOWN_HANDLE_TYPE is the
+ * new version */
+ g_assert (g_value_get_uint (value) == 0
+ || g_value_get_uint (value) == TP_HANDLE_TYPE_CONTACT
+ || g_value_get_uint (value) == TP_UNKNOWN_HANDLE_TYPE);
+ break;
+ case PROP_CHANNEL_TYPE:
+ tmp = g_value_get_string (value);
+ g_assert (tmp == NULL || !tp_strdiff (g_value_get_string (value),
+ TP_IFACE_CHANNEL_TYPE_TEXT));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+#define NUM_SUPPORTED_MESSAGE_TYPES 3
+
+static void
+_salut_im_channel_send (GObject *channel,
+ TpMessage *message,
+ TpMessageSendingFlags flags);
+
+static gboolean new_message_cb (WockyPorter *porter,
+ WockyStanza *stanza, gpointer user_data);
+
+static GObject *
+salut_im_channel_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ TpDBusDaemon *bus;
+ SalutImChannelPrivate *priv;
+ TpBaseConnection *base_conn;
+ TpHandleRepoIface *contact_repo;
+ WockyPorter *porter;
+ gchar *jid;
+
+ TpChannelTextMessageType types[NUM_SUPPORTED_MESSAGE_TYPES] = {
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
+ };
+
+ const gchar * supported_content_types[] = {
+ "text/plain",
+ NULL
+ };
+
+ /* Parent constructor chain */
+ obj = G_OBJECT_CLASS (salut_im_channel_parent_class)->
+ constructor (type, n_props, props);
+
+ priv = SALUT_IM_CHANNEL_GET_PRIVATE (SALUT_IM_CHANNEL (obj));
+ porter = priv->connection->porter;
+
+ /* Ref our handle and initiator handle */
+ base_conn = TP_BASE_CONNECTION (priv->connection);
+
+ contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_ref (contact_repo, priv->handle);
+
+ g_assert (priv->initiator != 0);
+ tp_handle_ref (contact_repo, priv->initiator);
+
+ /* Initialize message mixin */
+ tp_message_mixin_init (obj, G_STRUCT_OFFSET (SalutImChannel, message_mixin),
+ base_conn);
+
+ tp_message_mixin_implement_sending (obj, _salut_im_channel_send,
+ NUM_SUPPORTED_MESSAGE_TYPES, types, 0,
+ TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES,
+ supported_content_types);
+
+ /* Connect to the bus */
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+
+ /* Connect to further messages */
+ jid = wocky_contact_dup_jid (WOCKY_CONTACT (priv->contact));
+
+ priv->message_handler_id = wocky_porter_register_handler_from (
+ porter, WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
+ jid, WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ new_message_cb, obj, NULL);
+
+ g_free (jid);
+
+ /* ensure the connection doesn't close */
+ wocky_meta_porter_hold (WOCKY_META_PORTER (porter),
+ WOCKY_CONTACT (priv->contact));
+
+ return obj;
+}
+
+static void salut_im_channel_dispose (GObject *object);
+static void salut_im_channel_finalize (GObject *object);
+
+static void
+salut_im_channel_class_init (SalutImChannelClass *salut_im_channel_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_im_channel_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "TargetID", "target-id", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (salut_im_channel_class,
+ sizeof (SalutImChannelPrivate));
+
+ object_class->dispose = salut_im_channel_dispose;
+ object_class->finalize = salut_im_channel_finalize;
+
+ object_class->constructor = salut_im_channel_constructor;
+ object_class->get_property = salut_im_channel_get_property;
+ object_class->set_property = salut_im_channel_set_property;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_string ("target-id", "Target JID",
+ "The string obtained by inspecting this channel's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator JID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "contact",
+ "SalutContact object",
+ "Salut Contact to which this channel is dedicated",
+ SALUT_TYPE_CONTACT,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "Salut Connection that owns the connection for this IM channel",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ salut_im_channel_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutImChannelClass, dbus_props_class));
+
+ tp_message_mixin_init_dbus_properties (object_class);
+}
+
+void
+salut_im_channel_dispose (GObject *object)
+{
+ SalutImChannel *self = SALUT_IM_CHANNEL (object);
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->connection);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ tp_handle_unref (handle_repo, priv->handle);
+
+ if (priv->initiator != 0)
+ tp_handle_unref (handle_repo, priv->initiator);
+
+ salut_im_channel_do_close (self);
+
+ g_object_unref (priv->contact);
+ priv->contact = NULL;
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (salut_im_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_im_channel_parent_class)->dispose (object);
+}
+
+static void
+salut_im_channel_finalize (GObject *object)
+{
+ SalutImChannel *self = SALUT_IM_CHANNEL (object);
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+ g_free (priv->object_path);
+
+ tp_message_mixin_finalize (object);
+
+ G_OBJECT_CLASS (salut_im_channel_parent_class)->finalize (object);
+}
+
+void
+salut_im_channel_received_stanza (SalutImChannel *self,
+ WockyStanza *stanza)
+{
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) priv->connection;
+ const gchar *from;
+ TpChannelTextMessageType msgtype;
+ const gchar *body;
+ const gchar *body_offset;
+
+ if (!text_helper_parse_incoming_message (stanza, &from, &msgtype,
+ &body, &body_offset))
+ {
+ DEBUG ("Stanza not a text message, ignoring");
+ return;
+ }
+
+ if (body == NULL)
+ {
+ /* No body ? Ignore */
+ DEBUG ("Text message without a body");
+ return;
+ }
+
+ /* FIXME validate the from */
+ tp_message_mixin_take_received (G_OBJECT (self),
+ text_helper_create_received_message (base_conn, priv->handle,
+ time (NULL), msgtype, body_offset));
+}
+
+static gboolean
+new_message_cb (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutImChannel *self = SALUT_IM_CHANNEL (user_data);
+
+ if (wocky_node_get_child_ns (wocky_stanza_get_top_node (stanza), "invite",
+ WOCKY_TELEPATHY_NS_CLIQUE) != NULL)
+ /* we don't handle Clique MUC invites here, so pass it on to the
+ * next handler */
+ return FALSE;
+
+ salut_im_channel_received_stanza (self, stanza);
+
+ return TRUE;
+}
+
+typedef struct
+{
+ SalutImChannel *self;
+ guint timestamp;
+ TpChannelTextMessageType type;
+ gchar *text;
+ gchar *token;
+} SendMessageData;
+
+static void
+sent_message_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ GError *error = NULL;
+ SendMessageData *data = user_data;
+
+ if (!wocky_porter_send_finish (porter, result, &error))
+ {
+ DEBUG ("Failed to send message: %s", error->message);
+ g_clear_error (&error);
+
+ text_helper_report_delivery_error (TP_SVC_CHANNEL (data->self),
+ TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN, data->timestamp,
+ data->type, data->text, data->token);
+ }
+
+ /* successfully sent message */
+
+ g_free (data->text);
+ g_free (data->token);
+ g_slice_free (SendMessageData, data);
+}
+
+static gboolean
+_send_message (SalutImChannel *self,
+ WockyStanza *stanza,
+ guint timestamp,
+ TpChannelTextMessageType type,
+ const gchar *text,
+ const gchar *token)
+{
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (self);
+ SendMessageData *data;
+
+ data = g_slice_new0 (SendMessageData);
+ data->self = self;
+ data->timestamp = timestamp;
+ data->type = type;
+ data->text = g_strdup (text);
+ data->token = g_strdup (token);
+
+ wocky_porter_send_async (priv->connection->porter,
+ stanza, NULL, sent_message_cb, data);
+
+ return TRUE;
+}
+
+/**
+ * salut_im_channel_close
+ *
+ * Implements DBus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_im_channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ salut_im_channel_do_close (SALUT_IM_CHANNEL (iface));
+ tp_svc_channel_return_from_close (context);
+}
+
+
+/**
+ * salut_im_channel_get_channel_type
+ *
+ * Implements DBus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_im_channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+
+/**
+ * salut_im_channel_get_handle
+ *
+ * Implements DBus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_im_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutImChannel *self = SALUT_IM_CHANNEL (iface);
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (self);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ priv->handle);
+}
+
+
+/**
+ * salut_im_channel_get_interfaces
+ *
+ * Implements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_im_channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_im_channel_interfaces);
+}
+
+static void
+channel_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
+ klass, salut_im_channel_##x)
+ IMPLEMENT(close);
+ IMPLEMENT(get_channel_type);
+ IMPLEMENT(get_handle);
+ IMPLEMENT(get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+_salut_im_channel_send (GObject *channel,
+ TpMessage *message,
+ TpMessageSendingFlags flags)
+{
+ SalutImChannel *self = SALUT_IM_CHANNEL (channel);
+ SalutImChannelPrivate *priv = SALUT_IM_CHANNEL_GET_PRIVATE (self);
+ GError *error = NULL;
+ WockyStanza *stanza = NULL;
+ guint type;
+ gchar *text;
+ gchar *token;
+
+ if (!text_helper_validate_tp_message (message, &type, &token, &text, &error))
+ goto error;
+
+ stanza = text_helper_create_message (priv->connection->name,
+ priv->contact, type, text, &error);
+
+ if (stanza == NULL)
+ goto error;
+
+ if (!_send_message (self, stanza, time (NULL), type, text, token))
+ goto error;
+
+ tp_message_mixin_sent (channel, message, 0, token, NULL);
+ g_free (token);
+ g_object_unref (G_OBJECT (stanza));
+ return;
+
+error:
+ if (stanza != NULL)
+ g_object_unref (G_OBJECT (stanza));
+
+ if (error != NULL && error->domain != TP_ERRORS)
+ {
+ GError *e = NULL;
+ g_set_error_literal (&e, TP_ERRORS,
+ TP_ERROR_NETWORK_ERROR,
+ error->message);
+ g_error_free (error);
+ error = e;
+ }
+
+ if (error != NULL)
+ {
+ tp_message_mixin_sent (channel, message, 0, NULL, error);
+ g_error_free (error);
+ }
+ g_free (text);
+ g_free (token);
+ return;
+}
diff --git a/salut/src/im-channel.h b/salut/src/im-channel.h
new file mode 100644
index 000000000..80b61f4cb
--- /dev/null
+++ b/salut/src/im-channel.h
@@ -0,0 +1,70 @@
+/*
+ * im-channel.h - Header for SalutImChannel
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_IM_CHANNEL_H__
+#define __SALUT_IM_CHANNEL_H__
+
+#include <glib-object.h>
+#include <wocky/wocky-stanza.h>
+
+#include <telepathy-glib/message-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutImChannel SalutImChannel;
+typedef struct _SalutImChannelClass SalutImChannelClass;
+
+struct _SalutImChannelClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _SalutImChannel {
+ GObject parent;
+ TpMessageMixin message_mixin;
+};
+
+GType salut_im_channel_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_IM_CHANNEL \
+ (salut_im_channel_get_type ())
+#define SALUT_IM_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_IM_CHANNEL, SalutImChannel))
+#define SALUT_IM_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_IM_CHANNEL, \
+ SalutImChannelClass))
+#define SALUT_IS_IM_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_IM_CHANNEL))
+#define SALUT_IS_IM_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_IM_CHANNEL))
+#define SALUT_IM_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_IM_CHANNEL, \
+ SalutImChannelClass))
+
+void salut_im_channel_received_stanza (SalutImChannel *chan,
+ WockyStanza *stanza);
+
+gboolean
+salut_im_channel_is_text_message (WockyStanza *stanza);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_IM_CHANNEL_H__*/
diff --git a/salut/src/im-manager.c b/salut/src/im-manager.c
new file mode 100644
index 000000000..2460dea0c
--- /dev/null
+++ b/salut/src/im-manager.c
@@ -0,0 +1,647 @@
+/*
+ * im-manager.c - Source for SalutImManager
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <salut/caps-channel-manager.h>
+
+#include "extensions/extensions.h"
+#include "im-channel.h"
+#include "im-manager.h"
+#include "contact.h"
+
+#include <gibber/gibber-linklocal-transport.h>
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-namespaces.h>
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+
+#define DEBUG_FLAG DEBUG_IM
+#include "debug.h"
+
+static void salut_im_manager_channel_manager_iface_init (gpointer g_iface,
+ gpointer iface_data);
+static void gabble_caps_channel_manager_iface_init (
+ GabbleCapsChannelManagerIface *);
+
+static SalutImChannel *
+salut_im_manager_new_channel (SalutImManager *mgr, TpHandle handle,
+ TpHandle initiator, gpointer request);
+
+G_DEFINE_TYPE_WITH_CODE (SalutImManager, salut_im_manager, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ salut_im_manager_channel_manager_iface_init);
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_CAPS_CHANNEL_MANAGER,
+ gabble_caps_channel_manager_iface_init))
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_CONTACT_MANAGER,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutImManagerPrivate SalutImManagerPrivate;
+
+struct _SalutImManagerPrivate
+{
+ SalutContactManager *contact_manager;
+ SalutConnection *connection;
+ GHashTable *channels;
+ gulong status_changed_id;
+ guint message_handler_id;
+ gboolean dispose_has_run;
+};
+
+#define SALUT_IM_MANAGER_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_IM_MANAGER, \
+ SalutImManagerPrivate))
+
+static void
+salut_im_manager_init (SalutImManager *obj)
+{
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (obj);
+ /* allocate any data required by the object here */
+ priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+}
+
+static gboolean
+message_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutImManager *self = SALUT_IM_MANAGER (user_data);
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+ SalutImChannel *chan;
+ TpHandle handle;
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->connection);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ SalutContact *contact;
+
+ contact = SALUT_CONTACT (wocky_stanza_get_from_contact (stanza));
+
+ handle = tp_handle_lookup (handle_repo, contact->name, NULL, NULL);
+ g_assert (handle != 0);
+
+ if (g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (handle)) != NULL)
+ return FALSE; /* we only care about opening new channels */
+
+ chan = salut_im_manager_new_channel (self, handle, handle, NULL);
+ salut_im_channel_received_stanza (chan, stanza);
+
+ return TRUE;
+}
+
+static void
+salut_im_factory_close_all (SalutImManager *self)
+{
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+
+ if (priv->channels != NULL)
+ {
+ GHashTable *tmp = priv->channels;
+
+ DEBUG ("closing channels");
+ priv->channels = NULL;
+ g_hash_table_unref (tmp);
+ }
+
+ if (priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (priv->connection, priv->status_changed_id);
+ priv->status_changed_id = 0;
+ }
+}
+
+static void
+connection_status_changed_cb (SalutConnection *conn,
+ guint status,
+ guint reason,
+ SalutImManager *self)
+{
+ if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+ {
+ salut_im_factory_close_all (self);
+ }
+}
+
+
+static void salut_im_manager_dispose (GObject *object);
+static void salut_im_manager_finalize (GObject *object);
+
+static void
+salut_im_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutImManager *self = SALUT_IM_MANAGER (object);
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->connection);
+ break;
+ case PROP_CONTACT_MANAGER:
+ g_value_set_object (value, priv->contact_manager);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_im_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutImManager *self = SALUT_IM_MANAGER (object);
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->connection = g_value_get_object (value);
+ break;
+ case PROP_CONTACT_MANAGER:
+ priv->contact_manager = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+salut_im_manager_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutImManager *self;
+ SalutImManagerPrivate *priv;
+
+ obj = G_OBJECT_CLASS (salut_im_manager_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_IM_MANAGER (obj);
+ priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+
+ priv->message_handler_id = wocky_porter_register_handler_from_anyone (
+ priv->connection->porter, WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_NONE,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ message_stanza_callback, self, NULL);
+
+ priv->status_changed_id = g_signal_connect (priv->connection,
+ "status-changed", (GCallback) connection_status_changed_cb, self);
+
+ return obj;
+}
+
+static void
+salut_im_manager_class_init (SalutImManagerClass *salut_im_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_im_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_im_manager_class,
+ sizeof (SalutImManagerPrivate));
+
+ object_class->constructor = salut_im_manager_constructor;
+ object_class->dispose = salut_im_manager_dispose;
+ object_class->finalize = salut_im_manager_finalize;
+ object_class->get_property = salut_im_manager_get_property;
+ object_class->set_property = salut_im_manager_set_property;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "Salut connection object that owns this text channel factory object.",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_object (
+ "contact-manager",
+ "SalutContactManager object",
+ "Salut Contact Manager associated with the Salut Connection of this "
+ "manager",
+ SALUT_TYPE_CONTACT_MANAGER,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_MANAGER,
+ param_spec);
+}
+
+void
+salut_im_manager_dispose (GObject *object)
+{
+ SalutImManager *self = SALUT_IM_MANAGER (object);
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->connection->porter != NULL)
+ {
+ wocky_porter_unregister_handler (priv->connection->porter,
+ priv->message_handler_id);
+ priv->message_handler_id = 0;
+ }
+
+ if (priv->contact_manager)
+ {
+ g_object_unref (priv->contact_manager);
+ priv->contact_manager = NULL;
+ }
+
+ salut_im_factory_close_all (self);
+
+ if (G_OBJECT_CLASS (salut_im_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_im_manager_parent_class)->dispose (object);
+}
+
+void
+salut_im_manager_finalize (GObject *object)
+{
+ //SalutImManager *self = SALUT_IM_MANAGER (object);
+ //SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (salut_im_manager_parent_class)->finalize (object);
+}
+
+struct foreach_data
+{
+ TpExportableChannelFunc func;
+ gpointer data;
+};
+
+static void
+salut_im_manager_iface_foreach_one (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ TpExportableChannel *chan = TP_EXPORTABLE_CHANNEL (value);
+ struct foreach_data *f = (struct foreach_data *) data;
+
+ f->func (chan, f->data);
+}
+
+static void
+salut_im_manager_foreach_channel (TpChannelManager *iface,
+ TpExportableChannelFunc func,
+ gpointer user_data)
+{
+ SalutImManager *mgr = SALUT_IM_MANAGER (iface);
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (mgr);
+ struct foreach_data f;
+
+ f.func = func;
+ f.data = user_data;
+
+ g_hash_table_foreach (priv->channels, salut_im_manager_iface_foreach_one,
+ &f);
+}
+
+static const gchar * const im_channel_fixed_properties[] = {
+ TP_IFACE_CHANNEL ".ChannelType",
+ TP_IFACE_CHANNEL ".TargetHandleType",
+ NULL
+};
+
+
+static const gchar * const im_channel_allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ TP_IFACE_CHANNEL ".TargetID",
+ NULL
+};
+
+static void
+salut_im_manager_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ GValue *value;
+
+ value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ g_hash_table_insert (table, (gchar *) im_channel_fixed_properties[0],
+ value);
+
+ value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (table, (gchar *) im_channel_fixed_properties[1],
+ value);
+
+ func (type, table, im_channel_allowed_properties, user_data);
+
+ g_hash_table_unref (table);
+}
+
+
+static gboolean
+salut_im_manager_requestotron (SalutImManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) priv->connection;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle;
+ GError *error = NULL;
+ TpExportableChannel *channel;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType"), TP_IFACE_CHANNEL_TYPE_TEXT))
+ return FALSE;
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT)
+ return FALSE;
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandle", NULL);
+
+ if (!tp_handle_is_valid (contact_repo, handle, &error))
+ goto error;
+
+ /* Check if there are any other properties that we don't understand */
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ im_channel_fixed_properties, im_channel_allowed_properties,
+ &error))
+ {
+ goto error;
+ }
+
+ /* Don't support opening a channel to our self handle */
+ if (handle == base_conn->self_handle)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Can't open a text channel to yourself");
+ goto error;
+ }
+
+ channel = g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (handle));
+
+ if (channel == NULL)
+ {
+ salut_im_manager_new_channel (self, handle, base_conn->self_handle,
+ request_token);
+ return TRUE;
+ }
+
+ if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Already chatting with contact #%u in another channel", handle);
+ goto error;
+ }
+
+ tp_channel_manager_emit_request_already_satisfied (self, request_token,
+ channel);
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+
+static gboolean
+salut_im_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutImManager *self = SALUT_IM_MANAGER (manager);
+
+ return salut_im_manager_requestotron (self, request_token,
+ request_properties, TRUE);
+}
+
+
+static gboolean
+salut_im_manager_request_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutImManager *self = SALUT_IM_MANAGER (manager);
+
+ return salut_im_manager_requestotron (self, request_token,
+ request_properties, FALSE);
+}
+
+
+static gboolean
+salut_im_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutImManager *self = SALUT_IM_MANAGER (manager);
+
+ return salut_im_manager_requestotron (self, request_token,
+ request_properties, FALSE);
+}
+
+
+static void
+salut_im_manager_channel_manager_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = salut_im_manager_foreach_channel;
+ iface->type_foreach_channel_class =
+ salut_im_manager_type_foreach_channel_class;
+ iface->create_channel = salut_im_manager_create_channel;
+ iface->request_channel = salut_im_manager_request_channel;
+ iface->ensure_channel = salut_im_manager_ensure_channel;
+}
+
+
+/* private functions */
+static void
+im_channel_closed_cb (SalutImChannel *chan,
+ gpointer user_data)
+{
+ SalutImManager *self = SALUT_IM_MANAGER (user_data);
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (self);
+ TpHandle handle;
+
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (priv->channels)
+ {
+ g_object_get (chan, "handle", &handle, NULL);
+ DEBUG ("Removing channel with handle %u", handle);
+ g_hash_table_remove (priv->channels, GUINT_TO_POINTER (handle));
+ }
+}
+
+static SalutImChannel *
+salut_im_manager_new_channel (SalutImManager *mgr,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request)
+{
+ SalutImManagerPrivate *priv = SALUT_IM_MANAGER_GET_PRIVATE (mgr);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (priv->connection);
+ TpHandleRepoIface *handle_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+ SalutImChannel *chan;
+ SalutContact *contact;
+ const gchar *name;
+ gchar *path = NULL;
+ GSList *requests = NULL;
+
+ g_assert (g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (handle))
+ == NULL);
+
+ name = tp_handle_inspect (handle_repo, handle);
+ DEBUG ("Requested channel for handle: %u (%s)", handle, name);
+
+ contact = salut_contact_manager_get_contact (priv->contact_manager, handle);
+ if (contact == NULL)
+ {
+ gchar *message = g_strdup_printf ("%s is not online", name);
+ tp_channel_manager_emit_request_failed (mgr, request, TP_ERRORS,
+ TP_ERROR_NOT_AVAILABLE, message);
+ g_free (message);
+ return NULL;
+ }
+
+ path = g_strdup_printf ("%s/IMChannel/%u",
+ base_connection->object_path, handle);
+ chan = g_object_new (SALUT_TYPE_IM_CHANNEL,
+ "connection", priv->connection,
+ "contact", contact,
+ "object-path", path,
+ "handle", handle,
+ "initiator-handle", initiator,
+ NULL);
+ g_object_unref (contact);
+ g_free (path);
+ g_hash_table_insert (priv->channels, GUINT_TO_POINTER (handle), chan);
+
+ if (request != NULL)
+ requests = g_slist_prepend (requests, request);
+
+ tp_channel_manager_emit_new_channel (mgr, TP_EXPORTABLE_CHANNEL (chan),
+ requests);
+
+ g_slist_free (requests);
+
+ g_signal_connect (chan, "closed", G_CALLBACK (im_channel_closed_cb), mgr);
+
+ return chan;
+}
+
+/* public functions */
+SalutImManager *
+salut_im_manager_new (SalutConnection *connection,
+ SalutContactManager *contact_manager)
+{
+ return g_object_new (SALUT_TYPE_IM_MANAGER,
+ "connection", connection,
+ "contact-manager", contact_manager,
+ NULL);
+}
+
+static void
+salut_im_manager_add_contact_caps (GPtrArray *arr)
+{
+ GValue monster = {0, };
+ GHashTable *fixed_properties;
+ GValue *channel_type_value;
+ GValue *target_handle_type_value;
+ gchar *text_allowed_properties[] =
+ {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ NULL
+ };
+
+ g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS);
+ g_value_take_boxed (&monster,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS));
+
+ fixed_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ channel_type_value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (channel_type_value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType",
+ channel_type_value);
+
+ target_handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (target_handle_type_value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".TargetHandleType",
+ target_handle_type_value);
+
+ dbus_g_type_struct_set (&monster,
+ 0, fixed_properties,
+ 1, text_allowed_properties,
+ G_MAXUINT);
+
+ g_hash_table_unref (fixed_properties);
+
+ g_ptr_array_add (arr, g_value_get_boxed (&monster));
+}
+
+static void
+salut_im_manager_get_contact_caps_from_set (
+ GabbleCapsChannelManager *iface G_GNUC_UNUSED,
+ TpHandle handle G_GNUC_UNUSED,
+ const GabbleCapabilitySet *set G_GNUC_UNUSED,
+ GPtrArray *arr)
+{
+ /* We don't need to check this contact's capabilities, we assume every
+ * contact support text channels. */
+ salut_im_manager_add_contact_caps (arr);
+}
+
+static void
+gabble_caps_channel_manager_iface_init (GabbleCapsChannelManagerIface *iface)
+{
+ iface->get_contact_caps = salut_im_manager_get_contact_caps_from_set;
+}
diff --git a/salut/src/im-manager.h b/salut/src/im-manager.h
new file mode 100644
index 000000000..de000038a
--- /dev/null
+++ b/salut/src/im-manager.h
@@ -0,0 +1,64 @@
+/*
+ * contact-manager.h - Header for SalutImManager
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_IM_MANAGER_H__
+#define __SALUT_IM_MANAGER_H__
+
+#include <glib-object.h>
+#include "contact-manager.h"
+#include "im-channel.h"
+
+#include <gibber/gibber-linklocal-transport.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutImManager SalutImManager;
+typedef struct _SalutImManagerClass SalutImManagerClass;
+
+struct _SalutImManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _SalutImManager {
+ GObject parent;
+};
+
+
+GType salut_im_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_IM_MANAGER \
+ (salut_im_manager_get_type ())
+#define SALUT_IM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_IM_MANAGER, SalutImManager))
+#define SALUT_IM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_IM_MANAGER, \
+ SalutImManagerClass))
+#define SALUT_IS_IM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_IM_MANAGER))
+#define SALUT_IS_IM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_IM_MANAGER))
+#define SALUT_IM_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_IM_MANAGER, \
+ SalutImManagerClass))
+
+SalutImManager *
+salut_im_manager_new (SalutConnection *connection,
+ SalutContactManager *contact_manager);
+
+#endif /* #ifndef __SALUT_IM_MANAGER_H__*/
diff --git a/salut/src/muc-channel.c b/salut/src/muc-channel.c
new file mode 100644
index 000000000..88b593f52
--- /dev/null
+++ b/salut/src/muc-channel.c
@@ -0,0 +1,1422 @@
+/*
+ * muc-channel.c - Source for SalutMucChannel
+ * Copyright (C) 2006,2010 Collabora Ltd.
+ * Copyright (C) 2006 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+/* Maximum time to wait for others joining the group */
+#define CONNECTED_TIMEOUT 60 * 1000
+
+#include "muc-channel.h"
+
+#include <wocky/wocky-namespaces.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/util.h>
+
+#include <gibber/gibber-muc-connection.h>
+#include <gibber/gibber-transport.h>
+
+#include "connection.h"
+#include "contact-manager.h"
+#include "self.h"
+#include "muc-manager.h"
+#include "util.h"
+
+#include "text-helper.h"
+#include "tube-stream.h"
+
+static void channel_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE(SalutMucChannel, salut_muc_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT,
+ tp_message_mixin_text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES,
+ tp_message_mixin_messages_iface_init);
+)
+
+static const char *salut_muc_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES,
+ NULL
+};
+
+/* signal enum */
+enum
+{
+ READY,
+ JOIN_ERROR,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_MUC_CONNECTION,
+ PROP_CONNECTION,
+ PROP_NAME,
+ PROP_CREATOR,
+ PROP_INTERFACES,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_CHANNEL_DESTROYED,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutMucChannelPrivate SalutMucChannelPrivate;
+
+struct _SalutMucChannelPrivate
+{
+ gboolean dispose_has_run;
+ gchar *object_path;
+ TpHandle handle;
+ SalutSelf *self;
+ GibberMucConnection *muc_connection;
+ gchar *muc_name;
+ gboolean connected;
+ gboolean creator;
+ guint timeout;
+ /* (gchar *) -> (SalutContact *) */
+ GHashTable *senders;
+ SalutMucManager *muc_manager;
+ TpHandle initiator;
+ gboolean requested;
+ gboolean closed;
+};
+
+#define SALUT_MUC_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_MUC_CHANNEL, SalutMucChannelPrivate))
+
+/* Callback functions */
+static gboolean salut_muc_channel_send_stanza (SalutMucChannel *self,
+ guint type,
+ const gchar *token,
+ const gchar *text,
+ WockyStanza *stanza,
+ GError **error);
+static void salut_muc_channel_received_stanza (GibberMucConnection *conn,
+ const gchar *sender,
+ WockyStanza *stanza,
+ gpointer user_data);
+static gboolean
+salut_muc_channel_connect (SalutMucChannel *channel, GError **error);
+static void salut_muc_channel_disconnected (GibberTransport *transport,
+ gpointer user_data);
+static void salut_muc_channel_send (GObject *channel,
+ TpMessage *message,
+ TpMessageSendingFlags flags);
+
+static void
+salut_muc_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutMucChannel *chan = SALUT_MUC_CHANNEL (object);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (chan);
+ TpBaseConnection *base_conn = (TpBaseConnection *) chan->connection;
+
+ switch (property_id) {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, priv->muc_name);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_ROOM);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, priv->handle);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, chan->connection);
+ break;
+ case PROP_MUC_CONNECTION:
+ g_value_set_object (value, priv->muc_connection);
+ break;
+ case PROP_CREATOR:
+ g_value_set_boolean (value, priv->creator);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_static_boxed (value, salut_muc_channel_interfaces);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_ROOM);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->handle));
+ }
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->initiator));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, priv->requested);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ /* TODO: this should be FALSE if there are still pending messages, so
+ * the channel manager can respawn the channel.
+ */
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessagePartSupportFlags",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "DeliveryReportingSupport",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "SupportedContentTypes",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessageTypes",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_muc_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutMucChannel *chan = SALUT_MUC_CHANNEL (object);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (chan);
+ const gchar *tmp;
+
+ switch (property_id) {
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_NAME:
+ g_free (priv->muc_name);
+ priv->muc_name = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_CONNECTION:
+ chan->connection = g_value_get_object (value);
+ break;
+ case PROP_MUC_CONNECTION:
+ priv->muc_connection = g_value_get_object (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ /* 0 is the old tp-glib value of unset, TP_UNKNOWN_HANDLE_TYPE is the
+ * new version */
+ g_assert (g_value_get_uint (value) == 0
+ || g_value_get_uint (value) == TP_HANDLE_TYPE_ROOM
+ || g_value_get_uint (value) == TP_UNKNOWN_HANDLE_TYPE);
+ break;
+ case PROP_CHANNEL_TYPE:
+ tmp = g_value_get_string (value);
+ g_assert (tmp == NULL
+ || !tp_strdiff (g_value_get_string (value),
+ TP_IFACE_CHANNEL_TYPE_TEXT));
+ break;
+ case PROP_CREATOR:
+ priv->creator = g_value_get_boolean (value);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_REQUESTED:
+ priv->requested = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_muc_channel_add_self_to_members (SalutMucChannel *self)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+ TpIntSet *empty;
+ TpIntSet *add;
+
+ priv->connected = TRUE;
+ g_signal_emit (self, signals[READY], 0);
+
+ if (priv->timeout != 0)
+ {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+
+ /* Now we are connected, move yourself to members */
+ empty = tp_intset_new ();
+ add = tp_intset_new ();
+ tp_intset_add (add, base_connection->self_handle);
+
+ tp_group_mixin_change_members (G_OBJECT (self),
+ "", add, empty, empty, empty, base_connection->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (empty);
+ tp_intset_destroy (add);
+}
+
+static gboolean
+connected_timeout_cb (gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ DEBUG ("Didn't receive muc senders. Timeout expired, "
+ "adding myself as member anyway");
+ salut_muc_channel_add_self_to_members (self);
+ priv->timeout = 0;
+
+ return FALSE;
+}
+
+static void
+muc_connection_connected_cb (GibberMucConnection *connection,
+ SalutMucChannel *self)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->creator)
+ {
+ DEBUG ("I created this muc. Adding myself as member now");
+ salut_muc_channel_add_self_to_members (self);
+ }
+ else
+ {
+ DEBUG ("I didn't create this muc. Waiting for senders before adding "
+ "myself as member");
+ priv->timeout = g_timeout_add (CONNECTED_TIMEOUT, connected_timeout_cb,
+ self);
+ }
+
+ salut_muc_channel_publish_service (self);
+}
+
+#define NUM_SUPPORTED_MESSAGE_TYPES 3
+
+static GObject *
+salut_muc_channel_constructor (GType type, guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ TpDBusDaemon *bus;
+ SalutMucChannel *self;
+ SalutMucChannelPrivate *priv;
+ TpBaseConnection *base_conn;
+ TpHandleRepoIface *handle_repo;
+ TpHandleRepoIface *contact_repo;
+ TpChannelTextMessageType types[NUM_SUPPORTED_MESSAGE_TYPES] = {
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
+ };
+ const gchar * supported_content_types[] = {
+ "text/plain",
+ NULL
+ };
+
+ /* Parent constructor chain */
+ obj = G_OBJECT_CLASS (salut_muc_channel_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_MUC_CHANNEL (obj);
+ priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ /* Ref our handle */
+ base_conn = TP_BASE_CONNECTION (self->connection);
+
+ handle_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_ROOM);
+
+ tp_handle_ref (handle_repo, priv->handle);
+
+ /* Message mixin initialisation */
+ tp_message_mixin_init (obj, G_STRUCT_OFFSET (SalutMucChannel, message_mixin),
+ base_conn);
+ tp_message_mixin_implement_sending (obj, salut_muc_channel_send,
+ NUM_SUPPORTED_MESSAGE_TYPES, types, 0,
+ TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES |
+ TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES,
+ supported_content_types);
+
+ g_object_get (self->connection, "self", &(priv->self), NULL);
+ g_object_unref (priv->self);
+ g_assert (priv->self != NULL);
+
+ g_assert (priv->muc_connection != NULL);
+
+ priv->connected = FALSE;
+ g_signal_connect (priv->muc_connection, "connected",
+ G_CALLBACK (muc_connection_connected_cb), obj);
+
+ g_object_get (self->connection,
+ "muc-manager", &(priv->muc_manager),
+ NULL);
+ g_assert (priv->muc_manager != NULL);
+
+ /* no need to keep a ref on the muc manager as it keeps a ref on us */
+ g_object_unref (priv->muc_manager);
+
+ /* Connect to the bus */
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+
+ contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ tp_group_mixin_init (obj, G_STRUCT_OFFSET(SalutMucChannel, group),
+ contact_repo, base_conn->self_handle);
+
+ tp_group_mixin_change_flags (obj,
+ TP_CHANNEL_GROUP_FLAG_PROPERTIES |
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD |
+ TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD,
+ 0);
+
+ return obj;
+}
+
+static void
+salut_muc_channel_init (SalutMucChannel *self)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ /* allocate any data required by the object here */
+ priv->object_path = NULL;
+ self->connection = NULL;
+ priv->muc_name = NULL;
+ priv->timeout = 0;
+ priv->senders = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) g_object_unref);
+}
+
+static void salut_muc_channel_dispose (GObject *object);
+static void salut_muc_channel_finalize (GObject *object);
+
+static void
+invitation_append_parameter (gpointer key, gpointer value, gpointer data)
+{
+ WockyNode *node = (WockyNode *) data;
+ wocky_node_add_child_with_content (node, (gchar *) key,
+ (gchar *) value);
+}
+
+static WockyStanza *
+create_invitation (SalutMucChannel *self, SalutContact *contact,
+ const gchar *message)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION(self->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ WockyStanza *msg;
+ WockyNode *invite_node;
+
+ msg = wocky_stanza_build_to_contact (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_NORMAL,
+ self->connection->name, WOCKY_CONTACT (contact),
+ '(', "body",
+ '$', "You got a Clique chatroom invitation",
+ ')',
+ '(', "invite",
+ '*', &invite_node,
+ ':', WOCKY_TELEPATHY_NS_CLIQUE,
+ '(', "roomname",
+ '$', tp_handle_inspect (room_repo, priv->handle),
+ ')',
+ ')',
+ NULL);
+
+ if (message != NULL && *message != '\0')
+ {
+ wocky_node_add_child_with_content (invite_node, "reason", message);
+ }
+
+ g_hash_table_foreach (
+ (GHashTable *) gibber_muc_connection_get_parameters (
+ priv->muc_connection),
+ invitation_append_parameter, invite_node);
+
+#ifdef ENABLE_OLPC
+ salut_self_olpc_augment_invitation (priv->self, priv->handle, contact->handle,
+ invite_node);
+#endif
+
+ return msg;
+}
+
+gboolean
+salut_muc_channel_publish_service (SalutMucChannel *self)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ return SALUT_MUC_CHANNEL_GET_CLASS (self)->publish_service (self,
+ priv->muc_connection, priv->muc_name);
+}
+
+typedef struct
+{
+ SalutMucChannel *self;
+ SalutContact *contact;
+} SendInviteData;
+
+static void
+send_invite_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ SendInviteData *data = user_data;
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (
+ data->self->connection);
+ GError *error = NULL;
+ TpHandle handle;
+ TpIntSet *empty, *removed;
+
+ if (wocky_porter_send_finish (porter, result, &error))
+ /* success */
+ goto out;
+
+ /* failure */
+ DEBUG ("Failed to send stanza: %s", error->message);
+ g_clear_error (&error);
+
+ handle = data->contact->handle;
+
+ empty = tp_intset_new ();
+ removed = tp_intset_new ();
+ tp_intset_add (removed, handle);
+
+ tp_group_mixin_change_members (G_OBJECT (data->self), "", empty, removed, empty,
+ empty, base_connection->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_ERROR);
+
+ tp_intset_destroy (empty);
+ tp_intset_destroy (removed);
+
+out:
+ g_object_unref (data->contact);
+ g_slice_free (SendInviteData, data);
+}
+
+gboolean
+salut_muc_channel_send_invitation (SalutMucChannel *self,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ WockyStanza *stanza;
+ SalutContactManager *contact_manager = NULL;
+ SalutContact *contact;
+ WockyPorter *porter = self->connection->porter;
+ SendInviteData *data;
+
+ g_object_get (G_OBJECT (self->connection), "contact-manager",
+ &contact_manager, NULL);
+ g_assert (contact_manager != NULL);
+
+ contact = salut_contact_manager_get_contact (contact_manager, handle);
+ g_object_unref (contact_manager);
+
+ if (contact == NULL)
+ {
+ *error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Couldn't contact the contact");
+ return FALSE;
+ }
+
+ DEBUG ("request XMPP connection with contact %s", contact->name);
+
+ stanza = create_invitation (self, contact, message);
+
+ data = g_slice_new0 (SendInviteData);
+ data->self = self;
+ data->contact = contact; /* steal the ref */
+
+ wocky_porter_send_async (porter, stanza, NULL, send_invite_cb, data);
+
+ g_object_unref (stanza);
+ return TRUE;
+}
+
+/* FIXME: This is an ugly workaround. See fd.o #15092
+ * We shouldn't export this function */
+gboolean
+salut_muc_channel_add_member (GObject *iface,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL(iface);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+ TpIntSet *empty, *remote_pending;
+
+ if (handle == base_connection->self_handle)
+ {
+ /* adding yourself, let's join the muc */
+ TpIntSet *empty_;
+ TpIntSet *add;
+ gboolean ret = TRUE;
+
+ if (tp_handle_set_is_member (self->group.remote_pending,
+ base_connection->self_handle))
+ {
+ /* Already in remote pending, no need to redo */
+ return TRUE;
+ }
+
+ empty_ = tp_intset_new ();
+ add = tp_intset_new ();
+ tp_intset_add (add, handle);
+ /* Add to members */
+
+ if (salut_muc_channel_connect (self, NULL))
+ {
+ /* We are considered as remote-pending while the muc connection
+ * is not connected */
+ tp_group_mixin_change_members (G_OBJECT (self),
+ message, empty_, empty_, empty_, add,
+ base_connection->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ }
+ else
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ "Failed to connect to the group");
+ ret = FALSE;
+ }
+
+ tp_intset_destroy (empty_);
+ tp_intset_destroy (add);
+ return ret;
+ }
+
+ /* Adding a contact, let's invite him */
+
+ DEBUG ("Trying to add handle %u to %s", handle, priv->object_path);
+
+ if (!salut_muc_channel_send_invitation (self, handle, message, error))
+ return FALSE;
+
+ /* Set the contact as remote pending */
+ empty = tp_intset_new ();
+ remote_pending = tp_intset_new ();
+ tp_intset_add (remote_pending, handle);
+ tp_group_mixin_change_members (G_OBJECT(self), "", empty, empty, empty,
+ remote_pending, base_connection->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
+ tp_intset_destroy (empty);
+ tp_intset_destroy (remote_pending);
+
+ return TRUE;
+}
+
+static void
+salut_muc_channel_leave (SalutMucChannel *self,
+ TpChannelGroupChangeReason reason,
+ const gchar *message)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->closed)
+ return;
+
+ if (priv->connected)
+ {
+ /* FIXME: send a part-message based on reason and message first,
+ * once we've defined how */
+
+ /* priv->closed will be set in salut_muc_channel_disconnected */
+ gibber_muc_connection_disconnect (priv->muc_connection);
+ }
+ else
+ {
+ priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+}
+
+static gboolean
+salut_muc_channel_remove_member_with_reason (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ guint reason,
+ GError **error)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (object);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+
+ if (handle != base_connection->self_handle)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Contacts cannot be kicked from Clique chatrooms");
+ return FALSE;
+ }
+
+ salut_muc_channel_leave (self, reason, message);
+ return TRUE;
+}
+
+static void
+salut_muc_channel_class_init (SalutMucChannelClass *salut_muc_channel_class) {
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_muc_channel_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "TargetID", "target-id", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (salut_muc_channel_class,
+ sizeof (SalutMucChannelPrivate));
+
+ object_class->dispose = salut_muc_channel_dispose;
+ object_class->finalize = salut_muc_channel_finalize;
+
+ object_class->constructor = salut_muc_channel_constructor;
+ object_class->get_property = salut_muc_channel_get_property;
+ object_class->set_property = salut_muc_channel_set_property;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+
+ param_spec = g_param_spec_string ("target-id", "Target JID",
+ "The string obtained by inspecting this channel's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact which invited us to the MUC, or ourselves if we joined of "
+ "our own accord",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator JID",
+ "The string obtained by inspecting this channel's initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_string ("name",
+ "Name of the muc group",
+ "The name of the muc group",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_NAME, param_spec);
+
+ param_spec = g_param_spec_object ("muc-connection",
+ "The GibberMucConnection",
+ "muc connection object",
+ GIBBER_TYPE_MUC_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_MUC_CONNECTION, param_spec);
+ param_spec = g_param_spec_object ("connection",
+ "SalutConnection object",
+ "Salut Connection that owns the"
+ "connection for this IM channel",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boolean (
+ "creator",
+ "creator",
+ "Whether or not we created this muc",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_CREATOR, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+
+ signals[READY] = g_signal_new (
+ "ready",
+ G_OBJECT_CLASS_TYPE (salut_muc_channel_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[JOIN_ERROR] = g_signal_new (
+ "join-error",
+ G_OBJECT_CLASS_TYPE (salut_muc_channel_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ salut_muc_channel_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutMucChannelClass, dbus_props_class));
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET(SalutMucChannelClass, group_class),
+ salut_muc_channel_add_member, NULL);
+ tp_group_mixin_init_dbus_properties (object_class);
+
+ tp_group_mixin_class_allow_self_removal (object_class);
+ tp_group_mixin_class_set_remove_with_reason_func (object_class,
+ salut_muc_channel_remove_member_with_reason);
+
+ tp_message_mixin_init_dbus_properties (object_class);
+}
+
+void
+salut_muc_channel_dispose (GObject *object)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (object);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ g_signal_handlers_disconnect_matched (priv->muc_connection,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+
+ if (priv->muc_connection != NULL)
+ {
+ g_object_unref (priv->muc_connection);
+ priv->muc_connection = NULL;
+ }
+
+ if (priv->timeout != 0)
+ {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+
+ if (priv->senders != NULL)
+ {
+ g_hash_table_unref (priv->senders);
+ priv->senders = NULL;
+ }
+
+ if (!priv->closed)
+ {
+ priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (salut_muc_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_muc_channel_parent_class)->dispose (object);
+}
+
+void
+salut_muc_channel_finalize (GObject *object)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (object);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+ g_free (priv->object_path);
+ g_free (priv->muc_name);
+
+ tp_group_mixin_finalize (object);
+ tp_message_mixin_finalize (object);
+
+ G_OBJECT_CLASS (salut_muc_channel_parent_class)->finalize (object);
+}
+
+gboolean
+salut_muc_channel_invited (SalutMucChannel *self, TpHandle inviter,
+ const gchar *stanza, GError **error)
+{
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ gboolean ret = TRUE;
+
+ /* Got invited to this muc channel */
+ DEBUG ("Got an invitation to %s from %s",
+ tp_handle_inspect (room_repo, priv->handle),
+ tp_handle_inspect (contact_repo, inviter));
+
+ /* If we are already a member, no further actions are needed */
+ if (tp_handle_set_is_member (self->group.members,
+ base_connection->self_handle)) {
+ return TRUE;
+ }
+
+ if (inviter == base_connection->self_handle)
+ {
+ /* Invited ourselves, go straight to members */
+ gboolean r;
+ GArray *members = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
+ g_array_append_val (members, base_connection->self_handle);
+ r = tp_group_mixin_add_members (G_OBJECT (self), members, "", error);
+ g_assert (r);
+ g_array_unref (members);
+ }
+ else
+ {
+ TpIntSet *empty = tp_intset_new ();
+ TpIntSet *local_pending = tp_intset_new ();
+
+ g_assert (stanza != NULL);
+
+ tp_intset_add (local_pending, base_connection->self_handle);
+ tp_group_mixin_change_members (G_OBJECT(self), stanza,
+ empty, empty,
+ local_pending, empty,
+ inviter,
+ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
+ tp_intset_destroy (local_pending);
+ tp_intset_destroy (empty);
+ }
+
+ return ret;
+}
+
+/* Private functions */
+static gboolean
+salut_muc_channel_send_stanza (SalutMucChannel *self, guint type,
+ const gchar *token,
+ const gchar *text,
+ WockyStanza *stanza,
+ GError **error)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (!gibber_muc_connection_send (priv->muc_connection, stanza, error)) {
+ text_helper_report_delivery_error (TP_SVC_CHANNEL (self),
+ TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN, time (NULL), type, token, text);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+salut_muc_channel_add_members (SalutMucChannel *self,
+ GArray *members)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpIntSet *empty, *changes;
+ guint i;
+ SalutContactManager *contact_mgr;
+
+ empty = tp_intset_new ();
+ changes = tp_intset_new ();
+
+ g_object_get (G_OBJECT (self->connection), "contact-manager",
+ &contact_mgr, NULL);
+ g_assert (contact_mgr != NULL);
+
+ for (i = 0; i < members->len; i++)
+ {
+ gchar *sender = g_array_index (members, gchar *, i);
+ SalutContact *contact;
+
+ contact = salut_contact_manager_ensure_contact (contact_mgr, sender);
+
+ g_hash_table_insert (priv->senders, sender, contact);
+ tp_intset_add (changes, contact->handle);
+ }
+
+ tp_group_mixin_change_members (G_OBJECT(self),
+ "",
+ changes,
+ empty,
+ empty, empty,
+ 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (changes);
+ tp_intset_destroy (empty);
+ g_object_unref (contact_mgr);
+}
+
+static void
+salut_muc_channel_remove_members (SalutMucChannel *self,
+ GArray *members)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection =
+ (TpBaseConnection *) (self->connection);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (base_connection, TP_HANDLE_TYPE_CONTACT);
+ TpIntSet *empty, *changes;
+ guint i;
+
+ empty = tp_intset_new ();
+ changes = tp_intset_new ();
+
+ for (i = 0; i < members->len; i++)
+ {
+ TpHandle handle;
+ gchar *sender = g_array_index (members, gchar *, i);
+
+ handle = tp_handle_lookup (contact_repo, sender, NULL, NULL);
+ if (handle == 0)
+ {
+ DEBUG ("Lost sender (%s), but unknown contact", sender);
+ continue;
+ }
+
+ g_hash_table_remove (priv->senders, sender);
+
+ tp_intset_add (changes, handle);
+ }
+
+ tp_group_mixin_change_members (G_OBJECT(self),
+ "",
+ empty,
+ changes,
+ empty, empty,
+ 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (changes);
+ tp_intset_destroy (empty);
+}
+
+static void
+salut_muc_channel_received_stanza (GibberMucConnection *conn,
+ const gchar *sender,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+ TpHandleRepoIface *contact_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+
+ const gchar *from, *to, *body, *body_offset;
+ TpChannelTextMessageType msgtype;
+ TpHandle from_handle;
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+ WockyNode *tubes_node;
+
+ to = wocky_node_get_attribute (node, "to");
+ if (strcmp (to, priv->muc_name)) {
+ DEBUG("Stanza to another muc group, discarding");
+ return;
+ }
+
+ from_handle = tp_handle_lookup (contact_repo, sender, NULL, NULL);
+ if (from_handle == 0)
+ {
+ /* FIXME, unknown contact.. Need some way to handle this safely,
+ * just adding the contact is somewhat scary */
+ DEBUG("Got stanza from unknown contact, discarding");
+ return;
+ }
+
+#ifdef ENABLE_OLPC
+ if (salut_connection_olpc_observe_muc_stanza (self->connection, priv->handle,
+ from_handle, stanza))
+ return;
+#endif
+
+ tubes_node = wocky_node_get_child_ns (node, "tubes",
+ WOCKY_TELEPATHY_NS_TUBES);
+ if (tubes_node != NULL)
+ {
+ SalutTubesChannel *tubes_chan;
+ GPtrArray *tubes;
+ guint i;
+ GHashTable *channels;
+ gboolean created;
+
+ tubes_chan = salut_muc_manager_ensure_tubes_channel (priv->muc_manager,
+ priv->handle, from_handle, &created);
+ g_assert (tubes_chan != NULL);
+
+ channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+
+ if (created)
+ {
+ g_hash_table_insert (channels, tubes_chan, NULL);
+ }
+
+ tubes = salut_tubes_channel_muc_message_received (tubes_chan, sender,
+ stanza);
+
+ for (i = 0; i < tubes->len; i++)
+ {
+ SalutTubeIface *tube;
+
+ tube = g_ptr_array_index (tubes, i);
+ g_hash_table_insert (channels, tube, NULL);
+ }
+
+ if (g_hash_table_size (channels) > 0)
+ {
+ tp_channel_manager_emit_new_channels (priv->muc_manager, channels);
+ }
+
+ g_object_unref (tubes_chan);
+ g_ptr_array_unref (tubes);
+ g_hash_table_unref (channels);
+ }
+
+ if (!text_helper_parse_incoming_message (stanza, &from, &msgtype,
+ &body, &body_offset))
+ {
+ DEBUG("Couldn't parse stanza");
+ return;
+ }
+
+ if (body == NULL)
+ {
+ DEBUG("Message with an empty body");
+ return;
+ }
+
+ /* FIXME validate the from and the to */
+ tp_message_mixin_take_received (G_OBJECT (self),
+ text_helper_create_received_message (base_connection, from_handle,
+ time (NULL), msgtype, body_offset));
+}
+
+static void
+salut_muc_channel_new_senders (GibberMucConnection *connection,
+ GArray *senders,
+ gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+
+ salut_muc_channel_add_members (self, senders);
+ if (!tp_handle_set_is_member (self->group.members,
+ base_connection->self_handle))
+ {
+ DEBUG ("Got new senders. Adding myself as member");
+ salut_muc_channel_add_self_to_members (self);
+ }
+}
+
+static void
+salut_muc_channel_lost_senders (GibberMucConnection *connection,
+ GArray *senders, gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+
+ salut_muc_channel_remove_members (self, senders);
+}
+
+static gboolean
+salut_muc_channel_connect (SalutMucChannel *channel,
+ GError **error)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (channel);
+
+ g_signal_connect (priv->muc_connection, "received-stanza",
+ G_CALLBACK (salut_muc_channel_received_stanza), channel);
+
+ g_signal_connect (priv->muc_connection, "disconnected",
+ G_CALLBACK (salut_muc_channel_disconnected), channel);
+
+ g_signal_connect (priv->muc_connection, "new-senders",
+ G_CALLBACK (salut_muc_channel_new_senders), channel);
+
+ g_signal_connect (priv->muc_connection, "lost-senders",
+ G_CALLBACK (salut_muc_channel_lost_senders), channel);
+
+ return gibber_muc_connection_connect (priv->muc_connection, error);
+}
+
+static void
+salut_muc_channel_disconnected (GibberTransport *transport, gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->timeout != 0)
+ {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+
+ if (!priv->connected)
+ {
+ /* FIXME the disconnected signal should give us an error */
+ GError error = { TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ "can't join the muc" };
+ g_signal_emit (self, signals[JOIN_ERROR], 0, &error);
+ }
+
+ if (priv->closed)
+ return;
+
+ priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+}
+
+/* channel interfaces */
+/**
+ * salut_muc_channel_get_interfaces
+ *
+ * Implements D-Bus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_muc_channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_muc_channel_interfaces);
+}
+
+
+/**
+ * salut_muc_channel_get_handle
+ *
+ * Implements D-Bus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_muc_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (iface);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_ROOM,
+ priv->handle);
+}
+/**
+ * salut_muc_channel_get_channel_type
+ *
+ * Implements D-Bus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_muc_channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+/**
+ * salut_muc_channel_close
+ *
+ * Implements D-Bus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_muc_channel_close (TpSvcChannel *iface, DBusGMethodInvocation *context)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (iface);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->closed)
+ {
+ GError already = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Channel already closed"};
+ DEBUG ("channel already closed");
+
+ dbus_g_method_return_error (context, &already);
+ return;
+ }
+
+ salut_muc_channel_leave (self, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, "");
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
+ klass, salut_muc_channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+salut_muc_channel_send (GObject *channel,
+ TpMessage *message,
+ TpMessageSendingFlags flags)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL(channel);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE(self);
+ GError *error = NULL;
+ WockyStanza *stanza = NULL;
+ guint type;
+ gchar *text = NULL;
+ gchar *token = NULL;
+
+ if (!text_helper_validate_tp_message (message, &type, &token, &text, &error))
+ goto error;
+
+ stanza = text_helper_create_message_groupchat (self->connection->name,
+ priv->muc_name, type, text, &error);
+
+ if (stanza == NULL)
+ goto error;
+
+ if (!salut_muc_channel_send_stanza (self, type, token,
+ text, stanza, &error))
+ goto error;
+
+ tp_message_mixin_sent (channel, message, 0, token, NULL);
+ g_free (token);
+ g_object_unref (G_OBJECT (stanza));
+ return;
+
+error:
+ if (stanza != NULL)
+ g_object_unref (G_OBJECT (stanza));
+ tp_message_mixin_sent (channel, message, 0, NULL, error);
+ g_error_free (error);
+ g_free (text);
+ g_free (token);
+ return;
+}
+
diff --git a/salut/src/muc-channel.h b/salut/src/muc-channel.h
new file mode 100644
index 000000000..902db5e54
--- /dev/null
+++ b/salut/src/muc-channel.h
@@ -0,0 +1,92 @@
+/*
+ * muc-channel.h - Header for SalutMucChannel
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_MUC_CHANNEL_H__
+#define __SALUT_MUC_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/group-mixin.h>
+#include <telepathy-glib/message-mixin.h>
+
+#include <gibber/gibber-muc-connection.h>
+
+#include "connection.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutMucChannel SalutMucChannel;
+typedef struct _SalutMucChannelClass SalutMucChannelClass;
+
+struct _SalutMucChannelClass {
+ GObjectClass parent_class;
+ TpGroupMixinClass group_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+
+ /* Virtual method */
+ gboolean (*publish_service) (SalutMucChannel *self,
+ GibberMucConnection *muc_connection, const gchar *muc_name);
+};
+
+struct _SalutMucChannel {
+ GObject parent;
+ TpGroupMixin group;
+ TpMessageMixin message_mixin;
+
+ /* private */
+ SalutConnection *connection;
+};
+
+GType salut_muc_channel_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_MUC_CHANNEL \
+ (salut_muc_channel_get_type ())
+#define SALUT_MUC_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_MUC_CHANNEL, SalutMucChannel))
+#define SALUT_MUC_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_MUC_CHANNEL, \
+ SalutMucChannelClass))
+#define SALUT_IS_MUC_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_MUC_CHANNEL))
+#define SALUT_IS_MUC_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_MUC_CHANNEL))
+#define SALUT_MUC_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_MUC_CHANNEL, SalutMucChannelClass))
+
+gboolean
+salut_muc_channel_invited (SalutMucChannel *self,
+ TpHandle invitor, const gchar *message,
+ GError **error);
+
+gboolean
+salut_muc_channel_send_invitation (SalutMucChannel *self,
+ TpHandle handle, const gchar *message, GError **error);
+
+gboolean salut_muc_channel_publish_service (SalutMucChannel *self);
+
+/* FIXME: This is an ugly workaround. See fd.o #15092
+ * We shouldn't export this function */
+gboolean salut_muc_channel_add_member (GObject *iface, TpHandle handle,
+ const gchar *message, GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_MUC_CHANNEL_H__*/
diff --git a/salut/src/muc-manager.c b/salut/src/muc-manager.c
new file mode 100644
index 000000000..3661bc092
--- /dev/null
+++ b/salut/src/muc-manager.c
@@ -0,0 +1,1205 @@
+/*
+ * muc-manager.c - Source for SalutMucManager
+ * Copyright (C) 2006 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "muc-manager.h"
+
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-xmpp-error.h>
+
+#include <gibber/gibber-muc-connection.h>
+
+#include <salut/caps-channel-manager.h>
+
+#include "muc-channel.h"
+#include "contact-manager.h"
+#include "tubes-channel.h"
+#include "roomlist-channel.h"
+#include "roomlist-manager.h"
+#include "discovery-client.h"
+#include "tube-stream.h"
+#include "tube-dbus.h"
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+static gboolean
+invite_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza, gpointer user_data);
+
+
+static void salut_muc_manager_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE(SalutMucManager, salut_muc_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ salut_muc_manager_iface_init);
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_CAPS_CHANNEL_MANAGER, NULL))
+
+/* properties */
+enum {
+ PROP_CONNECTION = 1,
+ LAST_PROP
+};
+
+/* private structure */
+typedef struct _SalutMucManagerPrivate SalutMucManagerPrivate;
+
+struct _SalutMucManagerPrivate
+{
+ SalutConnection *connection;
+ gulong status_changed_id;
+
+ guint invite_handler_id;
+
+ /* GUINT_TO_POINTER (room_handle) => (SalutMucChannel *) */
+ GHashTable *text_channels;
+ /* GUINT_TO_POINTER(room_handle) => (SalutTubesChannel *) */
+ GHashTable *tubes_channels;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_MUC_MANAGER_GET_PRIVATE(obj) \
+ ((SalutMucManagerPrivate *) ((SalutMucManager *) obj)->priv)
+
+static void
+salut_muc_manager_init (SalutMucManager *obj)
+{
+ SalutMucManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj,
+ SALUT_TYPE_MUC_MANAGER, SalutMucManagerPrivate);
+
+ obj->priv = priv;
+
+ priv->connection = NULL;
+
+ /* allocate any data required by the object here */
+ priv->text_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+ priv->tubes_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+}
+
+static void
+salut_muc_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutMucManager *self = SALUT_MUC_MANAGER (object);
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->connection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_muc_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutMucManager *self = SALUT_MUC_MANAGER (object);
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->connection = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_muc_manager_close_all (SalutMucManager *self)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+
+ DEBUG ("closing channels");
+
+ if (priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (priv->connection, priv->status_changed_id);
+ priv->status_changed_id = 0;
+ }
+
+ if (priv->text_channels)
+ {
+ GHashTable *tmp = priv->text_channels;
+ priv->text_channels = NULL;
+ g_hash_table_unref (tmp);
+ }
+
+ if (priv->tubes_channels != NULL)
+ {
+ GHashTable *tmp = priv->tubes_channels;
+ priv->tubes_channels = NULL;
+ g_hash_table_unref (tmp);
+ }
+}
+
+static void
+connection_status_changed_cb (SalutConnection *conn,
+ guint status,
+ guint reason,
+ SalutMucManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ salut_muc_manager_close_all (self);
+ break;
+ }
+}
+
+static GObject *
+salut_muc_manager_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutMucManagerPrivate *priv;
+ WockyPorter *porter;
+
+ obj = G_OBJECT_CLASS (salut_muc_manager_parent_class)->
+ constructor (type, n_props, props);
+
+ priv = SALUT_MUC_MANAGER_GET_PRIVATE (obj);
+
+ porter = priv->connection->porter;
+ priv->invite_handler_id = wocky_porter_register_handler_from_anyone (
+ porter, WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL + 1, /* so we get called before the IM manager */
+ invite_stanza_callback, obj,
+ '(', "invite",
+ ':', WOCKY_TELEPATHY_NS_CLIQUE,
+ ')', NULL);
+
+ priv->status_changed_id = g_signal_connect (priv->connection,
+ "status-changed", (GCallback) connection_status_changed_cb, obj);
+
+ return obj;
+}
+
+static void salut_muc_manager_dispose (GObject *object);
+
+static void
+salut_muc_manager_class_init (SalutMucManagerClass *salut_muc_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_muc_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_muc_manager_class,
+ sizeof (SalutMucManagerPrivate));
+
+ object_class->get_property = salut_muc_manager_get_property;
+ object_class->set_property = salut_muc_manager_set_property;
+
+ object_class->constructor = salut_muc_manager_constructor;
+ object_class->dispose = salut_muc_manager_dispose;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "The Salut Connection associated with this muc manager",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ param_spec);
+}
+
+void
+salut_muc_manager_dispose (GObject *object)
+{
+ SalutMucManager *self = SALUT_MUC_MANAGER (object);
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->connection->porter != NULL)
+ {
+ wocky_porter_unregister_handler (priv->connection->porter,
+ priv->invite_handler_id);
+ priv->invite_handler_id = 0;
+ }
+
+ salut_muc_manager_close_all (self);
+ g_assert (priv->text_channels == NULL);
+ g_assert (priv->tubes_channels == NULL);
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (salut_muc_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_muc_manager_parent_class)->dispose (object);
+}
+
+/* Channel Manager interface */
+
+struct _ForeachData
+{
+ TpExportableChannelFunc foreach;
+ gpointer user_data;
+};
+
+static void
+_foreach_slave (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ struct _ForeachData *data = (struct _ForeachData *) user_data;
+ TpExportableChannel *channel = TP_EXPORTABLE_CHANNEL (value);
+
+ data->foreach (channel, data->user_data);
+}
+
+static void
+salut_muc_manager_foreach_channel (TpChannelManager *iface,
+ TpExportableChannelFunc foreach,
+ gpointer user_data)
+{
+ SalutMucManager *fac = SALUT_MUC_MANAGER (iface);
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (fac);
+ struct _ForeachData data;
+ GHashTableIter iter;
+ gpointer value;
+
+ data.user_data = user_data;
+ data.foreach = foreach;
+
+ g_hash_table_foreach (priv->text_channels, _foreach_slave, &data);
+
+ g_hash_table_iter_init (&iter, priv->tubes_channels);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ TpExportableChannel *chan = TP_EXPORTABLE_CHANNEL (value);
+
+ /* Add channels of type Channel.Type.Tubes */
+ foreach (chan, user_data);
+
+ /* Add channels of type Channel.Type.{Stream|DBus}Tube which live in the
+ * SalutTubesChannel object */
+ salut_tubes_channel_foreach (SALUT_TUBES_CHANNEL (chan), foreach,
+ user_data);
+ }
+}
+
+static const gchar * const muc_channel_fixed_properties[] = {
+ TP_IFACE_CHANNEL ".ChannelType",
+ TP_IFACE_CHANNEL ".TargetHandleType",
+ NULL
+};
+
+static const gchar * const * muc_tubes_channel_fixed_properties =
+ muc_channel_fixed_properties;
+
+static const gchar * const muc_channel_allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ TP_IFACE_CHANNEL ".TargetID",
+ NULL
+};
+
+static const gchar * const * muc_tubes_channel_allowed_properties =
+ muc_channel_allowed_properties;
+
+
+static void
+salut_muc_manager_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ GValue *channel_type_value, *handle_type_value;
+
+ channel_type_value = tp_g_value_slice_new (G_TYPE_STRING);
+ /* no string value yet - we'll change it for each channel class */
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType",
+ channel_type_value);
+
+ handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_ROOM);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
+ handle_type_value);
+
+ /* org.freedesktop.Telepathy.Channel.Type.Text */
+ g_value_set_static_string (channel_type_value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ func (type, table, muc_channel_allowed_properties,
+ user_data);
+
+ /* org.freedesktop.Telepathy.Channel.Type.Tubes */
+ g_value_set_static_string (channel_type_value, TP_IFACE_CHANNEL_TYPE_TUBES);
+ func (type, table, muc_tubes_channel_allowed_properties,
+ user_data);
+
+ /* org.freedesktop.Telepathy.Channel.Type.StreamTube */
+ g_value_set_static_string (channel_type_value,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE);
+ func (type, table, salut_tube_stream_channel_get_allowed_properties (),
+ user_data);
+
+ /* Muc Channel.Type.DBusTube */
+ g_value_set_static_string (channel_type_value,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE);
+ func (type, table, salut_tube_dbus_channel_get_allowed_properties (),
+ user_data);
+
+ g_hash_table_unref (table);
+}
+
+
+static void
+muc_channel_closed_cb (SalutMucChannel *chan,
+ gpointer user_data)
+{
+ SalutMucManager *self = SALUT_MUC_MANAGER (user_data);
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ TpHandle handle;
+
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (priv->text_channels)
+ {
+ g_object_get (chan, "handle", &handle, NULL);
+ DEBUG ("Removing channel with handle %u", handle);
+
+ if (priv->tubes_channels != NULL)
+ {
+ SalutTubesChannel *tubes;
+
+ tubes = g_hash_table_lookup (priv->tubes_channels,
+ GUINT_TO_POINTER (handle));
+ if (tubes != NULL)
+ salut_tubes_channel_close (tubes);
+ }
+
+ g_hash_table_remove (priv->text_channels, GUINT_TO_POINTER (handle));
+ }
+}
+
+/**
+ * tubes_channel_closed_cb:
+ *
+ * Signal callback for when a tubes channel is closed. Removes the references
+ * that MucManager holds to them.
+ */
+static void
+tubes_channel_closed_cb (SalutTubesChannel *chan,
+ gpointer user_data)
+{
+ SalutMucManager *fac = SALUT_MUC_MANAGER (user_data);
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (fac);
+ TpHandle room_handle;
+
+ tp_channel_manager_emit_channel_closed_for_object (fac,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (priv->tubes_channels != NULL)
+ {
+ g_object_get (chan, "handle", &room_handle, NULL);
+
+ DEBUG ("removing MUC tubes channel with handle %u", room_handle);
+
+ g_hash_table_remove (priv->tubes_channels,
+ GUINT_TO_POINTER (room_handle));
+
+ /* The channel will probably reopen soon due to an incoming tube message,
+ * but closing the corresponding text channel would be too astonishing */
+ }
+}
+
+static GibberMucConnection *
+_get_connection (SalutMucManager *mgr,
+ const gchar *protocol,
+ GHashTable *parameters,
+ GError **error)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (mgr);
+
+ return gibber_muc_connection_new (priv->connection->name,
+ protocol, parameters, error);
+}
+
+static SalutMucChannel *
+salut_muc_manager_new_muc_channel (SalutMucManager *mgr,
+ TpHandle handle,
+ GibberMucConnection *connection,
+ TpHandle initiator,
+ gboolean new_connection,
+ gboolean requested)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE(mgr);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION(priv->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ SalutMucChannel *chan;
+ const gchar *name;
+ gchar *path = NULL;
+
+ g_assert (g_hash_table_lookup (priv->text_channels,
+ GUINT_TO_POINTER (handle)) == NULL);
+ DEBUG ("Requested channel for handle: %u", handle);
+
+ /* FIXME The name of the muc and the handle might need to be different at
+ * some point.. E.g. if two rooms are called the same */
+ name = tp_handle_inspect (room_repo, handle);
+ path = g_strdup_printf ("%s/MucChannel/%u", base_connection->object_path,
+ handle);
+
+ chan = SALUT_MUC_MANAGER_GET_CLASS (mgr)->create_muc_channel (mgr,
+ priv->connection, path, connection, handle, name, initiator,
+ new_connection, requested);
+ g_free (path);
+
+ g_signal_connect (chan, "closed", G_CALLBACK (muc_channel_closed_cb), mgr);
+
+ g_hash_table_insert (priv->text_channels, GUINT_TO_POINTER (handle), chan);
+
+ return chan;
+}
+
+/**
+ * new_tubes_channel:
+ *
+ * Creates the SalutTubesChannel object with the given parameters.
+ */
+static SalutTubesChannel *
+new_tubes_channel (SalutMucManager *self,
+ TpHandle room,
+ SalutMucChannel *muc,
+ TpHandle initiator,
+ gboolean requested)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *conn = (TpBaseConnection *) priv->connection;
+ SalutTubesChannel *chan;
+ char *object_path;
+
+ g_assert (g_hash_table_lookup (priv->tubes_channels,
+ GUINT_TO_POINTER (room)) == NULL);
+
+ object_path = g_strdup_printf ("%s/MucTubesChannel%u",
+ conn->object_path, room);
+
+ DEBUG ("creating new tubes chan, object path %s", object_path);
+
+ chan = g_object_new (SALUT_TYPE_TUBES_CHANNEL,
+ "connection", priv->connection,
+ "object-path", object_path,
+ "handle", room,
+ "handle-type", TP_HANDLE_TYPE_ROOM,
+ "muc", muc,
+ "initiator-handle", initiator,
+ "requested", requested,
+ NULL);
+
+ g_signal_connect (chan, "closed", (GCallback) tubes_channel_closed_cb, self);
+
+ g_hash_table_insert (priv->tubes_channels, GUINT_TO_POINTER (room), chan);
+
+ g_free (object_path);
+
+ return chan;
+}
+
+static SalutMucChannel *
+salut_muc_manager_request_new_muc_channel (SalutMucManager *mgr,
+ TpHandle handle,
+ gpointer request_token,
+ gboolean announce,
+ GError **error)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (mgr);
+ TpBaseConnection *base_connection = (TpBaseConnection *) (priv->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ GibberMucConnection *connection;
+ SalutMucChannel *text_chan;
+ GError *connection_error = NULL;
+ const gchar *room_name;
+ GHashTable *params = NULL;
+ gchar *address;
+ guint16 p;
+ gboolean r;
+ GSList *tokens = NULL;
+ SalutRoomlistManager *roomlist_manager;
+ gboolean requested;
+
+ g_object_get (priv->connection, "roomlist-manager", &roomlist_manager, NULL);
+
+ room_name = tp_handle_inspect (room_repo, handle);
+
+ if (SALUT_ROOMLIST_MANAGER_GET_CLASS (roomlist_manager)->find_muc_address
+ (roomlist_manager, room_name, &address, &p))
+ {
+ /* This MUC already exists on the network, so we reuse its
+ * address */
+ gchar *port = g_strdup_printf ("%u", p);
+
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ g_free);
+ g_hash_table_insert (params, "address", address);
+ g_hash_table_insert (params, "port", port);
+ DEBUG ("found %s port %s for room %s", address, port,
+ room_name);
+ }
+ else
+ {
+ DEBUG ("Didn't find address for room %s, let's generate one", room_name);
+ }
+ g_object_unref (roomlist_manager);
+
+ connection = _get_connection (mgr, NULL, params, &connection_error);
+
+ if (params != NULL)
+ g_hash_table_unref (params);
+
+ if (connection == NULL)
+ {
+ DEBUG ("get connection failed: %s", connection_error->message);
+ if (error != NULL)
+ *error = g_error_new_literal (TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ connection_error->message);
+ g_error_free (connection_error);
+ return NULL;
+ }
+
+ /* We requested the channel, so invite ourselves to it */
+ if (!gibber_muc_connection_connect (connection, &connection_error))
+ {
+ DEBUG ("Connect failed: %s", connection_error->message);
+ if (error != NULL)
+ *error = g_error_new_literal (TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ connection_error->message);
+ g_error_free (connection_error);
+ g_object_unref (connection);
+ return NULL;
+ }
+ DEBUG ("Connect succeeded");
+
+ requested = (request_token != NULL);
+
+ text_chan = salut_muc_manager_new_muc_channel (mgr, handle,
+ connection, base_connection->self_handle, params == NULL,
+ requested);
+ r = salut_muc_channel_invited (text_chan,
+ base_connection->self_handle, NULL, NULL);
+ /* Inviting ourselves to a connected channel should always
+ * succeed */
+ g_assert (r);
+
+ if (request_token != NULL)
+ tokens = g_slist_prepend (tokens, request_token);
+
+ if (announce)
+ {
+ tp_channel_manager_emit_new_channel (mgr,
+ TP_EXPORTABLE_CHANNEL (text_chan), tokens);
+ }
+
+ g_slist_free (tokens);
+
+ return text_chan;
+}
+
+static SalutTubesChannel *
+create_tubes_channel (SalutMucManager *self,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token,
+ gboolean announce,
+ gboolean *text_created_out,
+ gboolean requested,
+ GError **error)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ SalutMucChannel *text_chan;
+ SalutTubesChannel *tubes_chan;
+ gboolean text_created = FALSE;
+
+ text_chan = g_hash_table_lookup (priv->text_channels,
+ GUINT_TO_POINTER (handle));
+
+ if (text_chan == NULL)
+ {
+ DEBUG ("have to create the text channel before the tubes one");
+ text_chan = salut_muc_manager_request_new_muc_channel (self,
+ handle, NULL, FALSE, error);
+
+ if (text_chan == NULL)
+ return NULL;
+
+ text_created = TRUE;
+ }
+
+ tubes_chan = new_tubes_channel (self, handle, text_chan, initiator,
+ requested);
+ g_assert (tubes_chan != NULL);
+
+ if (announce)
+ {
+ GHashTable *channels;
+ GSList *tokens = NULL;
+
+ if (request_token != NULL)
+ tokens = g_slist_prepend (tokens, request_token);
+
+ /* announce channels */
+ channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+
+ if (text_created)
+ {
+ g_hash_table_insert (channels, text_chan, NULL);
+ }
+
+ g_hash_table_insert (channels, tubes_chan, tokens);
+ tp_channel_manager_emit_new_channels (self, channels);
+
+ g_hash_table_unref (channels);
+ g_slist_free (tokens);
+ }
+
+ if (text_created_out != NULL)
+ *text_created_out = text_created;
+
+ return tubes_chan;
+}
+
+static gboolean
+handle_tube_channel_request (SalutMucManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new,
+ TpHandle handle,
+ GError **error)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) priv->connection;
+ SalutMucChannel *text_chan;
+ SalutTubesChannel *tubes_chan;
+ SalutTubeIface *new_channel;
+ GHashTable *channels;
+ GSList *request_tokens;
+ gboolean announce_text = FALSE, announce_tubes = FALSE;
+
+ tubes_chan = g_hash_table_lookup (priv->tubes_channels,
+ GUINT_TO_POINTER (handle));
+ if (tubes_chan == NULL)
+ {
+ tubes_chan = create_tubes_channel (self, handle,
+ base_conn->self_handle, NULL, FALSE, &announce_text,
+ FALSE, error);
+ if (tubes_chan == NULL)
+ return FALSE;
+ announce_tubes = TRUE;
+ }
+
+ g_assert (tubes_chan != NULL);
+ new_channel = salut_tubes_channel_tube_request (tubes_chan, request_token,
+ request_properties, require_new);
+ g_assert (new_channel != NULL);
+
+ /* announce channels */
+ channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+
+ if (announce_text)
+ {
+ text_chan = g_hash_table_lookup (priv->text_channels,
+ GINT_TO_POINTER (handle));
+ g_assert (text_chan != NULL);
+ g_hash_table_insert (channels, text_chan, NULL);
+ }
+
+ if (announce_tubes)
+ {
+ g_hash_table_insert (channels, tubes_chan, NULL);
+ }
+
+ request_tokens = g_slist_prepend (NULL, request_token);
+ g_hash_table_insert (channels, new_channel, request_tokens);
+ tp_channel_manager_emit_new_channels (self, channels);
+
+ g_hash_table_unref (channels);
+ g_slist_free (request_tokens);
+ return TRUE;
+}
+
+static gboolean
+handle_stream_tube_channel_request (SalutMucManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new,
+ TpHandle handle,
+ GError **error)
+{
+ const gchar *service;
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ muc_tubes_channel_fixed_properties,
+ salut_tube_stream_channel_get_allowed_properties (),
+ error))
+ return FALSE;
+
+ /* "Service" is a mandatory, not-fixed property */
+ service = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service");
+ if (service == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Request does not contain the mandatory property '%s'",
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service");
+ return FALSE;
+ }
+
+ return handle_tube_channel_request (self, request_token, request_properties,
+ require_new, handle, error);
+}
+
+static gboolean
+handle_dbus_tube_channel_request (SalutMucManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new,
+ TpHandle handle,
+ GError **error)
+{
+ const gchar *service;
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ muc_tubes_channel_fixed_properties,
+ salut_tube_dbus_channel_get_allowed_properties (),
+ error))
+ return FALSE;
+
+ /* "ServiceName" is a mandatory, not-fixed property */
+ service = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName");
+ if (service == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Request does not contain the mandatory property '%s'",
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName");
+ return FALSE;
+ }
+
+ return handle_tube_channel_request (self, request_token, request_properties,
+ require_new, handle, error);
+}
+
+static gboolean
+salut_muc_manager_request (SalutMucManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) priv->connection;
+ GError *error = NULL;
+ TpHandle handle;
+ const gchar *channel_type;
+ SalutMucChannel *text_chan;
+ SalutTubesChannel *tubes_chan;
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_ROOM)
+ return FALSE;
+
+ channel_type = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType");
+
+ if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT) &&
+ tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES) &&
+ tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE) &&
+ tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE))
+ return FALSE;
+
+ /* validity already checked by TpBaseConnection */
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandle", NULL);
+ g_assert (handle != 0);
+
+ if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT))
+ {
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ muc_channel_fixed_properties, muc_channel_allowed_properties,
+ &error))
+ goto error;
+
+ text_chan = g_hash_table_lookup (priv->text_channels,
+ GINT_TO_POINTER (handle));
+
+ if (text_chan != NULL)
+ {
+ if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "That channel has already been created (or requested)");
+ goto error;
+ }
+ else
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (text_chan));
+ }
+ }
+ else
+ {
+ text_chan = salut_muc_manager_request_new_muc_channel (self,
+ handle, request_token, TRUE, NULL);
+ }
+
+ return TRUE;
+ }
+ else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES))
+ {
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ muc_tubes_channel_fixed_properties,
+ muc_tubes_channel_allowed_properties,
+ &error))
+ goto error;
+
+ tubes_chan = g_hash_table_lookup (priv->tubes_channels,
+ GUINT_TO_POINTER (handle));
+
+ if (tubes_chan != NULL)
+ {
+ if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "That channel has already been created (or requested)");
+ goto error;
+ }
+ else
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (tubes_chan));
+ }
+ }
+ else
+ {
+ tubes_chan = create_tubes_channel (self, handle,
+ base_conn->self_handle, request_token, TRUE, NULL, TRUE, &error);
+ if (tubes_chan == NULL)
+ goto error;
+ }
+
+ return TRUE;
+ }
+ else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
+ {
+ if (handle_stream_tube_channel_request (self, request_token,
+ request_properties, require_new, handle, &error))
+ return TRUE;
+ }
+ else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE))
+ {
+ if (handle_dbus_tube_channel_request (self, request_token,
+ request_properties, require_new, handle, &error))
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+salut_muc_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutMucManager *self = SALUT_MUC_MANAGER (manager);
+
+ return salut_muc_manager_request (self, request_token, request_properties,
+ TRUE);
+}
+
+
+static gboolean
+salut_muc_manager_request_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutMucManager *self = SALUT_MUC_MANAGER (manager);
+
+ return salut_muc_manager_request (self, request_token, request_properties,
+ FALSE);
+}
+
+
+static gboolean
+salut_muc_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutMucManager *self = SALUT_MUC_MANAGER (manager);
+
+ return salut_muc_manager_request (self, request_token, request_properties,
+ FALSE);
+}
+
+
+static void salut_muc_manager_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = salut_muc_manager_foreach_channel;
+ iface->type_foreach_channel_class =
+ salut_muc_manager_type_foreach_channel_class;
+ iface->request_channel = salut_muc_manager_request_channel;
+ iface->create_channel = salut_muc_manager_create_channel;
+ iface->ensure_channel = salut_muc_manager_ensure_channel;
+}
+
+static gboolean
+invite_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutMucManager *self = SALUT_MUC_MANAGER (user_data);
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (priv->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ TpHandleRepoIface *contact_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+ WockyNode *invite, *room_node, *reason_node;
+ SalutMucChannel *chan;
+ const gchar *room = NULL;
+ const gchar *reason = NULL;
+ const gchar **params;
+ TpHandle room_handle;
+ TpHandle inviter_handle;
+ const gchar **p;
+ GHashTable *params_hash;
+ GibberMucConnection *connection = NULL;
+ SalutContact *contact = SALUT_CONTACT (wocky_stanza_get_from_contact (stanza));
+
+ invite = wocky_node_get_child_ns (wocky_stanza_get_top_node (stanza),
+ "invite", WOCKY_TELEPATHY_NS_CLIQUE);
+ g_assert (invite != NULL);
+
+ DEBUG("Got an invitation");
+
+ room_node = wocky_node_get_child (invite, "roomname");
+ if (room_node == NULL)
+ {
+ DEBUG ("Invalid invitation, discarding");
+ return TRUE;
+ }
+ room = room_node->content;
+
+ reason_node = wocky_node_get_child (invite, "reason");
+ if (reason_node != NULL)
+ reason = reason_node->content;
+
+ if (reason == NULL)
+ reason = "";
+
+ params = gibber_muc_connection_get_required_parameters (
+ WOCKY_TELEPATHY_NS_CLIQUE);
+ if (params == NULL)
+ {
+ DEBUG ("Invalid invitation, (unknown protocol) discarding");
+ return TRUE;
+ }
+
+ params_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+ for (p = params ; *p != NULL; p++)
+ {
+ WockyNode *param;
+
+ param = wocky_node_get_child (invite, *p);
+ if (param == NULL)
+ {
+ DEBUG("Invalid invitation, (missing parameter) discarding");
+ goto discard;
+ }
+
+ g_hash_table_insert (params_hash, (gchar *) *p,
+ g_strdup (param->content));
+ }
+
+ /* FIXME proper serialisation of handle name */
+ /* Create the group if it doesn't exist and myself to local_pending */
+ room_handle = tp_handle_ensure (room_repo, room, NULL, NULL);
+
+ /* FIXME handle properly */
+ g_assert (room_handle != 0);
+
+ chan = g_hash_table_lookup (priv->text_channels,
+ GINT_TO_POINTER (room_handle));
+
+ inviter_handle = tp_handle_ensure (contact_repo, contact->name, NULL, NULL);
+
+ if (chan == NULL)
+ {
+ connection = _get_connection (self, WOCKY_TELEPATHY_NS_CLIQUE,
+ params_hash, NULL);
+ if (connection == NULL)
+ {
+ DEBUG ("Invalid invitation, (wrong protocol parameters) discarding");
+ goto discard;
+ }
+
+ if (connection == NULL)
+ {
+ tp_handle_unref (room_repo, room_handle);
+ /* FIXME some kinda error to the user maybe ? Ignore for now */
+ goto discard;
+ }
+ /* Need to create a new one */
+ chan = salut_muc_manager_new_muc_channel (self, room_handle,
+ connection, inviter_handle, FALSE, FALSE);
+
+ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
+ NULL);
+ }
+
+ /* FIXME handle properly */
+ g_assert (chan != NULL);
+
+#ifdef ENABLE_OLPC
+ salut_connection_olpc_observe_invitation (priv->connection, room_handle,
+ inviter_handle, invite);
+#endif
+
+ salut_muc_channel_invited (chan, inviter_handle, reason, NULL);
+ tp_handle_unref (contact_repo, inviter_handle);
+
+ return TRUE;
+
+discard:
+ if (params_hash != NULL)
+ g_hash_table_unref (params_hash);
+ return TRUE;
+}
+
+/* public functions */
+
+SalutMucChannel *
+salut_muc_manager_get_text_channel (SalutMucManager *self,
+ TpHandle handle)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ SalutMucChannel *muc;
+
+ if (priv->text_channels == NULL)
+ return NULL;
+
+ muc = g_hash_table_lookup (priv->text_channels, GUINT_TO_POINTER (handle));
+ if (muc == NULL)
+ return NULL;
+
+ g_object_ref (muc);
+ return muc;
+}
+
+void
+salut_muc_manager_handle_si_stream_request (SalutMucManager *self,
+ GibberBytestreamIface *bytestream,
+ TpHandle room_handle,
+ const gchar *stream_id,
+ WockyStanza *msg)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->connection, TP_HANDLE_TYPE_ROOM);
+ SalutTubesChannel *chan = NULL;
+
+ g_return_if_fail (tp_handle_is_valid (room_repo, room_handle, NULL));
+
+ chan = g_hash_table_lookup (priv->tubes_channels,
+ GUINT_TO_POINTER (room_handle));
+ if (chan == NULL)
+ {
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "No tubes channel available for this MUC" };
+
+ DEBUG ("tubes channel doesn't exist for muc %d", room_handle);
+ gibber_bytestream_iface_close (bytestream, &e);
+ return;
+ }
+
+ salut_tubes_channel_bytestream_offered (chan, bytestream, msg);
+}
+
+/* Caller is reponsible of announcing the channel if created */
+SalutTubesChannel *
+salut_muc_manager_ensure_tubes_channel (SalutMucManager *self,
+ TpHandle handle,
+ TpHandle actor,
+ gboolean *created)
+{
+ SalutMucManagerPrivate *priv = SALUT_MUC_MANAGER_GET_PRIVATE (self);
+ SalutTubesChannel *tubes_chan;
+
+ tubes_chan = g_hash_table_lookup (priv->tubes_channels,
+ GUINT_TO_POINTER (handle));
+ if (tubes_chan != NULL)
+ {
+ g_object_ref (tubes_chan);
+ *created = FALSE;
+ return tubes_chan;
+ }
+
+
+ tubes_chan = create_tubes_channel (self, handle, actor, NULL, FALSE, NULL,
+ FALSE, NULL);
+ g_assert (tubes_chan != NULL);
+ g_object_ref (tubes_chan);
+
+ *created = TRUE;
+ return tubes_chan;
+}
diff --git a/salut/src/muc-manager.h b/salut/src/muc-manager.h
new file mode 100644
index 000000000..2e761a8de
--- /dev/null
+++ b/salut/src/muc-manager.h
@@ -0,0 +1,84 @@
+/*
+ * muc-manager.h - Header for SalutMucManager
+ * Copyright (C) 2006 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_MUC_MANAGER_H__
+#define __SALUT_MUC_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <gibber/gibber-bytestream-iface.h>
+
+#include <connection.h>
+#include "muc-channel.h"
+#include "tubes-channel.h"
+#include "muc-channel.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutMucManager SalutMucManager;
+typedef struct _SalutMucManagerClass SalutMucManagerClass;
+
+struct _SalutMucManagerClass {
+ GObjectClass parent_class;
+
+ SalutMucChannel * (*create_muc_channel) (SalutMucManager *self,
+ SalutConnection *connection, const gchar *path,
+ GibberMucConnection *muc_connection, TpHandle handle,
+ const gchar *name, TpHandle initiator, gboolean creator,
+ gboolean requested);
+};
+
+struct _SalutMucManager {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType salut_muc_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_MUC_MANAGER \
+ (salut_muc_manager_get_type ())
+#define SALUT_MUC_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_MUC_MANAGER, SalutMucManager))
+#define SALUT_MUC_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_MUC_MANAGER, SalutMucManagerClass))
+#define SALUT_IS_MUC_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_MUC_MANAGER))
+#define SALUT_IS_MUC_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_MUC_MANAGER))
+#define SALUT_MUC_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_MUC_MANAGER, SalutMucManagerClass))
+
+SalutMucChannel *
+salut_muc_manager_get_text_channel (SalutMucManager *muc_manager,
+ TpHandle handle);
+
+void salut_muc_manager_handle_si_stream_request (SalutMucManager *muc_manager,
+ GibberBytestreamIface *bytestream, TpHandle room_handle,
+ const gchar *stream_id, WockyStanza *msg);
+
+SalutTubesChannel * salut_muc_manager_ensure_tubes_channel (
+ SalutMucManager *muc_manager, TpHandle handle, TpHandle actor,
+ gboolean *created);
+
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_MUC_MANAGER_H__*/
diff --git a/salut/src/namespaces.h b/salut/src/namespaces.h
new file mode 100644
index 000000000..6f9b75b85
--- /dev/null
+++ b/salut/src/namespaces.h
@@ -0,0 +1,123 @@
+/*
+ * namespaces.h - XMPP namespace constants
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GABBLE_NAMESPACES__H__
+#define __GABBLE_NAMESPACES__H__
+
+#include "gabble_namespaces.h"
+#include <wocky/wocky-namespaces.h>
+
+#define NS_AMP WOCKY_XMPP_NS_AMP
+#define NS_BYTESTREAMS "http://jabber.org/protocol/bytestreams"
+#define NS_CHAT_STATES WOCKY_NS_CHATSTATE
+#define NS_FEATURENEG WOCKY_XMPP_NS_FEATURENEG
+#define NS_FILE_TRANSFER "http://jabber.org/protocol/si/profile/file-transfer"
+#define NS_GOOGLE_CAPS "http://www.google.com/xmpp/client/caps"
+#define NS_GOOGLE_FEAT_SESSION "http://www.google.com/xmpp/protocol/session"
+#define NS_GOOGLE_FEAT_SHARE "http://google.com/xmpp/protocol/share/v1"
+#define NS_GOOGLE_FEAT_VOICE "http://www.google.com/xmpp/protocol/voice/v1"
+#define NS_GOOGLE_FEAT_VIDEO "http://www.google.com/xmpp/protocol/video/v1"
+#define NS_GOOGLE_JINGLE_INFO "google:jingleinfo"
+#define NS_GOOGLE_ROSTER "google:roster"
+#define NS_GOOGLE_QUEUE "google:queue"
+#define NS_IBB WOCKY_XMPP_NS_IBB
+
+/* Namespaces for XEP-0166 draft v0.15, the most capable Jingle dialect
+ * supported by telepathy-gabble < 0.7.16, including the versions shipped with
+ * Maemo Chinook and Diablo.
+ */
+#define NS_JINGLE015 "http://jabber.org/protocol/jingle"
+
+/* RTP audio capability in Jingle v0.15 (obsoleted by NS_JINGLE_RTP) */
+#define NS_JINGLE_DESCRIPTION_AUDIO \
+ "http://jabber.org/protocol/jingle/description/audio"
+/* RTP video capability in Jingle v0.15 (obsoleted by NS_JINGLE_RTP) */
+#define NS_JINGLE_DESCRIPTION_VIDEO \
+ "http://jabber.org/protocol/jingle/description/video"
+
+/* XEP-0166 draft */
+#define NS_JINGLE032 "urn:xmpp:jingle:1"
+#define NS_JINGLE_ERRORS "urn:xmpp:jingle:errors:1"
+
+/* XEP-0167 (Jingle RTP) */
+#define NS_JINGLE_RTP "urn:xmpp:jingle:apps:rtp:1"
+#define NS_JINGLE_RTP_ERRORS "urn:xmpp:jingle:apps:rtp:errors:1"
+#define NS_JINGLE_RTP_INFO "urn:xmpp:jingle:apps:rtp:info:1"
+#define NS_JINGLE_RTP_AUDIO "urn:xmpp:jingle:apps:rtp:audio"
+#define NS_JINGLE_RTP_VIDEO "urn:xmpp:jingle:apps:rtp:video"
+
+/* Google's Jingle dialect */
+#define NS_GOOGLE_SESSION "http://www.google.com/session"
+/* Audio capability in Google Jingle dialect */
+#define NS_GOOGLE_SESSION_PHONE "http://www.google.com/session/phone"
+/* Video capability in Google's Jingle dialect */
+#define NS_GOOGLE_SESSION_VIDEO "http://www.google.com/session/video"
+/* File transfer capability in Google's Jingle dialect */
+#define NS_GOOGLE_SESSION_SHARE "http://www.google.com/session/share"
+
+/* google-p2p transport */
+#define NS_GOOGLE_TRANSPORT_P2P "http://www.google.com/transport/p2p"
+/* Jingle RAW-UDP transport */
+#define NS_JINGLE_TRANSPORT_RAWUDP "urn:xmpp:jingle:transports:raw-udp:1"
+/* Jingle ICE-UDP transport */
+#define NS_JINGLE_TRANSPORT_ICEUDP "urn:xmpp:jingle:transports:ice-udp:1"
+
+#define NS_MUC "http://jabber.org/protocol/muc"
+#define NS_MUC_BYTESTREAM "http://telepathy.freedesktop.org/xmpp/protocol/muc-bytestream"
+#define NS_MUC_USER "http://jabber.org/protocol/muc#user"
+#define NS_MUC_ADMIN "http://jabber.org/protocol/muc#admin"
+#define NS_MUC_OWNER "http://jabber.org/protocol/muc#owner"
+#define NS_NICK "http://jabber.org/protocol/nick"
+#define NS_OOB "jabber:iq:oob"
+#define NS_OLPC_BUDDY_PROPS "http://laptop.org/xmpp/buddy-properties"
+#define NS_OLPC_ACTIVITIES "http://laptop.org/xmpp/activities"
+#define NS_OLPC_CURRENT_ACTIVITY "http://laptop.org/xmpp/current-activity"
+#define NS_OLPC_ACTIVITY_PROPS "http://laptop.org/xmpp/activity-properties"
+#define NS_OLPC_BUDDY "http://laptop.org/xmpp/buddy"
+#define NS_OLPC_ACTIVITY "http://laptop.org/xmpp/activity"
+#define NS_PUBSUB "http://jabber.org/protocol/pubsub"
+#define NS_PRESENCE_INVISIBLE "presence-invisible"
+#define NS_PRIVACY "jabber:iq:privacy"
+#define NS_INVISIBLE "urn:xmpp:invisible:0"
+#define NS_REGISTER "jabber:iq:register"
+#define NS_ROSTER "jabber:iq:roster"
+#define NS_SEARCH "jabber:iq:search"
+#define NS_SI "http://jabber.org/protocol/si"
+#define NS_SI_MULTIPLE "http://telepathy.freedesktop.org/xmpp/si-multiple"
+#define NS_TUBES "http://telepathy.freedesktop.org/xmpp/tubes"
+#define NS_MUJI "http://telepathy.freedesktop.org/xmpp/muji"
+#define NS_VCARD_TEMP "vcard-temp"
+#define NS_VCARD_TEMP_UPDATE "vcard-temp:x:update"
+#define NS_X_DATA "jabber:x:data"
+#define NS_X_DELAY "jabber:x:delay"
+#define NS_X_CONFERENCE "jabber:x:conference"
+#define NS_XMPP_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas"
+#define NS_GEOLOC "http://jabber.org/protocol/geoloc"
+#define NS_GOOGLE_MAIL_NOTIFY "google:mail:notify"
+
+#define NS_TEMPPRES "urn:xmpp:temppres:0"
+#define NS_GOOGLE_SHARED_STATUS "google:shared-status"
+
+#define NS_OLPC_ACTIVITY_PROPS "http://laptop.org/xmpp/activity-properties"
+
+#define NS_TP_FT_METADATA_SERVICE "http://telepathy.freedesktop.org/xmpp/file-transfer-service"
+#define NS_TP_FT_METADATA "http://telepathy.freedesktop.org/xmpp/file-transfer-metadata"
+
+#endif /* __GABBLE_NAMESPACES__H__ */
diff --git a/salut/src/olpc-activity-manager.c b/salut/src/olpc-activity-manager.c
new file mode 100644
index 000000000..1ee3d87e1
--- /dev/null
+++ b/salut/src/olpc-activity-manager.c
@@ -0,0 +1,350 @@
+/*
+ * olpc-activity-manager.c - Source for SalutOlpcActivityManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "olpc-activity-manager.h"
+
+#include "connection.h"
+#include "signals-marshal.h"
+
+#define DEBUG_FLAG DEBUG_OLPC_ACTIVITY
+#include "debug.h"
+
+G_DEFINE_TYPE (SalutOlpcActivityManager, salut_olpc_activity_manager,
+ G_TYPE_OBJECT);
+
+/* properties */
+enum {
+ PROP_CONNECTION = 1,
+ LAST_PROP
+};
+
+/* signal enum */
+enum
+{
+ ACTIVITY_MODIFIED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+typedef struct _SalutOlpcActivityManagerPrivate SalutOlpcActivityManagerPrivate;
+
+struct _SalutOlpcActivityManagerPrivate
+{
+ /* TpHandle (owned by the activity) => SalutOlpcActivity */
+ GHashTable *activities_by_room;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_OLPC_ACTIVITY_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_OLPC_ACTIVITY_MANAGER, SalutOlpcActivityManagerPrivate))
+
+static void
+salut_olpc_activity_manager_init (SalutOlpcActivityManager *self)
+{
+ SalutOlpcActivityManagerPrivate *priv =
+ SALUT_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+ /* We just keep a weak reference on the activity object so we'll remove
+ * it from the hash when no one is using anymore */
+ priv->activities_by_room = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, NULL);
+}
+
+static void
+salut_olpc_activity_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutOlpcActivityManager *self = SALUT_OLPC_ACTIVITY_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->connection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_olpc_activity_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutOlpcActivityManager *self = SALUT_OLPC_ACTIVITY_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ self->connection = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void salut_olpc_activity_manager_dispose (GObject *object);
+static void salut_olpc_activity_manager_finalize (GObject *object);
+
+static void
+salut_olpc_activity_manager_class_init (SalutOlpcActivityManagerClass *salut_olpc_activity_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_olpc_activity_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_olpc_activity_manager_class,
+ sizeof (SalutOlpcActivityManagerPrivate));
+
+ object_class->get_property = salut_olpc_activity_manager_get_property;
+ object_class->set_property = salut_olpc_activity_manager_set_property;
+
+ object_class->dispose = salut_olpc_activity_manager_dispose;
+ object_class->finalize = salut_olpc_activity_manager_finalize;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "The Salut Connection associated with this muc manager",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ param_spec);
+
+ signals[ACTIVITY_MODIFIED] = g_signal_new ("activity-modified",
+ G_OBJECT_CLASS_TYPE (salut_olpc_activity_manager_class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ salut_signals_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, SALUT_TYPE_OLPC_ACTIVITY);
+}
+
+static gboolean
+remove_activity_foreach (gpointer room,
+ gpointer act,
+ gpointer activity)
+{
+ return act == activity;
+}
+
+static void
+activity_finalized_cb (gpointer data,
+ GObject *activity)
+{
+ SalutOlpcActivityManager *self = SALUT_OLPC_ACTIVITY_MANAGER (data);
+ SalutOlpcActivityManagerPrivate *priv =
+ SALUT_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ g_hash_table_foreach_remove (priv->activities_by_room,
+ remove_activity_foreach, activity);
+}
+
+static gboolean
+dispose_activity_foreach (gpointer room,
+ gpointer activity,
+ gpointer user_data)
+{
+ SalutOlpcActivityManager *self = SALUT_OLPC_ACTIVITY_MANAGER (user_data);
+
+ g_object_weak_unref (G_OBJECT (activity), activity_finalized_cb, self);
+ g_signal_handlers_disconnect_matched (activity, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+
+ return TRUE;
+}
+
+static void
+salut_olpc_activity_manager_dispose (GObject *object)
+{
+ SalutOlpcActivityManager *self = SALUT_OLPC_ACTIVITY_MANAGER (object);
+ SalutOlpcActivityManagerPrivate *priv = SALUT_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->activities_by_room != NULL)
+ {
+ g_hash_table_foreach_remove (priv->activities_by_room,
+ dispose_activity_foreach, self);
+ g_hash_table_unref (priv->activities_by_room);
+ priv->activities_by_room = NULL;
+ }
+
+ if (G_OBJECT_CLASS (salut_olpc_activity_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_olpc_activity_manager_parent_class)->dispose (object);
+}
+
+static void
+salut_olpc_activity_manager_finalize (GObject *object)
+{
+ //SalutOlpcActivityManager *self = SALUT_OLPC_ACTIVITY_MANAGER (object);
+
+ G_OBJECT_CLASS (salut_olpc_activity_manager_parent_class)->finalize (object);
+}
+
+gboolean
+salut_olpc_activity_manager_start (SalutOlpcActivityManager *self,
+ GError **error)
+{
+ return SALUT_OLPC_ACTIVITY_MANAGER_GET_CLASS (self)->start (self, error);
+}
+
+SalutOlpcActivity *
+salut_olpc_activity_manager_get_activity_by_room (SalutOlpcActivityManager *self,
+ TpHandle room)
+{
+ SalutOlpcActivityManagerPrivate *priv =
+ SALUT_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+ return g_hash_table_lookup (priv->activities_by_room,
+ GUINT_TO_POINTER (room));
+}
+
+SalutOlpcActivity *
+salut_olpc_activity_manager_get_activity_by_id (SalutOlpcActivityManager *self,
+ const gchar *activity_id)
+{
+ SalutOlpcActivityManagerPrivate *priv =
+ SALUT_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, priv->activities_by_room);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ SalutOlpcActivity *activity = value;
+ if (strcmp (activity->id, activity_id) == 0)
+ return activity;
+ }
+
+ return NULL;
+}
+
+SalutOlpcActivity *
+salut_olpc_activity_manager_ensure_activity_by_room (
+ SalutOlpcActivityManager *self,
+ TpHandle room)
+{
+ SalutOlpcActivity *activity;
+ SalutOlpcActivityManagerPrivate *priv =
+ SALUT_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ activity = g_hash_table_lookup (priv->activities_by_room,
+ GUINT_TO_POINTER (room));
+
+ if (activity != NULL)
+ {
+ return g_object_ref (activity);
+ }
+ else
+ {
+ activity = salut_olpc_activity_manager_create_activity (self, room);
+ return activity;
+ }
+}
+
+static void
+activity_modified_cb (SalutOlpcActivity *activity,
+ SalutOlpcActivityManager *self)
+{
+ g_signal_emit (self, signals[ACTIVITY_MODIFIED], 0, activity);
+}
+
+SalutOlpcActivity *
+salut_olpc_activity_manager_create_activity (SalutOlpcActivityManager *self,
+ TpHandle room)
+{
+ SalutOlpcActivity *activity;
+ SalutOlpcActivityManagerPrivate *priv =
+ SALUT_OLPC_ACTIVITY_MANAGER_GET_PRIVATE (self);
+
+ g_assert (room != 0);
+ g_assert (g_hash_table_lookup (priv->activities_by_room, GUINT_TO_POINTER (
+ room)) == NULL);
+
+ activity = SALUT_OLPC_ACTIVITY_MANAGER_GET_CLASS (self)->create_activity (
+ self);
+ salut_olpc_activity_update (activity, room, NULL, NULL, NULL, NULL, NULL,
+ TRUE);
+
+ g_hash_table_insert (priv->activities_by_room, GUINT_TO_POINTER (room),
+ activity);
+
+ g_signal_connect (activity, "modified", G_CALLBACK (activity_modified_cb),
+ self);
+ g_object_weak_ref (G_OBJECT (activity), activity_finalized_cb , self);
+
+ return activity;
+}
+
+SalutOlpcActivity *
+salut_olpc_activity_manager_got_invitation (SalutOlpcActivityManager *self,
+ TpHandle room,
+ SalutContact *inviter,
+ const gchar *id,
+ const gchar *name,
+ const gchar *type,
+ const gchar *color,
+ const gchar *tags)
+{
+ SalutOlpcActivity *activity;
+
+ activity = salut_olpc_activity_manager_ensure_activity_by_room (self, room);
+
+ salut_olpc_activity_update (activity, room, id, name, type, color, tags,
+ activity->is_private);
+
+ /* FIXME: we shouldn't add it if the local user is already in the activity
+ * as, for now, we don't manage private activity membership (it's PS job) */
+
+ /* add the inviter to the activity */
+ salut_contact_joined_activity (inviter, activity);
+
+ /* contact reffed the activity if it didn't hold a ref on it yet */
+ g_object_unref (activity);
+
+ return activity;
+}
+
+void
+salut_olpc_activity_manager_contact_joined (SalutOlpcActivityManager *self,
+ SalutContact *contact,
+ SalutOlpcActivity *activity)
+{
+ salut_contact_joined_activity (contact, activity);
+}
+
+void
+salut_olpc_activity_manager_contact_left (SalutOlpcActivityManager *mgr,
+ SalutContact *contact,
+ SalutOlpcActivity *activity)
+{
+ salut_contact_left_activity (contact, activity);
+}
diff --git a/salut/src/olpc-activity-manager.h b/salut/src/olpc-activity-manager.h
new file mode 100644
index 000000000..79686389c
--- /dev/null
+++ b/salut/src/olpc-activity-manager.h
@@ -0,0 +1,98 @@
+/*
+ * olpc-activity-managere.h - Header for SalutOlpcActivityManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_OLPC_ACTIVITY_MANAGER_H__
+#define __SALUT_OLPC_ACTIVITY_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/handle.h>
+
+#include "connection.h"
+#include "contact.h"
+#include "olpc-activity.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutOlpcActivityManager SalutOlpcActivityManager;
+typedef struct _SalutOlpcActivityManagerClass SalutOlpcActivityManagerClass;
+
+struct _SalutOlpcActivityManagerClass {
+ GObjectClass parent_class;
+
+ /* public abstract methods */
+ gboolean (*start) (SalutOlpcActivityManager *self, GError **error);
+
+ /* private abstract methods */
+ SalutOlpcActivity * (*create_activity) (SalutOlpcActivityManager *self);
+};
+
+struct _SalutOlpcActivityManager {
+ GObject parent;
+
+ /* private */
+ SalutConnection *connection;
+};
+
+GType salut_olpc_activity_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_OLPC_ACTIVITY_MANAGER \
+ (salut_olpc_activity_manager_get_type ())
+#define SALUT_OLPC_ACTIVITY_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_OLPC_ACTIVITY_MANAGER, SalutOlpcActivityManager))
+#define SALUT_OLPC_ACTIVITY_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_OLPC_ACTIVITY_MANAGER, SalutOlpcActivityManagerClass))
+#define SALUT_IS_OLPC_ACTIVITY_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_OLPC_ACTIVITY_MANAGER))
+#define SALUT_IS_OLPC_ACTIVITY_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_OLPC_ACTIVITY_MANAGER))
+#define SALUT_OLPC_ACTIVITY_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_OLPC_ACTIVITY_MANAGER, SalutOlpcActivityManagerClass))
+
+gboolean salut_olpc_activity_manager_start (SalutOlpcActivityManager *mgr,
+ GError **error);
+
+SalutOlpcActivity * salut_olpc_activity_manager_get_activity_by_room (
+ SalutOlpcActivityManager *mgr, TpHandle room);
+
+SalutOlpcActivity * salut_olpc_activity_manager_get_activity_by_id (
+ SalutOlpcActivityManager *mgr, const gchar *activity_id);
+
+SalutOlpcActivity * salut_olpc_activity_manager_ensure_activity_by_room (
+ SalutOlpcActivityManager *mgr, TpHandle room);
+
+SalutOlpcActivity * salut_olpc_activity_manager_got_invitation (
+ SalutOlpcActivityManager *mgr, TpHandle room, SalutContact *inviter,
+ const gchar *id, const gchar *name, const gchar *type, const gchar *color,
+ const gchar *tags);
+
+/* restricted methods */
+SalutOlpcActivity * salut_olpc_activity_manager_create_activity (
+ SalutOlpcActivityManager *mgr, TpHandle room);
+
+void salut_olpc_activity_manager_contact_joined (SalutOlpcActivityManager *mgr,
+ SalutContact *contact, SalutOlpcActivity *activity);
+
+void salut_olpc_activity_manager_contact_left (SalutOlpcActivityManager *mgr,
+ SalutContact *contact, SalutOlpcActivity *activity);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_OLPC_ACTIVITY_MANAGER_H__*/
diff --git a/salut/src/olpc-activity.c b/salut/src/olpc-activity.c
new file mode 100644
index 000000000..141783ee8
--- /dev/null
+++ b/salut/src/olpc-activity.c
@@ -0,0 +1,700 @@
+/*
+ * olpc-activity.c - Source for SalutOlpcActivity
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-stanza.h>
+
+#include "contact-manager.h"
+#include "olpc-activity.h"
+#include "muc-manager.h"
+#include "util.h"
+#include "namespaces.h"
+
+#include "signals-marshal.h"
+
+#define DEBUG_FLAG DEBUG_OLPC_ACTIVITY
+#include "debug.h"
+
+G_DEFINE_TYPE (SalutOlpcActivity, salut_olpc_activity, G_TYPE_OBJECT);
+
+/* properties */
+enum {
+ PROP_CONNECTION = 1,
+ LAST_PROP
+};
+
+/* signal enum */
+enum
+{
+ MODIFIED,
+ VALID,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+typedef struct _SalutOlpcActivityPrivate SalutOlpcActivityPrivate;
+
+struct _SalutOlpcActivityPrivate
+{
+ /* Handles of contacts we invited to join this activity */
+ TpHandleSet *invited;
+ /* can be NULL if we are not in the activity */
+ SalutMucChannel *muc;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_OLPC_ACTIVITY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_OLPC_ACTIVITY, SalutOlpcActivityPrivate))
+
+static void
+salut_olpc_activity_init (SalutOlpcActivity *self)
+{
+ self->connection = NULL;
+
+ self->is_private = TRUE;
+}
+
+static void
+salut_olpc_activity_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutOlpcActivity *self = SALUT_OLPC_ACTIVITY (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->connection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_olpc_activity_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutOlpcActivity *self = SALUT_OLPC_ACTIVITY (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ self->connection = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+salut_olpc_activity_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutOlpcActivity *self;
+ SalutOlpcActivityPrivate *priv;
+ TpHandleRepoIface *contact_repo;
+
+ obj = G_OBJECT_CLASS (salut_olpc_activity_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_OLPC_ACTIVITY (obj);
+ priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ g_assert (self->connection != NULL);
+ contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_CONTACT);
+
+ priv->invited = tp_handle_set_new (contact_repo);
+
+ return obj;
+}
+
+static void salut_olpc_activity_dispose (GObject *object);
+static void salut_olpc_activity_finalize (GObject *object);
+
+static void
+salut_olpc_activity_class_init (SalutOlpcActivityClass *salut_olpc_activity_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_olpc_activity_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_olpc_activity_class,
+ sizeof (SalutOlpcActivityPrivate));
+
+ object_class->get_property = salut_olpc_activity_get_property;
+ object_class->set_property = salut_olpc_activity_set_property;
+
+ object_class->constructor = salut_olpc_activity_constructor;
+ object_class->dispose = salut_olpc_activity_dispose;
+ object_class->finalize = salut_olpc_activity_finalize;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "The Salut Connection associated with this muc manager",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ param_spec);
+
+ signals[MODIFIED] = g_signal_new ("modified",
+ G_OBJECT_CLASS_TYPE (salut_olpc_activity_class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ salut_signals_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[VALID] = g_signal_new ("valid",
+ G_OBJECT_CLASS_TYPE (salut_olpc_activity_class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ salut_signals_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+salut_olpc_activity_dispose (GObject *object)
+{
+ SalutOlpcActivity *self = SALUT_OLPC_ACTIVITY (object);
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) self->connection, TP_HANDLE_TYPE_ROOM);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (self->room != 0)
+ {
+ tp_handle_unref (room_repo, self->room);
+ self->room = 0;
+ }
+
+ if (priv->muc != NULL)
+ {
+ g_signal_handlers_disconnect_matched (priv->muc, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+ g_object_unref (priv->muc);
+ priv->muc = NULL;
+ }
+
+ tp_handle_set_destroy (priv->invited);
+
+ if (G_OBJECT_CLASS (salut_olpc_activity_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_olpc_activity_parent_class)->dispose (object);
+}
+
+static void
+salut_olpc_activity_finalize (GObject *object)
+{
+ SalutOlpcActivity *self = SALUT_OLPC_ACTIVITY (object);
+
+ g_free (self->id);
+ g_free (self->name);
+ g_free (self->type);
+ g_free (self->color);
+ g_free (self->tags);
+
+ G_OBJECT_CLASS (salut_olpc_activity_parent_class)->finalize (object);
+}
+
+GHashTable *
+salut_olpc_activity_create_properties_table (SalutOlpcActivity *self)
+{
+ GHashTable *properties;
+ GValue *val;
+
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ if (self->color != NULL)
+ {
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (val, self->color);
+ g_hash_table_insert (properties, "color", val);
+ }
+
+ if (self->name != NULL)
+ {
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (val, self->name);
+ g_hash_table_insert (properties, "name", val);
+ }
+
+ if (self->type != NULL)
+ {
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (val, self->type);
+ g_hash_table_insert (properties, "type", val);
+ }
+
+ if (self->tags != NULL)
+ {
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (val, self->tags);
+ g_hash_table_insert (properties, "tags", val);
+ }
+
+ val = tp_g_value_slice_new (G_TYPE_BOOLEAN);
+ g_value_set_boolean (val, self->is_private);
+ g_hash_table_insert (properties, "private", val);
+
+ return properties;
+}
+
+static gboolean
+send_properties_change_msg (SalutOlpcActivity *self,
+ GError **error)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ GHashTable *properties;
+ GValue *activity_id_val;
+ WockyStanza *stanza;
+ WockyNode *top_node;
+ WockyNode *properties_node;
+ gchar *muc_name;
+ GibberMucConnection *muc_connection;
+ gboolean result;
+ GError *err = NULL;
+
+ if (priv->muc == NULL)
+ /* we are not in the muc */
+ return TRUE;
+
+ g_object_get (priv->muc,
+ "name", &muc_name,
+ "muc-connection", &muc_connection,
+ NULL);
+
+ if (muc_connection->state != GIBBER_MUC_CONNECTION_CONNECTED)
+ {
+ DEBUG ("Muc connection not connected yet. Drop activity change message");
+ g_object_unref (muc_connection);
+ g_free (muc_name);
+ return TRUE;
+ }
+
+ properties = salut_olpc_activity_create_properties_table (self);
+
+ /* add the activity id */
+ activity_id_val = g_slice_new0 (GValue);
+ g_value_init (activity_id_val, G_TYPE_STRING);
+ g_value_set_static_string (activity_id_val, self->id);
+ g_hash_table_insert (properties, "id", activity_id_val);
+
+ stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_GROUPCHAT,
+ self->connection->name, muc_name,
+ WOCKY_NODE_START, "properties",
+ WOCKY_NODE_XMLNS, NS_OLPC_ACTIVITY_PROPS,
+ WOCKY_NODE_END, NULL);
+ top_node = wocky_stanza_get_top_node (stanza);
+
+ properties_node = wocky_node_get_child_ns (top_node, "properties",
+ NS_OLPC_ACTIVITY_PROPS);
+
+ salut_wocky_node_add_children_from_properties (properties_node,
+ properties, "property");
+
+ result = gibber_muc_connection_send (muc_connection, stanza, &err);
+ if (!result)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s",
+ err->message);
+ g_error_free (err);
+ }
+
+ g_object_unref (stanza);
+ g_object_unref (muc_connection);
+ g_free (muc_name);
+ g_hash_table_unref (properties);
+
+ return result;
+}
+
+static void
+resend_invite_foreach (TpHandleSet *set,
+ TpHandle handle,
+ SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (!salut_muc_channel_send_invitation (priv->muc, handle,
+ "OLPC activity properties update", &error))
+ {
+ DEBUG ("failed to re-invite contact %d to activity %s", handle,
+ self->id);
+ }
+}
+
+static void
+resend_invite (SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ if (priv->muc == NULL)
+ /* we are not in the muc */
+ return;
+
+ /* Resend pending invitations so contacts will know about new properties */
+ tp_handle_set_foreach (priv->invited,
+ (TpHandleSetMemberFunc) resend_invite_foreach, self);
+}
+
+static void
+activity_changed (SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (!send_properties_change_msg (self, &error))
+ {
+ DEBUG ("send properties changes msg failed: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (!self->is_private && priv->muc != NULL)
+ {
+ /* update announcement */
+ if (!SALUT_OLPC_ACTIVITY_GET_CLASS (self)->update (self, &error))
+ {
+ DEBUG ("update activity failed: %s", error->message);
+ g_error_free (error);
+ }
+ }
+ else
+ {
+ resend_invite (self);
+ }
+}
+
+static gboolean
+salut_olpc_activity_announce (SalutOlpcActivity *self,
+ GError **error)
+{
+ GError *err = NULL;
+
+ if (!SALUT_OLPC_ACTIVITY_GET_CLASS (self)->announce (self, &err))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s",
+ err->message);
+ g_error_free (err);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+salut_olpc_activity_stop_announce (SalutOlpcActivity *self)
+{
+ SALUT_OLPC_ACTIVITY_GET_CLASS (self)->stop_announce (self);
+}
+
+gboolean
+salut_olpc_activity_update (SalutOlpcActivity *self,
+ TpHandle room,
+ const gchar *id,
+ const gchar *name,
+ const gchar *type,
+ const gchar *color,
+ const gchar *tags,
+ gboolean is_private)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) (self->connection);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_ROOM);
+ gboolean changed = FALSE;
+ GError *error = NULL;
+ gboolean become_valid = FALSE;
+
+ if (room != 0 && room != self->room)
+ {
+ if (self->room != 0)
+ {
+ tp_handle_unref (room_repo, self->room);
+ }
+
+ self->room = room;
+ tp_handle_ref (room_repo, room);
+ }
+
+ if (id != NULL && tp_strdiff (id, self->id))
+ {
+ if (self->id == NULL)
+ become_valid = TRUE;
+
+ g_free (self->id);
+ self->id = g_strdup (id);
+ changed = TRUE;
+ }
+
+ if (name != NULL && tp_strdiff (name, self->name))
+ {
+ g_free (self->name);
+ self->name = g_strdup (name);
+ changed = TRUE;
+ }
+
+ if (type != NULL && tp_strdiff (type, self->type))
+ {
+ g_free (self->type);
+ self->type = g_strdup (type);
+ changed = TRUE;
+ }
+
+ if (color != NULL && tp_strdiff (color, self->color))
+ {
+ g_free (self->color);
+ self->color = g_strdup (color);
+ changed = TRUE;
+ }
+
+ if (tp_strdiff (tags, self->tags))
+ {
+ g_free (self->tags);
+ self->tags = g_strdup (tags);
+ changed = TRUE;
+ }
+
+ if (is_private != self->is_private)
+ {
+ self->is_private = is_private;
+ changed = TRUE;
+
+ if (priv->muc != NULL)
+ {
+ if (is_private)
+ {
+ DEBUG ("activity is not public anymore. Stop to announce it");
+ salut_olpc_activity_stop_announce (self);
+ }
+ else
+ {
+ DEBUG ("activity becomes public. Announce it");
+ if (!salut_olpc_activity_announce (self, &error))
+ {
+ DEBUG ("activity announce failed: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+ }
+ }
+ }
+
+ if (become_valid)
+ {
+ g_signal_emit (self, signals[VALID], 0);
+ }
+
+ if (changed)
+ {
+ activity_changed (self);
+
+ g_signal_emit (self, signals[MODIFIED], 0);
+ }
+
+ return changed;
+}
+
+static void
+muc_channel_closed_cb (SalutMucChannel *chan,
+ SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ g_object_unref (priv->muc);
+ priv->muc = NULL;
+}
+
+gboolean
+salut_olpc_activity_joined (SalutOlpcActivity *self,
+ GError **error)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ TpHandleRepoIface *room_repo;
+ SalutMucManager *muc_manager;
+
+ if (priv->muc != NULL)
+ return TRUE;
+
+ room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_ROOM);
+
+ g_object_get (self->connection,
+ "muc-manager", &muc_manager,
+ NULL);
+
+ priv->muc = salut_muc_manager_get_text_channel (muc_manager, self->room);
+ g_object_unref (muc_manager);
+
+ if (priv->muc == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Can't find muc channel for room %s", tp_handle_inspect (
+ room_repo, self->room));
+ return FALSE;
+ }
+
+ if (!self->is_private)
+ {
+ /* This might fail but that doesn't prevent us from joining the
+ * activity.. */
+ salut_olpc_activity_announce (self, NULL);
+ }
+
+ g_signal_connect (priv->muc, "closed", G_CALLBACK (muc_channel_closed_cb),
+ self);
+
+ return TRUE;
+}
+
+void
+salut_olpc_activity_left (SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ if (priv->muc == NULL)
+ return;
+
+ if (!self->is_private)
+ salut_olpc_activity_stop_announce (self);
+
+ g_object_unref (priv->muc);
+ g_signal_handlers_disconnect_matched (priv->muc, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+ priv->muc = NULL;
+}
+
+void
+salut_olpc_activity_revoke_invitations (SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ WockyStanza *msg;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_CONTACT);
+ TpIntSetIter iter = TP_INTSET_ITER_INIT (tp_handle_set_peek (
+ priv->invited));
+ SalutContactManager *contact_mgr;
+ WockyNode *top_node;
+
+ if (tp_handle_set_size (priv->invited) <= 0)
+ return;
+
+ msg = wocky_stanza_build (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_NONE,
+ self->connection->name, NULL,
+ WOCKY_NODE_START, "uninvite",
+ WOCKY_NODE_XMLNS, NS_OLPC_ACTIVITY_PROPS,
+ WOCKY_NODE_ATTRIBUTE, "room", tp_handle_inspect (room_repo,
+ self->room),
+ WOCKY_NODE_ATTRIBUTE, "id", self->id,
+ WOCKY_NODE_END, NULL);
+ top_node = wocky_stanza_get_top_node (msg);
+
+ g_object_get (self->connection,
+ "contact-manager", &contact_mgr,
+ NULL);
+ g_assert (contact_mgr != NULL);
+
+ DEBUG ("revoke invitations for activity %s", self->id);
+ while (tp_intset_iter_next (&iter))
+ {
+ TpHandle contact_handle;
+ SalutContact *contact;
+ const gchar *to;
+
+ contact_handle = iter.element;
+ contact = salut_contact_manager_get_contact (contact_mgr, contact_handle);
+ if (contact == NULL)
+ {
+ DEBUG ("Can't find contact %d", contact_handle);
+ continue;
+ }
+
+ to = tp_handle_inspect (contact_repo, contact_handle);
+ wocky_node_set_attribute (top_node, "to", to);
+
+ wocky_stanza_set_to_contact (msg, WOCKY_CONTACT (contact));
+ wocky_porter_send (self->connection->porter, msg);
+
+ g_object_unref (contact);
+ }
+
+ g_object_unref (msg);
+ g_object_unref (contact_mgr);
+}
+
+void
+salut_olpc_activity_augment_invitation (SalutOlpcActivity *self,
+ TpHandle contact,
+ WockyNode *invite_node)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ WockyNode *properties_node;
+ GHashTable *properties;
+ GValue *activity_id_val;
+
+ properties = salut_olpc_activity_create_properties_table (self);
+
+ properties_node = wocky_node_add_child_ns (invite_node, "properties",
+ NS_OLPC_ACTIVITY_PROPS);
+
+ /* add the activity id */
+ activity_id_val = g_slice_new0 (GValue);
+ g_value_init (activity_id_val, G_TYPE_STRING);
+ g_value_set_static_string (activity_id_val, self->id);
+ g_hash_table_insert (properties, "id", activity_id_val);
+
+ salut_wocky_node_add_children_from_properties (properties_node,
+ properties, "property");
+
+ tp_handle_set_add (priv->invited, contact);
+
+ g_hash_table_unref (properties);
+}
+
+gboolean
+salut_olpc_activity_remove_invited (SalutOlpcActivity *self,
+ TpHandle contact)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ return tp_handle_set_remove (priv->invited, contact);
+}
diff --git a/salut/src/olpc-activity.h b/salut/src/olpc-activity.h
new file mode 100644
index 000000000..2adb64985
--- /dev/null
+++ b/salut/src/olpc-activity.h
@@ -0,0 +1,98 @@
+/*
+ * olpc-activity.h - Header for SalutOlpcActivity
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_OLPC_ACTIVITY_H__
+#define __SALUT_OLPC_ACTIVITY_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/handle.h>
+
+#include "connection.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutOlpcActivity SalutOlpcActivity;
+typedef struct _SalutOlpcActivityClass SalutOlpcActivityClass;
+
+struct _SalutOlpcActivityClass {
+ GObjectClass parent_class;
+
+ /* private abstract methods */
+ gboolean (*announce) (SalutOlpcActivity *activity, GError **error);
+ void (*stop_announce) (SalutOlpcActivity *activity);
+
+ gboolean (*update) (SalutOlpcActivity *activity, GError **error);
+};
+
+struct _SalutOlpcActivity {
+ GObject parent;
+
+ TpHandle room;
+ gchar *id;
+ gchar *name;
+ gchar *type;
+ gchar *color;
+ gchar *tags;
+ gboolean is_private;
+
+ /* private */
+ SalutConnection *connection;
+};
+
+GType salut_olpc_activity_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_OLPC_ACTIVITY \
+ (salut_olpc_activity_get_type ())
+#define SALUT_OLPC_ACTIVITY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_OLPC_ACTIVITY, SalutOlpcActivity))
+#define SALUT_OLPC_ACTIVITY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_OLPC_ACTIVITY, SalutOlpcActivityClass))
+#define SALUT_IS_OLPC_ACTIVITY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_OLPC_ACTIVITY))
+#define SALUT_IS_OLPC_ACTIVITY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_OLPC_ACTIVITY))
+#define SALUT_OLPC_ACTIVITY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_OLPC_ACTIVITY, SalutOlpcActivityClass))
+
+G_END_DECLS
+
+gboolean salut_olpc_activity_update (SalutOlpcActivity *activity,
+ TpHandle room, const gchar *id, const gchar *name,
+ const gchar *type, const gchar *color, const gchar *tags,
+ gboolean is_private);
+
+gboolean salut_olpc_activity_joined (SalutOlpcActivity *activity,
+ GError **error);
+
+void salut_olpc_activity_left (SalutOlpcActivity *activity);
+
+void salut_olpc_activity_revoke_invitations (SalutOlpcActivity *activity);
+
+GHashTable * salut_olpc_activity_create_properties_table (
+ SalutOlpcActivity *activity);
+
+void salut_olpc_activity_augment_invitation (SalutOlpcActivity *activity,
+ TpHandle contact, WockyNode *invite_node);
+
+gboolean salut_olpc_activity_remove_invited (SalutOlpcActivity *activity,
+ TpHandle contact);
+
+#endif /* #ifndef __SALUT_OLPC_ACTIVITY_H__*/
diff --git a/salut/src/plugin-loader.c b/salut/src/plugin-loader.c
new file mode 100644
index 000000000..75420ab77
--- /dev/null
+++ b/salut/src/plugin-loader.c
@@ -0,0 +1,362 @@
+/*
+ * plugin-loader.c — plugin support for telepathy-salut
+ * Copyright © 2009-2011 Collabora Ltd.
+ * Copyright © 2009 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "plugin-loader.h"
+
+#include <glib.h>
+
+#ifdef ENABLE_PLUGINS
+# include <gmodule.h>
+#endif
+
+#include <telepathy-glib/util.h>
+
+#define DEBUG_FLAG DEBUG_PLUGIN
+#include "debug.h"
+#include "salut/plugin.h"
+
+G_DEFINE_TYPE(SalutPluginLoader,
+ salut_plugin_loader,
+ G_TYPE_OBJECT)
+
+struct _SalutPluginLoaderPrivate
+{
+ GPtrArray *plugins;
+};
+
+#ifdef ENABLE_PLUGINS
+static void
+plugin_loader_try_to_load (
+ SalutPluginLoader *self,
+ const gchar *path)
+{
+ GModule *m = g_module_open (path, G_MODULE_BIND_LOCAL);
+ gpointer func;
+ SalutPluginCreateImpl create;
+ SalutPlugin *plugin;
+
+ if (m == NULL)
+ {
+ const gchar *e = g_module_error ();
+
+ /* the errors often seem to be prefixed by the filename */
+ if (g_str_has_prefix (e, path))
+ DEBUG ("%s", e);
+ else
+ DEBUG ("%s: %s", path, e);
+
+ return;
+ }
+
+ if (!g_module_symbol (m, "salut_plugin_create", &func))
+ {
+ DEBUG ("%s", g_module_error ());
+ g_module_close (m);
+ return;
+ }
+
+ /* We're about to try to instantiate an object. This installs the
+ * class with the type system, so we should ensure that this
+ * plug-in is never accidentally unloaded.
+ */
+ g_module_make_resident (m);
+
+ /* Here goes nothing... */
+ create = func;
+ plugin = create ();
+
+ if (plugin == NULL)
+ {
+ g_warning ("salut_plugin_create () failed for %s", path);
+ }
+ else
+ {
+ gchar *sidecars = NULL;
+ const gchar * const *ifaces = salut_plugin_get_sidecar_interfaces (plugin);
+ const gchar *version = salut_plugin_get_version (plugin);
+
+ if (version == NULL)
+ version = "(unspecified)";
+
+ if (ifaces != NULL)
+ sidecars = g_strjoinv (", ", (gchar **) ifaces);
+
+ DEBUG ("loaded '%s' version %s (%s), implementing %s sidecars: (%s)",
+ salut_plugin_get_name (plugin), version, path,
+ sidecars != NULL ? "these" : "no",
+ sidecars != NULL ? sidecars : "");
+
+ g_free (sidecars);
+
+ g_ptr_array_add (self->priv->plugins, plugin);
+ }
+}
+
+static void
+salut_plugin_loader_probe (SalutPluginLoader *self)
+{
+ GError *error = NULL;
+ const gchar *directory_name = g_getenv ("SALUT_PLUGIN_DIR");
+ GDir *d;
+ const gchar *file;
+
+ if (!g_module_supported ())
+ {
+ DEBUG ("modules aren't supported on this platform.");
+ return;
+ }
+
+ if (directory_name == NULL)
+ directory_name = PLUGIN_DIR;
+
+ DEBUG ("probing %s", directory_name);
+ d = g_dir_open (directory_name, 0, &error);
+
+ if (d == NULL)
+ {
+ DEBUG ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ while ((file = g_dir_read_name (d)) != NULL)
+ {
+ gchar *path;
+
+ if (!g_str_has_suffix (file, G_MODULE_SUFFIX))
+ continue;
+
+ path = g_build_filename (directory_name, file, NULL);
+ plugin_loader_try_to_load (self, path);
+ g_free (path);
+ }
+
+ g_dir_close (d);
+}
+#endif
+
+static void
+salut_plugin_loader_init (SalutPluginLoader *self)
+{
+ SalutPluginLoaderPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_PLUGIN_LOADER, SalutPluginLoaderPrivate);
+
+ self->priv = priv;
+ priv->plugins = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static GObject *
+salut_plugin_loader_constructor (
+ GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ static gpointer singleton = NULL;
+
+ if (singleton == NULL)
+ {
+ singleton = G_OBJECT_CLASS (salut_plugin_loader_parent_class)->
+ constructor (type, n_props, props);
+ g_object_add_weak_pointer (G_OBJECT (singleton), &singleton);
+
+ return singleton;
+ }
+ else
+ {
+ return g_object_ref (singleton);
+ }
+}
+
+static void
+salut_plugin_loader_constructed (GObject *object)
+{
+ SalutPluginLoader *self = SALUT_PLUGIN_LOADER (object);
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (salut_plugin_loader_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+#ifdef ENABLE_PLUGINS
+ salut_plugin_loader_probe (self);
+#else
+ DEBUG ("built without plugin support, not actually loading anything");
+ (void) self; /* silence unused variable warning. */
+#endif
+}
+
+static void
+salut_plugin_loader_finalize (GObject *object)
+{
+ SalutPluginLoader *self = SALUT_PLUGIN_LOADER (object);
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (salut_plugin_loader_parent_class)->finalize;
+
+ tp_clear_pointer (&self->priv->plugins, g_ptr_array_unref);
+
+ if (chain_up != NULL)
+ chain_up (object);
+}
+
+static void
+salut_plugin_loader_class_init (SalutPluginLoaderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (SalutPluginLoaderPrivate));
+
+ object_class->constructor = salut_plugin_loader_constructor;
+ object_class->constructed = salut_plugin_loader_constructed;
+ object_class->finalize = salut_plugin_loader_finalize;
+}
+
+SalutPluginLoader *
+salut_plugin_loader_dup ()
+{
+ return g_object_new (SALUT_TYPE_PLUGIN_LOADER, NULL);
+}
+
+static void
+create_sidecar_cb (
+ GObject *plugin_obj,
+ GAsyncResult *nested_result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result = user_data;
+ SalutSidecar *sidecar;
+ GError *error = NULL;
+
+ sidecar = salut_plugin_create_sidecar_finish (SALUT_PLUGIN (plugin_obj),
+ nested_result, &error);
+
+ if (sidecar == NULL)
+ {
+ g_simple_async_result_set_from_error (result, error);
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gpointer (result, sidecar,
+ g_object_unref);
+ }
+
+ g_simple_async_result_complete (result);
+ g_object_unref (result);
+}
+
+void
+salut_plugin_loader_create_sidecar_async (
+ SalutPluginLoader *self,
+ const gchar *sidecar_interface,
+ SalutConnection *connection,
+ WockySession *session,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SalutPluginLoaderPrivate *priv = self->priv;
+ guint i;
+
+ for (i = 0; i < priv->plugins->len; i++)
+ {
+ SalutPlugin *p = g_ptr_array_index (priv->plugins, i);
+
+ if (salut_plugin_implements_sidecar (p, sidecar_interface))
+ {
+ GSimpleAsyncResult *res = g_simple_async_result_new (G_OBJECT (self),
+ callback, user_data, salut_plugin_loader_create_sidecar_async);
+
+ salut_plugin_create_sidecar_async (p, sidecar_interface, connection, session,
+ create_sidecar_cb, res);
+ return;
+ }
+ }
+
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
+ TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "No plugin implements sidecar '%s'",
+ sidecar_interface);
+}
+
+SalutSidecar *
+salut_plugin_loader_create_sidecar_finish (
+ SalutPluginLoader *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ SalutSidecar *sidecar;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return NULL;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), salut_plugin_loader_create_sidecar_async), NULL);
+
+ sidecar = SALUT_SIDECAR (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (result)));
+ return g_object_ref (sidecar);
+}
+
+void
+salut_plugin_loader_initialize (SalutPluginLoader *self,
+ TpBaseConnectionManager *connection_manager)
+{
+ guint i;
+
+ for (i = 0; i < self->priv->plugins->len; i++)
+ {
+ SalutPlugin *plugin = g_ptr_array_index (self->priv->plugins, i);
+
+ salut_plugin_initialize (plugin, connection_manager);
+ }
+}
+
+static void
+copy_to_other_array (gpointer data,
+ gpointer user_data)
+{
+ g_ptr_array_add (user_data, data);
+}
+
+GPtrArray *
+salut_plugin_loader_create_channel_managers (
+ SalutPluginLoader *self,
+ TpBaseConnection *connection)
+{
+ GPtrArray *out = g_ptr_array_new ();
+ guint i;
+
+ for (i = 0; i < self->priv->plugins->len; i++)
+ {
+ SalutPlugin *plugin = g_ptr_array_index (self->priv->plugins, i);
+ GPtrArray *managers;
+
+ managers = salut_plugin_create_channel_managers (plugin, connection);
+
+ if (managers == NULL)
+ continue;
+
+ g_ptr_array_foreach (managers, copy_to_other_array, out);
+ g_ptr_array_unref (managers);
+ }
+
+ return out;
+}
diff --git a/salut/src/plugin-loader.h b/salut/src/plugin-loader.h
new file mode 100644
index 000000000..b1b65db88
--- /dev/null
+++ b/salut/src/plugin-loader.h
@@ -0,0 +1,88 @@
+/*
+ * plugin-loader.h — plugin support for telepathy-salut
+ * Copyright © 2009-2011 Collabora Ltd.
+ * Copyright © 2009 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef __PLUGIN_LOADER_H__
+#define __PLUGIN_LOADER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection-manager.h>
+#include <telepathy-glib/base-connection.h>
+
+#include <wocky/wocky-session.h>
+
+#include "salut/sidecar.h"
+
+typedef struct _SalutPluginLoader SalutPluginLoader;
+typedef struct _SalutPluginLoaderClass SalutPluginLoaderClass;
+typedef struct _SalutPluginLoaderPrivate SalutPluginLoaderPrivate;
+
+struct _SalutPluginLoaderClass {
+ GObjectClass parent_class;
+};
+
+struct _SalutPluginLoader {
+ GObject parent;
+
+ SalutPluginLoaderPrivate *priv;
+};
+
+GType salut_plugin_loader_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_PLUGIN_LOADER \
+ (salut_plugin_loader_get_type ())
+#define SALUT_PLUGIN_LOADER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_PLUGIN_LOADER, \
+ SalutPluginLoader))
+#define SALUT_PLUGIN_LOADER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_PLUGIN_LOADER, \
+ SalutPluginLoaderClass))
+#define SALUT_IS_PLUGIN_LOADER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_PLUGIN_LOADER))
+#define SALUT_IS_PLUGIN_LOADER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_PLUGIN_LOADER))
+#define SALUT_PLUGIN_LOADER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_PLUGIN_LOADER, \
+ SalutPluginLoaderClass))
+
+SalutPluginLoader * salut_plugin_loader_dup (void);
+
+void salut_plugin_loader_create_sidecar_async (
+ SalutPluginLoader *self,
+ const gchar *sidecar_interface,
+ SalutConnection *connection,
+ WockySession *session,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+SalutSidecar *salut_plugin_loader_create_sidecar_finish (
+ SalutPluginLoader *self,
+ GAsyncResult *result,
+ GError **error);
+
+void salut_plugin_loader_initialize (
+ SalutPluginLoader *self,
+ TpBaseConnectionManager *connection_manager);
+
+GPtrArray * salut_plugin_loader_create_channel_managers (
+ SalutPluginLoader *self,
+ TpBaseConnection *connection);
+
+#endif /* #ifndef __PLUGIN_LOADER_H__ */
diff --git a/salut/src/plugin.c b/salut/src/plugin.c
new file mode 100644
index 000000000..488893af3
--- /dev/null
+++ b/salut/src/plugin.c
@@ -0,0 +1,139 @@
+/*
+ * plugin.c — API for telepathy-salut plugins
+ * Copyright © 2009-2011 Collabora Ltd.
+ * Copyright © 2009 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "salut/plugin.h"
+
+#include <telepathy-glib/util.h>
+
+#define DEBUG_FLAG DEBUG_PLUGINS
+#include "debug.h"
+
+G_DEFINE_INTERFACE (SalutPlugin, salut_plugin, G_TYPE_OBJECT)
+
+static void
+salut_plugin_default_init (SalutPluginInterface *iface)
+{
+}
+
+const gchar *
+salut_plugin_get_name (SalutPlugin *plugin)
+{
+ SalutPluginInterface *iface = SALUT_PLUGIN_GET_INTERFACE (plugin);
+
+ return iface->name;
+}
+
+const gchar *
+salut_plugin_get_version (SalutPlugin *plugin)
+{
+ SalutPluginInterface *iface = SALUT_PLUGIN_GET_INTERFACE (plugin);
+
+ return iface->version;
+}
+
+const gchar * const *
+salut_plugin_get_sidecar_interfaces (SalutPlugin *plugin)
+{
+ SalutPluginInterface *iface = SALUT_PLUGIN_GET_INTERFACE (plugin);
+
+ return iface->sidecar_interfaces;
+}
+
+gboolean
+salut_plugin_implements_sidecar (
+ SalutPlugin *plugin,
+ const gchar *sidecar_interface)
+{
+ SalutPluginInterface *iface = SALUT_PLUGIN_GET_INTERFACE (plugin);
+
+ return tp_strv_contains (iface->sidecar_interfaces, sidecar_interface);
+}
+
+void
+salut_plugin_create_sidecar_async (
+ SalutPlugin *plugin,
+ const gchar *sidecar_interface,
+ SalutConnection *connection,
+ WockySession *session,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SalutPluginInterface *iface = SALUT_PLUGIN_GET_INTERFACE (plugin);
+
+ if (!salut_plugin_implements_sidecar (plugin, sidecar_interface))
+ g_simple_async_report_error_in_idle (G_OBJECT (plugin), callback,
+ user_data, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Salut is buggy: '%s' doesn't implement sidecar %s",
+ iface->name, sidecar_interface);
+ else if (iface->create_sidecar == NULL)
+ g_simple_async_report_error_in_idle (G_OBJECT (plugin), callback,
+ user_data, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "'%s' is buggy: it claims to implement %s, but does not implement "
+ "create_sidecar", iface->name, sidecar_interface);
+ else
+ iface->create_sidecar (plugin, sidecar_interface, connection, session,
+ callback, user_data);
+}
+
+SalutSidecar *
+salut_plugin_create_sidecar_finish (
+ SalutPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ SalutSidecar *sidecar;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return NULL;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (plugin), salut_plugin_create_sidecar_async), NULL);
+
+ sidecar = SALUT_SIDECAR (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (result)));
+ return g_object_ref (sidecar);
+}
+
+void
+salut_plugin_initialize (SalutPlugin *plugin,
+ TpBaseConnectionManager *connection_manager)
+{
+ SalutPluginInterface *iface = SALUT_PLUGIN_GET_INTERFACE (plugin);
+ SalutPluginInitializeImpl func = iface->initialize;
+
+ if (func != NULL)
+ func (plugin, connection_manager);
+}
+
+GPtrArray *
+salut_plugin_create_channel_managers (SalutPlugin *plugin,
+ TpBaseConnection *connection)
+{
+ SalutPluginInterface *iface = SALUT_PLUGIN_GET_INTERFACE (plugin);
+ SalutPluginCreateChannelManagersImpl func = iface->create_channel_managers;
+ GPtrArray *out = NULL;
+
+ if (func != NULL)
+ out = func (plugin, connection);
+
+ return out;
+}
+
diff --git a/salut/src/presence-cache.c b/salut/src/presence-cache.c
new file mode 100644
index 000000000..cc3ad90b3
--- /dev/null
+++ b/salut/src/presence-cache.c
@@ -0,0 +1,691 @@
+/*
+ * presence-cache.c - Salut's contact presence cache
+ * Copyright (C) 2005-2008 Collabora Ltd.
+ * Copyright (C) 2005-2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "presence-cache.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-data-form.h>
+#include <wocky/wocky-caps-hash.h>
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/intset.h>
+#include <salut/capabilities.h>
+
+#define DEBUG_FLAG DEBUG_PRESENCE
+
+#include "capabilities.h"
+#include "debug.h"
+#include "caps-hash.h"
+#include "disco.h"
+#include "signals-marshal.h"
+
+G_DEFINE_TYPE (SalutPresenceCache, salut_presence_cache, G_TYPE_OBJECT);
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ LAST_PROPERTY
+};
+
+/* signal enum */
+enum
+{
+ CAPABILITIES_UPDATE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define SALUT_PRESENCE_CACHE_PRIV(cache) ((cache)->priv)
+
+typedef struct _CapabilityInfo CapabilityInfo;
+
+struct _CapabilityInfo
+{
+ GabbleCapabilitySet *caps;
+ GPtrArray *data_forms;
+};
+
+struct _SalutPresenceCachePrivate
+{
+ SalutConnection *conn;
+
+ /* CapabilityInfo representing anyone without XEP-0115 capabilities (to
+ * interoperate, we actually assume they can do iChat-compatible FT) */
+ CapabilityInfo not_xep_capabilities;
+
+ /* gchar *uri -> CapabilityInfo */
+ GHashTable *capabilities;
+
+ /* gchar *uri -> GSList* of DiscoWaiter* */
+ GHashTable *disco_pending;
+
+ guint caps_serial;
+
+ gboolean dispose_has_run;
+};
+
+typedef struct _DiscoWaiter DiscoWaiter;
+
+struct _DiscoWaiter
+{
+ SalutContact *contact;
+ gchar *hash;
+ gchar *ver;
+ /* if a discovery request fails, we will ask another contact */
+ gboolean disco_requested;
+};
+
+static DiscoWaiter *
+disco_waiter_new (SalutContact *contact,
+ const gchar *hash,
+ const gchar *ver)
+{
+ DiscoWaiter *waiter;
+
+ g_object_ref (contact);
+
+ waiter = g_slice_new0 (DiscoWaiter);
+ waiter->contact = contact;
+ waiter->hash = g_strdup (hash);
+ waiter->ver = g_strdup (ver);
+
+ DEBUG ("created waiter %p for contact %s", waiter, contact->name);
+
+ return waiter;
+}
+
+static void
+disco_waiter_free (DiscoWaiter *waiter)
+{
+ g_assert (NULL != waiter);
+
+ DEBUG ("freeing waiter %p for contact %s", waiter,
+ waiter->contact->name);
+
+ g_object_unref (waiter->contact);
+ g_free (waiter->hash);
+ g_free (waiter->ver);
+ g_slice_free (DiscoWaiter, waiter);
+}
+
+static void
+disco_waiter_list_free (GSList *list)
+{
+ GSList *i;
+
+ DEBUG ("list %p", list);
+
+ for (i = list; NULL != i; i = i->next)
+ disco_waiter_free ((DiscoWaiter *) i->data);
+
+ g_slist_free (list);
+}
+
+static CapabilityInfo *
+capability_info_get (SalutPresenceCache *cache, const gchar *uri)
+{
+ SalutPresenceCachePrivate *priv = SALUT_PRESENCE_CACHE_PRIV (cache);
+ return g_hash_table_lookup (priv->capabilities, uri);
+}
+
+static void
+capability_info_free (CapabilityInfo *info)
+{
+ tp_clear_pointer (&info->caps, gabble_capability_set_free);
+ tp_clear_pointer (&info->data_forms, g_ptr_array_unref);
+
+ g_slice_free (CapabilityInfo, info);
+}
+
+static void salut_presence_cache_init (SalutPresenceCache *presence_cache);
+static GObject * salut_presence_cache_constructor (GType type, guint n_props,
+ GObjectConstructParam *props);
+static void salut_presence_cache_dispose (GObject *object);
+static void salut_presence_cache_finalize (GObject *object);
+static void salut_presence_cache_set_property (GObject *object, guint
+ property_id, const GValue *value, GParamSpec *pspec);
+static void salut_presence_cache_get_property (GObject *object, guint
+ property_id, GValue *value, GParamSpec *pspec);
+
+static void
+salut_presence_cache_class_init (SalutPresenceCacheClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (object_class, sizeof (SalutPresenceCachePrivate));
+
+ object_class->constructor = salut_presence_cache_constructor;
+
+ object_class->dispose = salut_presence_cache_dispose;
+ object_class->finalize = salut_presence_cache_finalize;
+
+ object_class->get_property = salut_presence_cache_get_property;
+ object_class->set_property = salut_presence_cache_set_property;
+
+ param_spec = g_param_spec_object ("connection", "SalutConnection object",
+ "Salut connection object that owns this "
+ "presence cache.",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class,
+ PROP_CONNECTION,
+ param_spec);
+
+ signals[CAPABILITIES_UPDATE] = g_signal_new (
+ "capabilities-update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+}
+
+static void
+salut_presence_cache_init (SalutPresenceCache *cache)
+{
+ SalutPresenceCachePrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (cache,
+ SALUT_TYPE_PRESENCE_CACHE, SalutPresenceCachePrivate);
+
+ cache->priv = priv;
+
+ priv->capabilities = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) capability_info_free);
+ priv->disco_pending = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) disco_waiter_list_free);
+ priv->caps_serial = 1;
+}
+
+static GObject *
+salut_presence_cache_constructor (GType type, guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutPresenceCache *self;
+
+ obj = G_OBJECT_CLASS (salut_presence_cache_parent_class)->
+ constructor (type, n_props, props);
+ self = SALUT_PRESENCE_CACHE (obj);
+
+ self->priv->not_xep_capabilities.caps = gabble_capability_set_new ();
+ gabble_capability_set_add (self->priv->not_xep_capabilities.caps,
+ QUIRK_NOT_XEP_CAPABILITIES);
+ self->priv->not_xep_capabilities.data_forms =
+ g_ptr_array_new_with_free_func (g_object_unref);
+
+ return obj;
+}
+
+static void
+salut_presence_cache_dispose (GObject *object)
+{
+ SalutPresenceCache *self = SALUT_PRESENCE_CACHE (object);
+ SalutPresenceCachePrivate *priv = SALUT_PRESENCE_CACHE_PRIV (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("dispose called");
+
+ priv->dispose_has_run = TRUE;
+
+ g_hash_table_unref (priv->capabilities);
+ priv->capabilities = NULL;
+
+ g_hash_table_unref (priv->disco_pending);
+ priv->disco_pending = NULL;
+
+ tp_clear_pointer (&(priv->not_xep_capabilities.caps),
+ gabble_capability_set_free);
+ tp_clear_pointer (&(priv->not_xep_capabilities.data_forms),
+ g_ptr_array_unref);
+
+ if (G_OBJECT_CLASS (salut_presence_cache_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_presence_cache_parent_class)->dispose (object);
+}
+
+static void
+salut_presence_cache_finalize (GObject *object)
+{
+ DEBUG ("called with %p", object);
+
+ G_OBJECT_CLASS (salut_presence_cache_parent_class)->finalize (object);
+}
+
+static void
+salut_presence_cache_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutPresenceCache *cache = SALUT_PRESENCE_CACHE (object);
+ SalutPresenceCachePrivate *priv = SALUT_PRESENCE_CACHE_PRIV (cache);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_presence_cache_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutPresenceCache *cache = SALUT_PRESENCE_CACHE (object);
+ SalutPresenceCachePrivate *priv = SALUT_PRESENCE_CACHE_PRIV (cache);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+data_forms_equal (GPtrArray *one,
+ GPtrArray *two)
+{
+ guint i;
+
+ /* We don't bother looking into each data form at its FORM_TYPE
+ * because object pointers make sense here -- the arrays are stored
+ * in the CapabilityInfo structs, so a completely equal array could
+ * be handed over. */
+
+ if (one->len != two->len)
+ return FALSE;
+
+ for (i = 0; i < one->len; i++)
+ {
+ gpointer data = g_ptr_array_index (one, i);
+
+ if (!tp_g_ptr_array_contains (two, data))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+salut_presence_cache_change_caps (SalutPresenceCache *self,
+ SalutContact *contact,
+ const gchar *thanked,
+ CapabilityInfo *info)
+{
+ if (gabble_capability_set_equals (info->caps, contact->caps)
+ && data_forms_equal (info->data_forms, contact->data_forms))
+ {
+ DEBUG ("capabilities of %s did not actually change", contact->name);
+ return;
+ }
+
+ DEBUG ("setting caps for %s (thanks to %s)", contact->name, thanked);
+
+ salut_contact_set_capabilities (contact, info->caps, info->data_forms);
+ g_signal_emit (self, signals[CAPABILITIES_UPDATE], 0, contact->handle);
+}
+
+static GPtrArray *
+get_data_forms (WockyNode *node)
+{
+ GPtrArray *out = g_ptr_array_new_with_free_func (g_object_unref);
+
+ WockyNodeIter iter;
+ WockyNode *x_node = NULL;
+
+ wocky_node_iter_init (&iter, node, "x", WOCKY_XMPP_NS_DATA);
+ while (wocky_node_iter_next (&iter, &x_node))
+ {
+ WockyDataForm *form = wocky_data_form_new_from_node (x_node, NULL);
+
+ /* we've already parsed the reply to check the hash matches, so
+ * we can already guarantee these data forms will be parsed
+ * fine */
+ if (G_LIKELY (form != NULL))
+ g_ptr_array_add (out, form);
+ }
+
+ return out;
+}
+
+static void
+_caps_disco_cb (SalutDisco *disco,
+ SalutDiscoRequest *request,
+ SalutContact *contact,
+ const gchar *node,
+ WockyNode *query_result,
+ GError *error,
+ gpointer user_data)
+{
+ GSList *waiters, *i;
+ DiscoWaiter *waiter_self;
+ SalutPresenceCache *cache;
+ SalutPresenceCachePrivate *priv;
+ gboolean bad_hash = FALSE;
+ CapabilityInfo *info = NULL;
+
+ if (query_result == NULL)
+ return;
+
+ cache = SALUT_PRESENCE_CACHE (user_data);
+ priv = SALUT_PRESENCE_CACHE_PRIV (cache);
+
+ if (NULL == node)
+ {
+ DEBUG ("got disco response with NULL node, ignoring");
+ return;
+ }
+
+ waiters = g_hash_table_lookup (priv->disco_pending, node);
+
+ if (NULL != error)
+ {
+ DiscoWaiter *waiter = NULL;
+
+ DEBUG ("disco query failed: %s", error->message);
+
+ for (i = waiters; NULL != i; i = i->next)
+ {
+ waiter = (DiscoWaiter *) i->data;
+
+ if (!waiter->disco_requested)
+ {
+ salut_disco_request (disco, SALUT_DISCO_TYPE_INFO,
+ waiter->contact, node, _caps_disco_cb, cache,
+ G_OBJECT(cache), NULL);
+ waiter->disco_requested = TRUE;
+ break;
+ }
+ }
+
+ if (NULL != i)
+ {
+ DEBUG ("sent a retry disco request to %s for URI %s",
+ contact->name, node);
+ }
+ else
+ {
+ /* The contact sends us an error and we don't have any other
+ * contacts to send the discovery request on the same node. We
+ * cannot get the caps for this node. */
+ DEBUG ("failed to find a suitable candidate to retry disco "
+ "request for URI %s", node);
+ g_hash_table_remove (priv->disco_pending, node);
+ }
+
+ return;
+ }
+
+ waiter_self = NULL;
+ for (i = waiters; NULL != i; i = i->next)
+ {
+ DiscoWaiter *waiter;
+
+ waiter = (DiscoWaiter *) i->data;
+ if (waiter->contact == contact)
+ {
+ waiter_self = waiter;
+ break;
+ }
+ }
+ if (NULL == waiter_self)
+ {
+ DEBUG ("Ignoring non requested disco reply");
+ return;
+ }
+
+ /* Only 'sha-1' is mandatory to implement by XEP-0115. If the remote contact
+ * uses another hash algorithm, don't check the hash and fallback to the old
+ * method. The hash method is not included in the discovery request nor
+ * response but we saved it in disco_pending when we received the presence
+ * stanza. */
+ if (!tp_strdiff (waiter_self->hash, "sha-1"))
+ {
+ gchar *computed_hash;
+
+ computed_hash = wocky_caps_hash_compute_from_node (query_result);
+
+ if (!g_str_equal (waiter_self->ver, computed_hash))
+ bad_hash = TRUE;
+
+ if (!bad_hash)
+ {
+ info = capability_info_get (cache, node);
+
+ if (info == NULL)
+ {
+ info = g_slice_new0 (CapabilityInfo);
+ info->caps = gabble_capability_set_new_from_stanza (
+ query_result);
+ info->data_forms = get_data_forms (query_result);
+ g_hash_table_insert (priv->capabilities, g_strdup (node), info);
+ }
+ }
+ else
+ {
+ /* The received reply does not match the */
+ DEBUG ("The announced verification string '%s' does not match "
+ "our hash '%s'.", waiter_self->ver, computed_hash);
+ }
+
+ g_free (computed_hash);
+ }
+ else
+ {
+ /* Do not allow tubes caps if the contact does not observe XEP-0115
+ * version 1.5: we don't need to bother being compatible with both version
+ * 1.3 and tubes caps */
+ DEBUG ("Unsupported hash algorithm, ignoring caps: %s",
+ waiter_self->hash == NULL ? "(none)" : waiter_self->hash);
+ }
+
+ if (info == NULL)
+ info = &priv->not_xep_capabilities;
+
+ for (i = waiters; NULL != i;)
+ {
+ DiscoWaiter *waiter;
+
+ waiter = (DiscoWaiter *) i->data;
+
+ if (!bad_hash || waiter->contact == contact)
+ {
+ GSList *tmp;
+ gpointer key;
+ gpointer value;
+
+ if (!bad_hash)
+ {
+ salut_presence_cache_change_caps (cache, waiter->contact,
+ contact->name, info);
+ }
+
+ tmp = i;
+ i = i->next;
+
+ waiters = g_slist_delete_link (waiters, tmp);
+
+ if (!g_hash_table_lookup_extended (priv->disco_pending, node, &key,
+ &value))
+ g_assert_not_reached ();
+
+ g_hash_table_steal (priv->disco_pending, node);
+ g_hash_table_insert (priv->disco_pending, key, waiters);
+
+ disco_waiter_free (waiter);
+ }
+ else
+ {
+ /* if the possible trust, not counting this guy, is too low,
+ * we have been poisoned and reset our trust meters - disco
+ * anybody we still haven't to be able to get more trusted replies */
+
+ if (!waiter->disco_requested)
+ {
+ salut_disco_request (disco, SALUT_DISCO_TYPE_INFO,
+ waiter->contact, node, _caps_disco_cb, cache,
+ G_OBJECT(cache), NULL);
+ waiter->disco_requested = TRUE;
+ }
+
+ i = i->next;
+ }
+ }
+
+ if (!bad_hash)
+ g_hash_table_remove (priv->disco_pending, node);
+}
+
+
+/* Called when the contact update its hash, node and ver txt records
+ * If theses variables are absent from the record, the parameters are NULL
+ */
+void
+salut_presence_cache_process_caps (SalutPresenceCache *self,
+ SalutContact *contact,
+ const gchar *hash,
+ const gchar *node,
+ const gchar *ver)
+{
+ gchar *uri = NULL;
+ SalutPresenceCachePrivate *priv;
+ CapabilityInfo *info;
+ const gchar *caps_source;
+
+ DEBUG ("Called for %s with '%s' '%s' '%s'",
+ contact->name, hash, node, ver);
+
+ priv = SALUT_PRESENCE_CACHE_PRIV (self);
+
+ if (hash == NULL || node == NULL || ver == NULL ||
+ tp_strdiff (hash, "sha-1"))
+ {
+ /* if the contact does not support capabilities, we consider the default
+ * basic ones */
+ caps_source = "the default capabilities";
+ info = &priv->not_xep_capabilities;
+ }
+ else
+ {
+ uri = g_strdup_printf ("%s#%s", node, ver);
+ info = capability_info_get (self, uri);
+
+ if (info != NULL)
+ caps_source = "an existing cache entry";
+ }
+
+ if (info != NULL)
+ {
+ salut_presence_cache_change_caps (self, contact, caps_source,
+ info);
+ }
+ else
+ {
+ /* Append the contact to the list of such contacts waiting for
+ * capabilities for this uri, and send a disco request if we don't
+ * have enough possible trust yet */
+
+ GSList *waiters;
+ DiscoWaiter *waiter;
+ gpointer key;
+ gpointer value = NULL;
+
+ /* If the URI is in the hash table, steal it and its value; we can
+ * reuse the same URI for the following insertion. Otherwise, make a
+ * copy of the URI for use as a key.
+ */
+ if (g_hash_table_lookup_extended (priv->disco_pending, uri, &key,
+ &value))
+ {
+ g_hash_table_steal (priv->disco_pending, key);
+ }
+ else
+ {
+ key = g_strdup (uri);
+ }
+
+ waiters = (GSList *) value;
+ waiter = disco_waiter_new (contact, hash, ver);
+ waiters = g_slist_prepend (waiters, waiter);
+ g_hash_table_insert (priv->disco_pending, key, waiters);
+
+
+ if (!value)
+ {
+ /* Nobody was asked for this uri so far. Do it now. */
+ salut_disco_request (priv->conn->disco, SALUT_DISCO_TYPE_INFO,
+ contact, uri, _caps_disco_cb, self, G_OBJECT (self), NULL);
+ waiter->disco_requested = TRUE;
+ }
+ }
+
+ g_free (uri);
+}
+
+SalutPresenceCache *
+salut_presence_cache_new (SalutConnection *connection)
+{
+ return g_object_new (SALUT_TYPE_PRESENCE_CACHE,
+ "connection", connection,
+ NULL);
+}
+
+void
+salut_presence_cache_learn_caps (SalutPresenceCache *self,
+ const gchar *node,
+ const gchar *ver,
+ const GabbleCapabilitySet *caps,
+ const GPtrArray *data_forms)
+{
+ SalutPresenceCachePrivate *priv;
+ CapabilityInfo *info;
+ gchar *tmp;
+
+ priv = SALUT_PRESENCE_CACHE_PRIV (self);
+
+ tmp = g_strdup_printf ("%s#%s", node, ver);
+ DEBUG ("learning %s\n", tmp);
+
+ info = g_slice_new0 (CapabilityInfo);
+ info->caps = gabble_capability_set_copy (caps);
+ info->data_forms = g_ptr_array_ref ((GPtrArray *) data_forms);
+ g_hash_table_insert (priv->capabilities, tmp, info);
+}
diff --git a/salut/src/presence-cache.h b/salut/src/presence-cache.h
new file mode 100644
index 000000000..98f73cbff
--- /dev/null
+++ b/salut/src/presence-cache.h
@@ -0,0 +1,87 @@
+/*
+ * presence-cache.h - Headers for Salut's contact presence cache
+ * Copyright (C) 2005-2008 Collabora Ltd.
+ * Copyright (C) 2005-2008 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_PRESENCE_CACHE_H__
+#define __SALUT_PRESENCE_CACHE_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "capabilities.h"
+#include "connection.h"
+#include "contact.h"
+
+G_BEGIN_DECLS
+
+#define SALUT_TYPE_PRESENCE_CACHE salut_presence_cache_get_type ()
+
+#define SALUT_PRESENCE_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ SALUT_TYPE_PRESENCE_CACHE, SalutPresenceCache))
+
+#define SALUT_PRESENCE_CACHE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ SALUT_TYPE_PRESENCE_CACHE, SalutPresenceCacheClass))
+
+#define SALUT_IS_PRESENCE_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ SALUT_TYPE_PRESENCE_CACHE))
+
+#define SALUT_IS_PRESENCE_CACHE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ SALUT_TYPE_PRESENCE_CACHE))
+
+#define SALUT_PRESENCE_CACHE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ SALUT_TYPE_PRESENCE_CACHE, SalutPresenceCacheClass))
+
+
+typedef struct _SalutPresenceCachePrivate SalutPresenceCachePrivate;
+
+struct _SalutPresenceCache {
+ GObject parent;
+ SalutPresenceCachePrivate *priv;
+};
+
+typedef struct _SalutPresenceCacheClass SalutPresenceCacheClass;
+
+struct _SalutPresenceCacheClass {
+ GObjectClass parent_class;
+};
+
+GType salut_presence_cache_get_type (void);
+
+SalutPresenceCache *salut_presence_cache_new (SalutConnection *connection);
+
+void salut_presence_cache_process_caps (SalutPresenceCache *self,
+ SalutContact *contact, const gchar *hash, const gchar *node,
+ const gchar *ver);
+
+void salut_presence_cache_learn_caps (SalutPresenceCache *self,
+ const gchar *node, const gchar *ver,
+ const GabbleCapabilitySet *caps, const GPtrArray *data_forms);
+
+/* Salut-specific pseudo-capability: llXMPP clients without XEP-0115 caps */
+#define QUIRK_NOT_XEP_CAPABILITIES QUIRK_PREFIX "not-xep-capabilities"
+
+G_END_DECLS
+
+#endif /* __SALUT_PRESENCE_CACHE_H__ */
+
diff --git a/salut/src/presence.h b/salut/src/presence.h
new file mode 100644
index 000000000..98dc1dd67
--- /dev/null
+++ b/salut/src/presence.h
@@ -0,0 +1,53 @@
+/*
+ * presence.h - Header for Salut Presence types
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_PRESENCE_H__
+#define __SALUT_PRESENCE_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/enums.h>
+
+G_BEGIN_DECLS
+
+#define SALUT_DNSSD_CLIQUE "_clique._udp"
+#define SALUT_DNSSD_OLPC_ACTIVITY "_olpc-activity1._udp"
+#define SALUT_DNSSD_PRESENCE "_presence._tcp"
+
+/* private structure */
+typedef struct {
+ const gchar *name;
+ const gchar *txt_name;
+ TpConnectionPresenceType presence_type;
+} SalutPresenceStatusInfo;
+
+
+typedef enum {
+ SALUT_PRESENCE_AVAILABLE,
+ SALUT_PRESENCE_AWAY,
+ SALUT_PRESENCE_DND,
+ SALUT_PRESENCE_OFFLINE, /* offline is a dummy, FIXME, check handling */
+ SALUT_PRESENCE_NR_PRESENCES
+} SalutPresenceId;
+
+/* Must be in the same order as the enum SalutPresenceId */
+extern const char *salut_presence_status_txt_names[];
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_PRESENCE_H__*/
diff --git a/salut/src/protocol.c b/salut/src/protocol.c
new file mode 100644
index 000000000..986d12e2b
--- /dev/null
+++ b/salut/src/protocol.c
@@ -0,0 +1,329 @@
+/*
+ * protocol.c - SalutProtocol
+ * Copyright © 2007-2010 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "protocol.h"
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-protocol.h>
+#include <telepathy-glib/base-connection-manager.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "connection.h"
+#include "contact-manager.h"
+#include "ft-manager.h"
+#include "im-manager.h"
+#include "muc-manager.h"
+#include "roomlist-manager.h"
+#include "tubes-manager.h"
+#include "avahi-discovery-client.h"
+
+/* there is no appropriate vCard field for this protocol */
+#define VCARD_FIELD_NAME ""
+
+G_DEFINE_TYPE (SalutProtocol,
+ salut_protocol,
+ TP_TYPE_BASE_PROTOCOL)
+
+enum {
+ PROP_BACKEND = 1,
+ PROP_DNSSD_NAME,
+ PROP_ENGLISH_NAME,
+ PROP_ICON_NAME
+};
+
+struct _SalutProtocolPrivate
+{
+ GType backend_type;
+ gchar *english_name;
+ gchar *icon_name;
+ gchar *dnssd_name;
+};
+
+static const TpCMParamSpec salut_params[] = {
+ { "nickname", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, 0,
+ tp_cm_param_filter_string_nonempty, NULL },
+ { "first-name", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_REQUIRED, NULL, 0 },
+ { "last-name", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_REQUIRED, NULL, 0 },
+ { "jid", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, 0 },
+ { "email", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, 0 },
+ { "published-name", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, 0,
+ tp_cm_param_filter_string_nonempty, NULL },
+ { NULL, NULL, 0, 0, NULL, 0 }
+};
+
+static void
+salut_protocol_init (SalutProtocol *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SALUT_TYPE_PROTOCOL,
+ SalutProtocolPrivate);
+}
+
+static const TpCMParamSpec *
+get_parameters (TpBaseProtocol *self G_GNUC_UNUSED)
+{
+ return salut_params;
+}
+
+static TpBaseConnection *
+new_connection (TpBaseProtocol *protocol,
+ GHashTable *params,
+ GError **error)
+{
+ SalutProtocol *self = SALUT_PROTOCOL (protocol);
+ GObject *obj;
+ guint i;
+
+ obj = g_object_new (SALUT_TYPE_CONNECTION,
+ "protocol", tp_base_protocol_get_name (protocol),
+ /* deliberately set :dnssd-name before backend-type */
+ "dnssd-name", self->priv->dnssd_name,
+ "backend-type", self->priv->backend_type,
+ NULL);
+
+ for (i = 0; salut_params[i].name != NULL; i++)
+ {
+ GValue *val = g_hash_table_lookup (params, salut_params[i].name);
+
+ if (val != NULL)
+ {
+ g_object_set_property (obj, salut_params[i].name, val);
+ }
+ }
+
+ return TP_BASE_CONNECTION (obj);
+}
+
+static gchar *
+normalize_contact (TpBaseProtocol *self G_GNUC_UNUSED,
+ const gchar *contact,
+ GError **error)
+{
+ return salut_normalize_non_empty (contact, error);
+}
+
+static gchar *
+identify_account (TpBaseProtocol *self G_GNUC_UNUSED,
+ GHashTable *asv,
+ GError **error)
+{
+ /* Nothing uniquely identifies a particular Salut account. The published
+ * name is part of our identifier, but can be changed at any time;
+ * the best an account manager can do is to number accounts. */
+ return g_strdup ("");
+}
+
+static GStrv
+get_interfaces (TpBaseProtocol *self)
+{
+ return g_new0 (gchar *, 1);
+}
+
+static void
+get_connection_details (TpBaseProtocol *self,
+ GStrv *connection_interfaces,
+ GType **channel_managers,
+ gchar **icon_name,
+ gchar **english_name,
+ gchar **vcard_field)
+{
+ SalutProtocolPrivate *priv = SALUT_PROTOCOL (self)->priv;
+
+ if (connection_interfaces != NULL)
+ {
+ *connection_interfaces = g_strdupv (
+ (GStrv) salut_connection_get_implemented_interfaces ());
+ }
+
+ if (channel_managers != NULL)
+ {
+ GType types[] = {
+ SALUT_TYPE_CONTACT_MANAGER,
+ SALUT_TYPE_FT_MANAGER,
+ SALUT_TYPE_IM_MANAGER,
+ SALUT_TYPE_MUC_MANAGER,
+ SALUT_TYPE_ROOMLIST_MANAGER,
+ SALUT_TYPE_TUBES_MANAGER,
+ G_TYPE_INVALID };
+
+ *channel_managers = g_memdup (types, sizeof(types));
+ }
+
+ if (icon_name != NULL)
+ {
+ *icon_name = g_strdup (priv->icon_name);
+ }
+
+ if (vcard_field != NULL)
+ {
+ *vcard_field = g_strdup (VCARD_FIELD_NAME);
+ }
+
+ if (english_name != NULL)
+ {
+ *english_name = g_strdup (priv->english_name);
+ }
+}
+
+static void
+salut_protocol_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutProtocol *self = SALUT_PROTOCOL (object);
+
+ switch (property_id)
+ {
+ case PROP_BACKEND:
+ g_value_set_gtype (value, self->priv->backend_type);
+ break;
+
+ case PROP_DNSSD_NAME:
+ g_value_set_string (value, self->priv->dnssd_name);
+ break;
+
+ case PROP_ENGLISH_NAME:
+ g_value_set_string (value, self->priv->english_name);
+ break;
+
+ case PROP_ICON_NAME:
+ g_value_set_string (value, self->priv->icon_name);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_protocol_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutProtocol *self = SALUT_PROTOCOL (object);
+
+ switch (property_id)
+ {
+ case PROP_BACKEND:
+ {
+ GType type = g_value_get_gtype (value);
+
+ if (type == G_TYPE_NONE)
+ type = SALUT_TYPE_AVAHI_DISCOVERY_CLIENT;
+
+ self->priv->backend_type = type;
+ }
+ break;
+
+ case PROP_DNSSD_NAME:
+ self->priv->dnssd_name = g_value_dup_string (value);
+ break;
+
+ case PROP_ENGLISH_NAME:
+ self->priv->english_name = g_value_dup_string (value);
+ break;
+
+ case PROP_ICON_NAME:
+ self->priv->icon_name = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_protocol_finalize (GObject *object)
+{
+ SalutProtocol *self = SALUT_PROTOCOL (object);
+
+ tp_clear_pointer (&self->priv->english_name, g_free);
+ tp_clear_pointer (&self->priv->icon_name, g_free);
+ tp_clear_pointer (&self->priv->dnssd_name, g_free);
+
+ if (G_OBJECT_CLASS (salut_protocol_parent_class)->finalize)
+ G_OBJECT_CLASS (salut_protocol_parent_class)->finalize (object);
+}
+
+
+static void
+salut_protocol_class_init (SalutProtocolClass *klass)
+{
+ TpBaseProtocolClass *base_class = (TpBaseProtocolClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (SalutProtocolPrivate));
+
+ base_class->get_parameters = get_parameters;
+ base_class->new_connection = new_connection;
+ base_class->normalize_contact = normalize_contact;
+ base_class->identify_account = identify_account;
+ base_class->get_interfaces = get_interfaces;
+ base_class->get_connection_details = get_connection_details;
+
+ object_class->get_property = salut_protocol_get_property;
+ object_class->set_property = salut_protocol_set_property;
+ object_class->finalize = salut_protocol_finalize;
+
+ param_spec = g_param_spec_gtype ("backend-type", "backend type",
+ "a G_TYPE_GTYPE of the backend to use", G_TYPE_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_BACKEND,
+ param_spec);
+
+ param_spec = g_param_spec_string ("dnssd-name", "DNS-SD name",
+ "The DNS-SD name of the protocol", "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DNSSD_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_string ("english-name", "English name",
+ "The English name of the protocol", "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ENGLISH_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_string ("icon-name", "Icon name",
+ "The icon name of the protocol", "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ICON_NAME,
+ param_spec);
+}
+
+TpBaseProtocol *
+salut_protocol_new (GType backend_type,
+ const gchar *dnssd_name,
+ const gchar *protocol_name,
+ const gchar *english_name,
+ const gchar *icon_name)
+{
+ return g_object_new (SALUT_TYPE_PROTOCOL,
+ "name", protocol_name,
+ "dnssd-name", dnssd_name,
+ "english-name", english_name,
+ "backend-type", backend_type,
+ "icon-name", icon_name,
+ NULL);
+}
diff --git a/salut/src/roomlist-channel.c b/salut/src/roomlist-channel.c
new file mode 100644
index 000000000..66ca70d95
--- /dev/null
+++ b/salut/src/roomlist-channel.c
@@ -0,0 +1,692 @@
+/*
+ * roomlist-channel.c - Source for SalutRoomlistChannel
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "roomlist-channel.h"
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "connection.h"
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+#define DEBUG_FLAG DEBUG_ROOMLIST
+#include "debug.h"
+
+static void channel_iface_init (gpointer, gpointer);
+static void roomlist_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (SalutRoomlistChannel, salut_roomlist_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_ROOM_LIST,
+ roomlist_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL)
+ );
+
+static const char *salut_roomlist_channel_interfaces[] = { NULL };
+
+/* properties */
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_CONFERENCE_SERVER,
+ PROP_TARGET_ID,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_REQUESTED,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutRoomlistChannelPrivate SalutRoomlistChannelPrivate;
+
+struct _SalutRoomlistChannelPrivate
+{
+ SalutConnection *connection;
+ gchar *object_path;
+
+ GPtrArray *rooms;
+
+ gboolean closed;
+ gboolean dispose_has_run;
+};
+
+#define SALUT_ROOMLIST_CHANNEL_GET_PRIVATE(obj) \
+ ((SalutRoomlistChannelPrivate *) ((SalutRoomlistChannel *) obj)->priv)
+
+static void
+salut_roomlist_channel_init (SalutRoomlistChannel *self)
+{
+ SalutRoomlistChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_ROOMLIST_CHANNEL, SalutRoomlistChannelPrivate);
+
+ self->priv = priv;
+
+ priv->rooms = g_ptr_array_new ();
+}
+
+static GObject *
+salut_roomlist_channel_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutRoomlistChannelPrivate *priv;
+ TpDBusDaemon *bus;
+ TpBaseConnection *base_conn;
+
+ obj = G_OBJECT_CLASS (salut_roomlist_channel_parent_class)->constructor (
+ type, n_props, props);
+ priv = SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (SALUT_ROOMLIST_CHANNEL (obj));
+
+ base_conn = TP_BASE_CONNECTION (priv->connection);
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+
+ return obj;
+}
+
+static void
+salut_roomlist_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutRoomlistChannel *chan = SALUT_ROOMLIST_CHANNEL (object);
+ SalutRoomlistChannelPrivate *priv =
+ SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (chan);
+ TpBaseConnection *conn = (TpBaseConnection *) priv->connection;
+
+ switch (property_id) {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_ROOM_LIST);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_NONE);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->connection);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_static_boxed (value, salut_roomlist_channel_interfaces);
+ break;
+ case PROP_CONFERENCE_SERVER:
+ /* Salut does not use a server, so this string is always empty */
+ g_value_set_string (value, "");
+ break;
+ case PROP_TARGET_ID:
+ g_value_set_static_string (value, "");
+ break;
+ case PROP_INITIATOR_HANDLE:
+ /* Room listing is always initiated by the local user */
+ g_value_set_uint (value, conn->self_handle);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (repo, conn->self_handle));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_TYPE_ROOM_LIST, "Server",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_roomlist_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutRoomlistChannel *chan = SALUT_ROOMLIST_CHANNEL (object);
+ SalutRoomlistChannelPrivate *priv =
+ SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (chan);
+ const gchar *value_str;
+
+ switch (property_id) {
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_CHANNEL_TYPE:
+ /* this property is writable in the interface (in
+ * telepathy-glib > 0.7.0), but not actually
+ * meaningfully changeable on this channel, so we do nothing */
+ value_str = g_value_get_string (value);
+ g_assert (value_str == NULL || !tp_strdiff (value_str,
+ TP_IFACE_CHANNEL_TYPE_ROOM_LIST));
+ break;
+ case PROP_HANDLE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ g_assert (g_value_get_uint (value) == 0);
+ break;
+ case PROP_HANDLE_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing.
+ * */
+ g_assert (g_value_get_uint (value) == TP_HANDLE_TYPE_NONE
+ || g_value_get_uint (value) == TP_UNKNOWN_HANDLE_TYPE);
+ break;
+ case PROP_CONNECTION:
+ priv->connection = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void salut_roomlist_channel_dispose (GObject *object);
+static void salut_roomlist_channel_finalize (GObject *object);
+
+static void
+salut_roomlist_channel_class_init (
+ SalutRoomlistChannelClass *salut_roomlist_channel_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_roomlist_channel_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "TargetID", "target-id", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { "Requested", "requested", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl roomlist_props[] = {
+ { "Server", "conference-server", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { TP_IFACE_CHANNEL_TYPE_ROOM_LIST,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ roomlist_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (salut_roomlist_channel_class,
+ sizeof (SalutRoomlistChannelPrivate));
+
+ object_class->constructor = salut_roomlist_channel_constructor;
+
+ object_class->get_property = salut_roomlist_channel_get_property;
+ object_class->set_property = salut_roomlist_channel_set_property;
+
+ object_class->dispose = salut_roomlist_channel_dispose;
+ object_class->finalize = salut_roomlist_channel_finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE,
+ "handle");
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Target JID",
+ "The string obtained by inspecting this channel's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's bare JID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_object ("connection", "SalutConnection object",
+ "Salut connection object that owns this "
+ "room list channel object.",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_string ("conference-server",
+ "Name of conference server to use",
+ "Name of conference server to use, which is an empty string for Salut",
+ "",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONFERENCE_SERVER,
+ param_spec);
+
+ salut_roomlist_channel_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutRoomlistChannelClass, dbus_props_class));
+
+}
+
+static void
+rooms_free (SalutRoomlistChannel *self)
+{
+ SalutRoomlistChannelPrivate *priv =
+ SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = (TpBaseConnection *) (priv->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ guint i;
+
+ g_assert (priv->rooms != NULL);
+
+ for (i = 0; i < priv->rooms->len; i++)
+ {
+ GValue room = {0,};
+ gpointer boxed;
+ TpHandle handle;
+
+ boxed = g_ptr_array_index (priv->rooms, i);
+ g_value_init (&room, TP_STRUCT_TYPE_ROOM_INFO);
+ g_value_set_static_boxed (&room, boxed);
+ dbus_g_type_struct_get (&room,
+ 0, &handle,
+ G_MAXUINT);
+
+ g_boxed_free (TP_STRUCT_TYPE_ROOM_INFO, boxed);
+ tp_handle_unref (room_repo, handle);
+ }
+
+ g_ptr_array_unref (priv->rooms);
+ priv->rooms = NULL;
+}
+
+static void
+salut_roomlist_channel_dispose (GObject *object)
+{
+ SalutRoomlistChannel *self = SALUT_ROOMLIST_CHANNEL (object);
+ SalutRoomlistChannelPrivate *priv =
+ SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (!priv->closed)
+ {
+ priv->closed = TRUE;
+ tp_svc_channel_emit_closed ((TpSvcChannel *) object);
+ }
+
+ if (G_OBJECT_CLASS (salut_roomlist_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_roomlist_channel_parent_class)->dispose (object);
+}
+
+static void
+salut_roomlist_channel_finalize (GObject *object)
+{
+ SalutRoomlistChannel *self = SALUT_ROOMLIST_CHANNEL (object);
+ SalutRoomlistChannelPrivate *priv =
+ SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+
+ g_free (priv->object_path);
+
+ if (priv->rooms != NULL)
+ rooms_free (self);
+
+ G_OBJECT_CLASS (salut_roomlist_channel_parent_class)->finalize (object);
+}
+
+SalutRoomlistChannel *
+salut_roomlist_channel_new (SalutConnection *conn,
+ const gchar *object_path)
+{
+ g_return_val_if_fail (SALUT_IS_CONNECTION (conn), NULL);
+ g_return_val_if_fail (object_path != NULL, NULL);
+
+ return SALUT_ROOMLIST_CHANNEL (
+ g_object_new (SALUT_TYPE_ROOMLIST_CHANNEL,
+ "connection", conn,
+ "object-path", object_path,
+ NULL));
+}
+
+void
+salut_roomlist_channel_add_room (SalutRoomlistChannel *self,
+ const gchar *room_name)
+{
+ SalutRoomlistChannelPrivate *priv =
+ SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = (TpBaseConnection *) (priv->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ GValue room = {0,};
+ TpHandle handle;
+ GHashTable *keys;
+ GValue handle_name = {0,};
+
+ handle = tp_handle_ensure (room_repo, room_name, NULL, NULL);
+ if (handle == 0)
+ return;
+
+ keys = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+
+ /* handle-name */
+ g_value_init (&handle_name, G_TYPE_STRING);
+ g_value_take_string (&handle_name, (gchar *) room_name);
+ g_hash_table_insert (keys, "handle-name", &handle_name);
+
+ g_value_init (&room, TP_STRUCT_TYPE_ROOM_INFO);
+ g_value_take_boxed (&room,
+ dbus_g_type_specialized_construct (TP_STRUCT_TYPE_ROOM_INFO));
+ dbus_g_type_struct_set (&room,
+ 0, handle,
+ 1, "org.freedesktop.Telepathy.Channel.Type.Text",
+ 2, keys,
+ G_MAXUINT);
+ g_ptr_array_add (priv->rooms, g_value_get_boxed (&room));
+ g_hash_table_unref (keys);
+
+ DEBUG ("add room %s", room_name);
+}
+
+void
+salut_roomlist_channel_remove_room (SalutRoomlistChannel *self,
+ const gchar *room_name)
+{
+ SalutRoomlistChannelPrivate *priv =
+ SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = (TpBaseConnection *) (priv->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ TpHandle handle;
+ guint i;
+
+ handle = tp_handle_lookup (room_repo, room_name, NULL, NULL);
+ if (handle == 0)
+ return;
+
+ for (i = 0; i < priv->rooms->len; i++)
+ {
+ GValue room = {0,};
+ gpointer boxed;
+ TpHandle h;
+
+ boxed = g_ptr_array_index (priv->rooms, i);
+ g_value_init (&room, TP_STRUCT_TYPE_ROOM_INFO);
+ g_value_set_static_boxed (&room, boxed);
+ dbus_g_type_struct_get (&room,
+ 0, &h,
+ G_MAXUINT);
+
+ if (handle == h)
+ {
+ g_boxed_free (TP_STRUCT_TYPE_ROOM_INFO, boxed);
+ g_ptr_array_remove_index_fast (priv->rooms, i);
+ tp_handle_unref (room_repo, handle);
+ DEBUG ("remove %s", room_name);
+ break;
+ }
+ }
+}
+
+/************************* D-Bus Method definitions **************************/
+
+/**
+ * salut_roomlist_channel_close
+ *
+ * Implements D-Bus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_roomlist_channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutRoomlistChannel *self = SALUT_ROOMLIST_CHANNEL (iface);
+ SalutRoomlistChannelPrivate *priv =
+ SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (self);
+ g_assert (SALUT_IS_ROOMLIST_CHANNEL (self));
+
+ DEBUG ("called on %p", self);
+
+ tp_svc_channel_emit_closed (iface);
+ priv->closed = TRUE;
+
+ tp_svc_channel_return_from_close (context);
+}
+
+
+/**
+ * salut_roomlist_channel_get_channel_type
+ *
+ * Implements D-Bus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_roomlist_channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_ROOM_LIST);
+}
+
+
+/**
+ * salut_roomlist_channel_get_handle
+ *
+ * Implements D-Bus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_roomlist_channel_get_handle (TpSvcChannel *self,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_handle (context, 0, 0);
+}
+
+
+/**
+ * salut_roomlist_channel_get_interfaces
+ *
+ * Implements D-Bus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_roomlist_channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_roomlist_channel_interfaces);
+}
+
+
+/**
+ * salut_roomlist_channel_get_listing_rooms
+ *
+ * Implements D-Bus method GetListingRooms
+ * on interface org.freedesktop.Telepathy.Channel.Type.RoomList
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_roomlist_channel_get_listing_rooms (TpSvcChannelTypeRoomList *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutRoomlistChannel *self = SALUT_ROOMLIST_CHANNEL (iface);
+
+ g_assert (SALUT_IS_ROOMLIST_CHANNEL (self));
+
+ tp_svc_channel_type_room_list_return_from_get_listing_rooms (
+ context, FALSE);
+}
+
+
+/**
+ * salut_roomlist_channel_list_rooms
+ *
+ * Implements D-Bus method ListRooms
+ * on interface org.freedesktop.Telepathy.Channel.Type.RoomList
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_roomlist_channel_list_rooms (TpSvcChannelTypeRoomList *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutRoomlistChannel *self = SALUT_ROOMLIST_CHANNEL (iface);
+ SalutRoomlistChannelPrivate *priv;
+
+ g_assert (SALUT_IS_ROOMLIST_CHANNEL (self));
+
+ priv = SALUT_ROOMLIST_CHANNEL_GET_PRIVATE (self);
+
+ tp_svc_channel_type_room_list_emit_listing_rooms (iface, TRUE);
+ tp_svc_channel_type_room_list_emit_got_rooms (iface, priv->rooms);
+ tp_svc_channel_type_room_list_emit_listing_rooms (iface, FALSE);
+
+ tp_svc_channel_type_room_list_return_from_list_rooms (context);
+}
+
+/**
+ * salut_roomlist_channel_stop_listing
+ *
+ * Implements D-Bus method StopListing
+ * on interface org.freedesktop.Telepathy.Channel.Type.RoomList
+ */
+static void
+salut_roomlist_channel_stop_listing (TpSvcChannelTypeRoomList *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_type_room_list_return_from_stop_listing (context);
+}
+
+static void
+channel_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
+ klass, salut_roomlist_channel_##x)
+ IMPLEMENT(close);
+ IMPLEMENT(get_channel_type);
+ IMPLEMENT(get_handle);
+ IMPLEMENT(get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+roomlist_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelTypeRoomListClass *klass =
+ (TpSvcChannelTypeRoomListClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_type_room_list_implement_##x (\
+ klass, salut_roomlist_channel_##x)
+ IMPLEMENT(get_listing_rooms);
+ IMPLEMENT(list_rooms);
+ IMPLEMENT(stop_listing);
+#undef IMPLEMENT
+}
diff --git a/salut/src/roomlist-channel.h b/salut/src/roomlist-channel.h
new file mode 100644
index 000000000..6bb64a95b
--- /dev/null
+++ b/salut/src/roomlist-channel.h
@@ -0,0 +1,76 @@
+/*
+ * roomlist-channel.h - Header for SalutRoomlistChannel
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_ROOMLIST_CHANNEL_H__
+#define __SALUT_ROOMLIST_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include "connection.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutRoomlistChannel SalutRoomlistChannel;
+typedef struct _SalutRoomlistChannelClass SalutRoomlistChannelClass;
+
+struct _SalutRoomlistChannelClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _SalutRoomlistChannel {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType salut_roomlist_channel_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_ROOMLIST_CHANNEL \
+ (salut_roomlist_channel_get_type ())
+#define SALUT_ROOMLIST_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_ROOMLIST_CHANNEL,\
+ SalutRoomlistChannel))
+#define SALUT_ROOMLIST_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_ROOMLIST_CHANNEL,\
+ SalutRoomlistChannelClass))
+#define SALUT_IS_ROOMLIST_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_ROOMLIST_CHANNEL))
+#define SALUT_IS_ROOMLIST_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_ROOMLIST_CHANNEL))
+#define SALUT_ROOMLIST_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_ROOMLIST_CHANNEL,\
+ SalutRoomlistChannelClass))
+
+
+SalutRoomlistChannel *
+salut_roomlist_channel_new (SalutConnection *conn, const gchar *object_path);
+
+void
+salut_roomlist_channel_add_room (SalutRoomlistChannel *self,
+ const gchar *room_name);
+
+void
+salut_roomlist_channel_remove_room (SalutRoomlistChannel *self,
+ const gchar *room_name);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_ROOMLIST_CHANNEL_H__*/
diff --git a/salut/src/roomlist-manager.c b/salut/src/roomlist-manager.c
new file mode 100644
index 000000000..91709586f
--- /dev/null
+++ b/salut/src/roomlist-manager.c
@@ -0,0 +1,549 @@
+/*
+ * roomlist-manager.c - Source for SalutRoomlistManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "roomlist-manager.h"
+
+#include <wocky/wocky-namespaces.h>
+
+#include <gibber/gibber-muc-connection.h>
+
+#include <salut/caps-channel-manager.h>
+
+#include "roomlist-channel.h"
+#include "contact-manager.h"
+#include "muc-manager.h"
+#include "tubes-channel.h"
+#include "roomlist-channel.h"
+#include "discovery-client.h"
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+static void salut_roomlist_manager_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE(SalutRoomlistManager, salut_roomlist_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ salut_roomlist_manager_iface_init);
+ G_IMPLEMENT_INTERFACE (
+ GABBLE_TYPE_CAPS_CHANNEL_MANAGER, NULL))
+
+/* properties */
+enum {
+ PROP_CONNECTION = 1,
+ LAST_PROP
+};
+
+/* private structure */
+typedef struct _SalutRoomlistManagerPrivate SalutRoomlistManagerPrivate;
+
+struct _SalutRoomlistManagerPrivate
+{
+ SalutConnection *connection;
+ gulong status_changed_id;
+
+ GSList *roomlist_channels;
+
+ /* Map from channels to the request-tokens of requests that they will
+ * satisfy when they're ready.
+ * Borrowed TpExportableChannel => GSList of gpointer */
+ GHashTable *queued_requests;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_ROOMLIST_MANAGER_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_ROOMLIST_MANAGER, \
+ SalutRoomlistManagerPrivate))
+
+static void
+salut_roomlist_manager_init (SalutRoomlistManager *obj)
+{
+}
+
+static void
+salut_roomlist_manager_get_property (GObject *object,
+ guint property_id, GValue *value, GParamSpec *pspec)
+{
+ SalutRoomlistManager *self = SALUT_ROOMLIST_MANAGER (object);
+ SalutRoomlistManagerPrivate *priv = SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->connection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_roomlist_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutRoomlistManager *self = SALUT_ROOMLIST_MANAGER (object);
+ SalutRoomlistManagerPrivate *priv = SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->connection = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_roomlist_manager_close_all (SalutRoomlistManager *self)
+{
+ SalutRoomlistManagerPrivate *priv = SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ DEBUG ("closing channels");
+
+ if (priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (priv->connection, priv->status_changed_id);
+ priv->status_changed_id = 0;
+ }
+
+ if (priv->roomlist_channels != NULL)
+ {
+ GSList *l = priv->roomlist_channels;
+ priv->roomlist_channels = NULL;
+ g_slist_foreach (l, (GFunc) g_object_unref, NULL);
+ g_slist_free (l);
+ }
+}
+
+static void
+connection_status_changed_cb (SalutConnection *conn,
+ guint status,
+ guint reason,
+ SalutRoomlistManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ salut_roomlist_manager_close_all (self);
+ break;
+ }
+}
+
+static GObject *
+salut_roomlist_manager_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutRoomlistManagerPrivate *priv;
+
+ obj = G_OBJECT_CLASS (salut_roomlist_manager_parent_class)->
+ constructor (type, n_props, props);
+
+ priv = SALUT_ROOMLIST_MANAGER_GET_PRIVATE (obj);
+
+ priv->status_changed_id = g_signal_connect (priv->connection,
+ "status-changed", (GCallback) connection_status_changed_cb, obj);
+
+ return obj;
+}
+
+static void salut_roomlist_manager_dispose (GObject *object);
+static void salut_roomlist_manager_finalize (GObject *object);
+
+static void
+salut_roomlist_manager_class_init (
+ SalutRoomlistManagerClass *salut_roomlist_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_roomlist_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_roomlist_manager_class,
+ sizeof (SalutRoomlistManagerPrivate));
+
+ object_class->get_property = salut_roomlist_manager_get_property;
+ object_class->set_property = salut_roomlist_manager_set_property;
+
+ object_class->constructor = salut_roomlist_manager_constructor;
+ object_class->dispose = salut_roomlist_manager_dispose;
+ object_class->finalize = salut_roomlist_manager_finalize;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "The Salut Connection associated with this roomlist manager",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ param_spec);
+}
+
+void
+salut_roomlist_manager_dispose (GObject *object)
+{
+ SalutRoomlistManager *self = SALUT_ROOMLIST_MANAGER (object);
+ SalutRoomlistManagerPrivate *priv = SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ salut_roomlist_manager_close_all (self);
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (salut_roomlist_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_roomlist_manager_parent_class)->dispose (object);
+}
+
+void
+salut_roomlist_manager_finalize (GObject *object)
+{
+ /*SalutRoomlistManager *self = SALUT_ROOMLIST_MANAGER (object);*/
+ /*SalutRoomlistManagerPrivate *priv = SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);*/
+
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (salut_roomlist_manager_parent_class)->finalize (object);
+}
+
+/* Channel Factory interface */
+
+struct _ForeachData
+{
+ TpExportableChannelFunc foreach;
+ gpointer user_data;
+};
+
+
+static void
+salut_roomlist_manager_foreach_one_list (TpChannelManager *chan,
+ gpointer user_data)
+{
+ struct _ForeachData *data = (struct _ForeachData *) user_data;
+ TpExportableChannel *channel = TP_EXPORTABLE_CHANNEL (chan);
+
+ data->foreach (channel, data->user_data);
+}
+
+
+static void
+salut_roomlist_manager_foreach_channel (TpChannelManager *iface,
+ TpExportableChannelFunc foreach,
+ gpointer user_data)
+{
+ SalutRoomlistManager *fac = SALUT_ROOMLIST_MANAGER (iface);
+ SalutRoomlistManagerPrivate *priv = SALUT_ROOMLIST_MANAGER_GET_PRIVATE (fac);
+ struct _ForeachData data;
+
+ data.user_data = user_data;
+ data.foreach = foreach;
+
+ g_slist_foreach (priv->roomlist_channels,
+ (GFunc) salut_roomlist_manager_foreach_one_list, &data);
+}
+
+static const gchar * const roomlist_channel_fixed_properties[] = {
+ TP_IFACE_CHANNEL ".ChannelType",
+ TP_IFACE_CHANNEL ".TargetHandleType",
+ NULL
+};
+
+static const gchar * const roomlist_channel_allowed_properties[] = {
+ NULL
+};
+
+
+static void
+salut_roomlist_manager_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ GValue *channel_type_value, *handle_type_value;
+
+ channel_type_value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (channel_type_value,
+ TP_IFACE_CHANNEL_TYPE_ROOM_LIST);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType",
+ channel_type_value);
+
+ handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_NONE);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
+ handle_type_value);
+
+ func (type, table, roomlist_channel_allowed_properties,
+ user_data);
+
+ g_hash_table_unref (table);
+}
+
+
+static void
+roomlist_channel_closed_cb (SalutRoomlistChannel *channel,
+ gpointer user_data)
+{
+ SalutRoomlistManager *self = SALUT_ROOMLIST_MANAGER (user_data);
+ SalutRoomlistManagerPrivate *priv =
+ SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (channel));
+
+ if (priv->roomlist_channels != NULL)
+ {
+ priv->roomlist_channels = g_slist_remove (priv->roomlist_channels,
+ channel);
+ g_object_unref (channel);
+ }
+}
+
+
+static SalutRoomlistChannel *
+make_roomlist_channel (SalutRoomlistManager *self)
+{
+ SalutRoomlistManagerPrivate *priv =
+ SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *conn = TP_BASE_CONNECTION (priv->connection);
+ SalutRoomlistChannel *roomlist_channel;
+ gchar *object_path;
+ static guint cpt = 0;
+ GSList *rooms, *l;
+
+ /* FIXME: this is not optimal as all the Connection will share the same cpt
+ * and we could have problem if we overflow the guint. */
+ object_path = g_strdup_printf ("%s/RoomlistChannel%u",
+ conn->object_path, cpt++);
+
+ roomlist_channel = salut_roomlist_channel_new (priv->connection,
+ object_path);
+
+ rooms = SALUT_ROOMLIST_MANAGER_GET_CLASS (self)->get_rooms (self);
+ for (l = rooms; l != NULL; l = g_slist_next (l))
+ {
+ const gchar *room_name = l->data;
+
+ salut_roomlist_channel_add_room (roomlist_channel, room_name);
+ }
+
+ priv->roomlist_channels = g_slist_prepend (priv->roomlist_channels,
+ g_object_ref (roomlist_channel));
+
+ g_signal_connect (roomlist_channel, "closed",
+ (GCallback) roomlist_channel_closed_cb, self);
+
+ g_free (object_path);
+ return roomlist_channel;
+}
+
+
+static gboolean
+salut_roomlist_manager_request (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ SalutRoomlistManager *self = SALUT_ROOMLIST_MANAGER (manager);
+ SalutRoomlistManagerPrivate *priv =
+ SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+ SalutRoomlistChannel *roomlist_channel = NULL;
+ GError *error = NULL;
+ GSList *request_tokens;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType"),
+ TP_IFACE_CHANNEL_TYPE_ROOM_LIST))
+ return FALSE;
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL) != 0)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "RoomList channels can't have a target handle");
+ goto error;
+ }
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ roomlist_channel_fixed_properties,
+ roomlist_channel_allowed_properties,
+ &error))
+ goto error;
+
+ if (!require_new && priv->roomlist_channels != NULL)
+ {
+ /* reuse the first channel */
+ roomlist_channel = priv->roomlist_channels->data;
+
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (roomlist_channel));
+ return TRUE;
+ }
+
+ roomlist_channel = make_roomlist_channel (self);
+
+ g_signal_connect (roomlist_channel, "closed",
+ (GCallback) roomlist_channel_closed_cb, self);
+ priv->roomlist_channels = g_slist_prepend (priv->roomlist_channels,
+ roomlist_channel);
+
+ request_tokens = g_slist_prepend (NULL, request_token);
+ tp_channel_manager_emit_new_channel (self,
+ TP_EXPORTABLE_CHANNEL (roomlist_channel), request_tokens);
+ g_slist_free (request_tokens);
+
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+
+static gboolean
+salut_roomlist_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return salut_roomlist_manager_request (manager, request_token,
+ request_properties, TRUE);
+}
+
+
+static gboolean
+salut_roomlist_manager_request_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return salut_roomlist_manager_request (manager, request_token,
+ request_properties, FALSE);
+}
+
+
+static gboolean
+salut_roomlist_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return salut_roomlist_manager_request (manager, request_token,
+ request_properties, FALSE);
+}
+
+
+static void salut_roomlist_manager_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = salut_roomlist_manager_foreach_channel;
+ iface->type_foreach_channel_class =
+ salut_roomlist_manager_type_foreach_channel_class;
+ iface->request_channel = salut_roomlist_manager_request_channel;
+ iface->create_channel = salut_roomlist_manager_create_channel;
+ iface->ensure_channel = salut_roomlist_manager_ensure_channel;
+}
+
+/* public functions */
+
+gboolean
+salut_roomlist_manager_start (SalutRoomlistManager *self,
+ GError **error)
+{
+ return SALUT_ROOMLIST_MANAGER_GET_CLASS (self)->start (self, error);
+}
+
+static void
+add_room_foreach (SalutRoomlistChannel *roomlist_channel,
+ const gchar *room)
+{
+ salut_roomlist_channel_add_room (roomlist_channel, room);
+}
+
+void
+salut_roomlist_manager_room_discovered (SalutRoomlistManager *self,
+ const gchar *room)
+{
+ SalutRoomlistManagerPrivate *priv =
+ SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+
+ g_slist_foreach (priv->roomlist_channels, (GFunc) add_room_foreach,
+ (gchar *) room);
+}
+
+static void
+remove_room_foreach (SalutRoomlistChannel *roomlist_channel,
+ const gchar *room)
+{
+ salut_roomlist_channel_remove_room (roomlist_channel, room);
+}
+
+void
+salut_roomlist_manager_room_removed (SalutRoomlistManager *self,
+ const gchar *room)
+{
+ SalutRoomlistManagerPrivate *priv = SALUT_ROOMLIST_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (priv->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ TpHandle handle;
+ SalutMucChannel *muc;
+ SalutMucManager *muc_manager;
+
+ g_slist_foreach (priv->roomlist_channels, (GFunc) remove_room_foreach,
+ (gchar *) room);
+
+ /* Do we have to re-announce this room ? */
+ handle = tp_handle_lookup (room_repo, room, NULL, NULL);
+ if (handle == 0)
+ return;
+
+ g_object_get (priv->connection, "muc-manager", &muc_manager, NULL);
+ g_assert (muc_manager != NULL);
+
+ muc = salut_muc_manager_get_text_channel (muc_manager, handle);
+ if (muc == NULL)
+ return;
+
+ DEBUG ("We know this room %s. Try to re-announce it", room);
+ salut_muc_channel_publish_service (muc);
+ g_object_unref (muc);
+}
diff --git a/salut/src/roomlist-manager.h b/salut/src/roomlist-manager.h
new file mode 100644
index 000000000..eb0874391
--- /dev/null
+++ b/salut/src/roomlist-manager.h
@@ -0,0 +1,86 @@
+/*
+ * roomlist-manager.h - Header for SalutRoomlistManager
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_ROOMLIST_MANAGER_H__
+#define __SALUT_ROOMLIST_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <gibber/gibber-bytestream-iface.h>
+
+#include <connection.h>
+#include "tubes-channel.h"
+#include "roomlist-channel.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutRoomlistManager SalutRoomlistManager;
+typedef struct _SalutRoomlistManagerClass SalutRoomlistManagerClass;
+
+struct _SalutRoomlistManagerClass {
+ GObjectClass parent_class;
+
+ /* public abstract methods */
+ gboolean (*start) (SalutRoomlistManager *self, GError **error);
+
+ /* private abstract methods */
+ gboolean (*find_muc_address) (SalutRoomlistManager *self, const gchar *name,
+ gchar **address, guint16 *port);
+
+ GSList * (*get_rooms) (SalutRoomlistManager *self);
+};
+
+struct _SalutRoomlistManager {
+ GObject parent;
+};
+
+GType salut_roomlist_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_ROOMLIST_MANAGER \
+ (salut_roomlist_manager_get_type ())
+#define SALUT_ROOMLIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_ROOMLIST_MANAGER, \
+ SalutRoomlistManager))
+#define SALUT_ROOMLIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_ROOMLIST_MANAGER, \
+ SalutRoomlistManagerClass))
+#define SALUT_IS_ROOMLIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_ROOMLIST_MANAGER))
+#define SALUT_IS_ROOMLIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_ROOMLIST_MANAGER))
+#define SALUT_ROOMLIST_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_ROOMLIST_MANAGER, \
+ SalutRoomlistManagerClass))
+
+gboolean
+salut_roomlist_manager_start (SalutRoomlistManager *roomlist_manager,
+ GError **error);
+
+/* "protected" methods */
+void salut_roomlist_manager_room_discovered (
+ SalutRoomlistManager *roomlist_manager, const gchar *room);
+
+void salut_roomlist_manager_room_removed (
+ SalutRoomlistManager *roomlist_manager, const gchar *room);
+
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_ROOMLIST_MANAGER_H__*/
diff --git a/salut/src/salut.c b/salut/src/salut.c
new file mode 100644
index 000000000..f2574147c
--- /dev/null
+++ b/salut/src/salut.c
@@ -0,0 +1,63 @@
+#include "config.h"
+
+#include <glib.h>
+
+#include <telepathy-glib/run.h>
+#include <telepathy-glib/debug.h>
+
+#include "connection-manager.h"
+#include "avahi-discovery-client.h"
+#include "debug.h"
+#include "plugin-loader.h"
+#include "symbol-hacks.h"
+
+static TpBaseConnectionManager *
+salut_create_connection_manager (void)
+{
+ return TP_BASE_CONNECTION_MANAGER (
+ g_object_new (SALUT_TYPE_CONNECTION_MANAGER,
+ "backend-type", SALUT_TYPE_AVAHI_DISCOVERY_CLIENT,
+ NULL));
+}
+
+int
+#ifdef BUILD_AS_ANDROID_SERVICE
+salut_main (int argc, char **argv)
+#else
+main (int argc, char **argv)
+#endif
+{
+ GLogLevelFlags fatal_mask;
+ gint ret;
+ SalutPluginLoader *loader;
+
+ g_type_init ();
+ g_thread_init (NULL);
+
+ salut_symbol_hacks ();
+
+ /* treat criticals as, well, critical */
+ fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
+ fatal_mask |= G_LOG_LEVEL_CRITICAL;
+ g_log_set_always_fatal (fatal_mask);
+
+#ifdef ENABLE_DEBUG
+ tp_debug_divert_messages (g_getenv ("SALUT_LOGFILE"));
+ debug_set_flags_from_env ();
+
+ if (g_getenv ("SALUT_PERSIST"))
+ tp_debug_set_persistent (TRUE);
+#endif
+
+ loader = salut_plugin_loader_dup ();
+
+ ret = tp_run_connection_manager ("telepathy-salut", VERSION,
+ salut_create_connection_manager,
+ argc, argv);
+
+ g_object_unref (loader);
+
+ return ret;
+}
+
+
diff --git a/salut/src/self.c b/salut/src/self.c
new file mode 100644
index 000000000..62b94274b
--- /dev/null
+++ b/salut/src/self.c
@@ -0,0 +1,1081 @@
+/*
+ * self.c - Source for SalutSelf
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include "self.h"
+
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-xep-0115-capabilities.h>
+
+#include <gibber/gibber-linklocal-transport.h>
+#include <gibber/gibber-muc-connection.h>
+
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/util.h>
+
+#include "capabilities.h"
+#include "contact-manager.h"
+#include "util.h"
+#include "muc-manager.h"
+
+#ifdef ENABLE_OLPC
+#include "olpc-activity.h"
+#include "olpc-activity-manager.h"
+#endif
+
+#define DEBUG_FLAG DEBUG_SELF
+#include <debug.h>
+
+static void xep_0115_capabilities_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (SalutSelf, salut_self, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (WOCKY_TYPE_XEP_0115_CAPABILITIES,
+ xep_0115_capabilities_iface_init);
+)
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_NICKNAME,
+ PROP_FIRST_NAME,
+ PROP_LAST_NAME,
+ PROP_JID,
+ PROP_EMAIL,
+ PROP_PUBLISHED_NAME,
+#ifdef ENABLE_OLPC
+ PROP_OLPC_KEY,
+ PROP_OLPC_COLOR
+#endif
+};
+
+/* signal enum */
+enum
+{
+ ESTABLISHED,
+ FAILURE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+
+struct _SalutSelfPrivate
+{
+ SalutContactManager *contact_manager;
+ TpHandleRepoIface *room_repo;
+#ifdef ENABLE_OLPC
+ SalutOlpcActivityManager *olpc_activity_manager;
+#endif
+
+ GIOChannel *listener;
+ guint io_watch_in;
+
+#ifdef ENABLE_OLPC
+ /* room handle owned by the SalutOlpcActivity -> SalutOlpcActivity */
+ GHashTable *olpc_activities;
+#endif
+
+ GabbleCapabilitySet *caps;
+ GPtrArray *data_forms;
+
+ gboolean dispose_has_run;
+};
+
+#ifdef ENABLE_OLPC
+void
+contact_manager_contact_change_cb (SalutContactManager *mgr,
+ SalutContact *contact, int changes, gpointer user_data);
+#endif
+
+static void
+salut_self_init (SalutSelf *obj)
+{
+ SalutSelfPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj, SALUT_TYPE_SELF,
+ SalutSelfPrivate);
+
+ obj->priv = priv;
+
+ /* allocate any data required by the object here */
+ obj->status = SALUT_PRESENCE_AVAILABLE;
+ obj->status_message = NULL;
+ obj->jid = NULL;
+#ifdef ENABLE_OLPC
+ obj->olpc_key = NULL;
+ obj->olpc_color = NULL;
+ obj->olpc_cur_act = NULL;
+ obj->olpc_cur_act_room = 0;
+#endif
+
+ obj->first_name = NULL;
+ obj->last_name = NULL;
+ obj->email = NULL;
+ obj->published_name = NULL;
+
+#ifdef ENABLE_OLPC
+ priv->olpc_activities = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) g_object_unref);
+#endif
+ priv->listener = NULL;
+}
+
+static void
+salut_self_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutSelf *self = SALUT_SELF (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->connection);
+ break;
+ case PROP_NICKNAME:
+ g_value_set_string (value, self->nickname);
+ break;
+ case PROP_FIRST_NAME:
+ g_value_set_string (value, self->first_name);
+ break;
+ case PROP_LAST_NAME:
+ g_value_set_string (value, self->last_name);
+ break;
+ case PROP_JID:
+ g_value_set_string (value, self->jid);
+ break;
+ case PROP_EMAIL:
+ g_value_set_string (value, self->email);
+ break;
+ case PROP_PUBLISHED_NAME:
+ g_value_set_string (value, self->published_name);
+ break;
+#ifdef ENABLE_OLPC
+ case PROP_OLPC_KEY:
+ g_value_set_pointer (value, self->olpc_key);
+ break;
+ case PROP_OLPC_COLOR:
+ g_value_set_string (value, self->olpc_color);
+ break;
+#endif
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_self_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutSelf *self = SALUT_SELF (object);
+#ifdef ENABLE_OLPC
+ GArray *arr;
+#endif
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ self->connection = g_value_get_object (value);
+ break;
+ case PROP_NICKNAME:
+ g_free (self->nickname);
+ self->nickname = g_value_dup_string (value);
+ break;
+ case PROP_FIRST_NAME:
+ g_free (self->first_name);
+ self->first_name = g_value_dup_string (value);
+ break;
+ case PROP_LAST_NAME:
+ g_free (self->last_name);
+ self->last_name = g_value_dup_string (value);
+ break;
+ case PROP_JID:
+ g_free (self->jid);
+ self->jid = g_value_dup_string (value);
+ break;
+ case PROP_EMAIL:
+ g_free (self->email);
+ self->email = g_value_dup_string (value);
+ break;
+ case PROP_PUBLISHED_NAME:
+ g_free (self->published_name);
+ self->published_name = g_value_dup_string (value);
+ break;
+#ifdef ENABLE_OLPC
+ case PROP_OLPC_KEY:
+ arr = g_value_get_pointer (value);
+ if (arr != NULL)
+ {
+ if (self->olpc_key != NULL)
+ g_array_unref (self->olpc_key);
+
+ self->olpc_key = g_array_sized_new (FALSE, FALSE, sizeof (guint8),
+ arr->len);
+ g_array_append_vals (self->olpc_key, arr->data, arr->len);
+ }
+ break;
+ case PROP_OLPC_COLOR:
+ g_free (self->olpc_color);
+ self->olpc_color = g_value_dup_string (value);
+ break;
+#endif
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+salut_self_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutSelf *self;
+ SalutSelfPrivate *priv;
+
+ obj = G_OBJECT_CLASS (salut_self_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_SELF (obj);
+ priv = self->priv;
+
+ g_assert (self->connection != NULL);
+ g_object_get (self->connection,
+ "contact-manager", &(priv->contact_manager),
+#ifdef ENABLE_OLPC
+ "olpc-activity-manager", &(priv->olpc_activity_manager),
+#endif
+ NULL);
+ g_assert (priv->contact_manager != NULL);
+#ifdef ENABLE_OLPC
+ g_assert (priv->olpc_activity_manager != NULL);
+#endif
+
+ priv->room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_ROOM);
+
+ /* Prefer using the nickname as alias */
+ if (self->nickname != NULL)
+ {
+ self->alias = g_strdup (self->nickname);
+ }
+ else
+ {
+ if (self->first_name != NULL)
+ {
+ if (self->last_name != NULL)
+ self->alias = g_strdup_printf ("%s %s", self->first_name,
+ self->last_name);
+ else
+ self->alias = g_strdup (self->first_name);
+ }
+ else if (self->last_name != NULL)
+ {
+ self->alias = g_strdup (self->last_name);
+ }
+ }
+
+#ifdef ENABLE_OLPC
+ g_signal_connect (priv->contact_manager, "contact-change",
+ G_CALLBACK (contact_manager_contact_change_cb), self);
+#endif
+
+ priv->caps = salut_dup_self_advertised_caps ();
+ priv->data_forms = g_ptr_array_new ();
+
+ return obj;
+}
+
+static void salut_self_dispose (GObject *object);
+static void salut_self_finalize (GObject *object);
+
+static void
+salut_self_class_init (SalutSelfClass *salut_self_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_self_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_self_class, sizeof (SalutSelfPrivate));
+
+ object_class->constructor = salut_self_constructor;
+ object_class->get_property = salut_self_get_property;
+ object_class->set_property = salut_self_set_property;
+
+ object_class->dispose = salut_self_dispose;
+ object_class->finalize = salut_self_finalize;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "The Salut Connection associated with this self object",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "nickname",
+ "the nickname",
+ "The nickname of the self user",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NICKNAME,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "first-name",
+ "the first name",
+ "The first name of the self user",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_FIRST_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "last-name",
+ "the last name",
+ "The last name of the self user",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LAST_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "jid",
+ "the jid",
+ "The jabber ID of the self user",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_JID,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "email",
+ "the email",
+ "The email of the self user",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_EMAIL,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "published-name",
+ "the published name",
+ "The name used to publish the presence service",
+ g_get_user_name (),
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PUBLISHED_NAME,
+ param_spec);
+
+#ifdef ENABLE_OLPC
+ param_spec = g_param_spec_pointer (
+ "olpc-key",
+ "the OLPC key",
+ "A pointer to a GArray containing the OLPC key",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OLPC_KEY,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "olpc-color",
+ "the OLPC color",
+ "The OLPC color of the self user",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OLPC_COLOR,
+ param_spec);
+#endif
+
+ signals[ESTABLISHED] =
+ g_signal_new ("established",
+ G_OBJECT_CLASS_TYPE (salut_self_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[FAILURE] =
+ g_signal_new ("failure",
+ G_OBJECT_CLASS_TYPE (salut_self_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 0);
+}
+
+void
+salut_self_dispose (GObject *object)
+{
+ SalutSelf *self = SALUT_SELF (object);
+ SalutSelfPrivate *priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ /* release any references held by the object here */
+
+ gabble_capability_set_free (self->priv->caps);
+
+ if (priv->contact_manager != NULL)
+ {
+ g_object_unref (priv->contact_manager);
+ priv->contact_manager = NULL;
+ }
+
+#ifdef ENABLE_OLPC
+ if (priv->olpc_activity_manager != NULL)
+ {
+ g_object_unref (priv->olpc_activity_manager);
+ priv->olpc_activity_manager = NULL;
+ }
+
+ if (priv->olpc_activities != NULL)
+ g_hash_table_unref (priv->olpc_activities);
+
+ if (self->olpc_cur_act_room != 0)
+ {
+ tp_handle_unref (priv->room_repo, self->olpc_cur_act_room);
+ self->olpc_cur_act_room = 0;
+ }
+#endif
+
+ priv->room_repo = NULL;
+
+ if (priv->listener)
+ {
+ g_io_channel_unref (priv->listener);
+ g_source_remove (priv->io_watch_in);
+ priv->listener = NULL;
+ }
+
+ if (priv->data_forms != NULL)
+ {
+ g_ptr_array_unref (priv->data_forms);
+ priv->data_forms = NULL;
+ }
+
+
+ if (G_OBJECT_CLASS (salut_self_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_self_parent_class)->dispose (object);
+}
+
+void
+salut_self_finalize (GObject *object)
+{
+ SalutSelf *self = SALUT_SELF (object);
+
+ /* free any data held directly by the object here */
+
+ g_free (self->jid);
+ g_free (self->name);
+
+ g_free (self->first_name);
+ g_free (self->last_name);
+ g_free (self->email);
+ g_free (self->published_name);
+ g_free (self->alias);
+#ifdef ENABLE_OLPC
+ if (self->olpc_key != NULL)
+ g_array_unref (self->olpc_key);
+ g_free (self->olpc_color);
+ g_free (self->olpc_cur_act);
+#endif
+ g_free (self->node);
+ g_free (self->hash);
+ g_free (self->ver);
+
+ G_OBJECT_CLASS (salut_self_parent_class)->finalize (object);
+}
+
+/* Start announcing our presence on the network */
+gboolean
+salut_self_announce (SalutSelf *self,
+ guint16 port,
+ GError **error)
+{
+ return SALUT_SELF_GET_CLASS (self)->announce (self, port, error);
+}
+
+gboolean
+salut_self_set_presence (SalutSelf *self, SalutPresenceId status,
+ const gchar *message, GError **error)
+{
+
+ g_assert (status < SALUT_PRESENCE_NR_PRESENCES);
+
+ self->status = status;
+ g_free (self->status_message);
+ if (tp_strdiff (message, ""))
+ self->status_message = g_strdup (message);
+ else
+ self->status_message = NULL;
+
+ return SALUT_SELF_GET_CLASS (self)->set_presence (self, error);
+}
+
+gboolean
+salut_self_set_caps (SalutSelf *self,
+ const gchar *node,
+ const gchar *hash,
+ const gchar *ver,
+ GError **error)
+{
+ gboolean out;
+
+ g_free (self->node);
+ self->node = g_strdup (node);
+ g_free (self->hash);
+ self->hash = g_strdup (hash);
+ g_free (self->ver);
+ self->ver = g_strdup (ver);
+
+ out = SALUT_SELF_GET_CLASS (self)->set_caps (self, error);
+
+ g_signal_emit_by_name (self, "capabilities-changed");
+
+ return out;
+}
+
+const gchar *
+salut_self_get_alias (SalutSelf *self)
+{
+ if (self->alias == NULL)
+ {
+ return self->name;
+ }
+ return self->alias;
+}
+
+gboolean
+salut_self_set_alias (SalutSelf *self, const gchar *alias, GError **error)
+{
+ gboolean ret;
+ GError *err = NULL;
+
+ g_free (self->alias);
+ g_free (self->nickname);
+ self->alias = g_strdup (alias);
+ self->nickname = g_strdup (alias);
+
+ ret = SALUT_SELF_GET_CLASS (self)->set_alias (self, &err);
+ if (!ret)
+ {
+ if (error != NULL)
+ *error = g_error_new_literal (TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ err->message);
+ g_error_free (err);
+ }
+ return ret;
+}
+
+static void
+salut_self_remove_avatar (SalutSelf *self)
+{
+ DEBUG ("Removing avatar");
+
+ SALUT_SELF_GET_CLASS (self)->remove_avatar (self);
+}
+
+gboolean
+salut_self_set_avatar (SalutSelf *self, guint8 *data,
+ gsize size, GError **error)
+{
+ gboolean ret = TRUE;
+ GError *err = NULL;
+
+ g_free (self->avatar_token);
+ self->avatar_token = NULL;
+
+ g_free (self->avatar);
+ self->avatar = NULL;
+
+ self->avatar_size = 0;
+
+ if (size == 0)
+ {
+ self->avatar_token = g_strdup ("");
+ salut_self_remove_avatar (self);
+ return TRUE;
+ }
+
+ ret = SALUT_SELF_GET_CLASS (self)->set_avatar (self, data, size, error);
+
+ if (!ret)
+ {
+ salut_self_remove_avatar (self);
+ if (error != NULL)
+ *error = g_error_new_literal (TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ err->message);
+ g_error_free (err);
+ }
+
+ return ret;
+}
+
+#ifdef ENABLE_OLPC
+
+gboolean
+salut_self_add_olpc_activity (SalutSelf *self, const gchar *activity_id,
+ TpHandle room, GError **error)
+{
+ SalutOlpcActivity *activity;
+
+ g_return_val_if_fail (SALUT_IS_SELF (self), FALSE);
+ g_return_val_if_fail (activity_id != NULL, FALSE);
+ g_return_val_if_fail (room != 0, FALSE);
+
+ if (strchr (activity_id, ':') != NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Activity IDs may not contain ':'");
+ return FALSE;
+ }
+
+ activity = salut_olpc_activity_manager_ensure_activity_by_room (
+ self->priv->olpc_activity_manager, room);
+
+ if (!salut_olpc_activity_joined (activity, error))
+ {
+ g_object_unref (activity);
+ return FALSE;
+ }
+
+ salut_olpc_activity_update (activity, room, activity_id, NULL, NULL, NULL,
+ NULL, activity->is_private);
+
+ g_hash_table_insert (self->priv->olpc_activities, GUINT_TO_POINTER (room),
+ activity);
+
+ return TRUE;
+}
+
+gboolean
+salut_self_remove_olpc_activity (SalutSelf *self, SalutOlpcActivity *activity)
+{
+ SalutSelfPrivate *priv = self->priv;
+
+ g_return_val_if_fail (activity != NULL, FALSE);
+
+ g_hash_table_remove (priv->olpc_activities,
+ GUINT_TO_POINTER (activity->room));
+
+ salut_olpc_activity_left (activity);
+ salut_olpc_activity_revoke_invitations (activity);
+
+ return TRUE;
+}
+
+struct _set_olpc_activities_ctx
+{
+ SalutSelf *self;
+ TpHandleRepoIface *room_repo;
+ GHashTable *olpc_activities;
+ GHashTable *room_to_act_id;
+ GError **error;
+};
+
+static void
+_set_olpc_activities_add (gpointer key, gpointer value, gpointer user_data)
+{
+ struct _set_olpc_activities_ctx *data = user_data;
+ SalutOlpcActivity *activity;
+ const gchar *id = (const gchar *) value;
+ TpHandle room = GPOINTER_TO_UINT (key);
+
+ if (*(data->error) != NULL)
+ {
+ /* we already lost */
+ return;
+ }
+
+ activity = g_hash_table_lookup (data->olpc_activities, key);
+ if (activity == NULL)
+ {
+ /* add the activity service if it's not in data->olpc_activities */
+ if (!salut_self_add_olpc_activity (data->self, id, room, data->error))
+ return;
+ }
+ else
+ {
+ /* activity was already known */
+ salut_olpc_activity_update (activity, room, id, NULL, NULL, NULL,
+ NULL, activity->is_private);
+ }
+}
+
+static gboolean
+_set_olpc_activities_delete (gpointer key, gpointer value, gpointer user_data)
+{
+ SalutOlpcActivity *activity = (SalutOlpcActivity *) value;
+ struct _set_olpc_activities_ctx *data = user_data;
+ gboolean remove_activity;
+
+ /* delete the activity service if it's not in data->room_to_act_id */
+ remove_activity = (g_hash_table_lookup (data->room_to_act_id, key) == NULL);
+
+ if (remove_activity)
+ {
+ salut_olpc_activity_left (activity);
+ salut_olpc_activity_revoke_invitations (activity);
+ }
+
+ return remove_activity;
+}
+
+gboolean
+salut_self_set_olpc_activities (SalutSelf *self,
+ GHashTable *room_to_act_id,
+ GError **error)
+{
+ GError *e = NULL;
+ struct _set_olpc_activities_ctx data = { self, self->priv->room_repo,
+ self->priv->olpc_activities, room_to_act_id, &e };
+
+ g_return_val_if_fail (SALUT_IS_SELF (self), FALSE);
+
+ /* delete any which aren't in room_to_act_id. Can't fail */
+ g_hash_table_foreach_remove (self->priv->olpc_activities,
+ _set_olpc_activities_delete, &data);
+
+ /* add any which aren't in olpc_activities */
+ g_hash_table_foreach (room_to_act_id, _set_olpc_activities_add, &data);
+
+ if (error != NULL)
+ *error = e;
+ return (e == NULL);
+}
+
+gboolean
+salut_self_set_olpc_current_activity (SalutSelf *self,
+ const gchar *id,
+ TpHandle room,
+ GError **error)
+{
+ GError *err = NULL;
+ const gchar *room_name;
+
+ g_return_val_if_fail (SALUT_IS_SELF (self), FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+
+ /* if one of id and room is empty, require the other to be */
+ if (room == 0)
+ {
+ room_name = "";
+
+ if (id[0] != '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "In SetCurrentActivity, activity ID must be \"\" if room handle "
+ "is 0");
+ return FALSE;
+ }
+ }
+ else
+ {
+ room_name = tp_handle_inspect (self->priv->room_repo, room);
+
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "In SetCurrentActivity, activity ID must not be \"\" if room "
+ "handle is non-zero");
+ return FALSE;
+ }
+ }
+
+ g_free (self->olpc_cur_act);
+ self->olpc_cur_act = g_strdup (id);
+
+ if (self->olpc_cur_act_room != 0)
+ tp_handle_unref (self->priv->room_repo, self->olpc_cur_act_room);
+ self->olpc_cur_act_room = room;
+ if (room != 0)
+ tp_handle_ref (self->priv->room_repo, room);
+
+ if (!SALUT_SELF_GET_CLASS (self)->update_current_activity (self, room_name,
+ &err))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s",
+ err->message);
+ g_error_free (err);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+salut_self_set_olpc_activity_properties (SalutSelf *self,
+ TpHandle handle,
+ const gchar *color,
+ const gchar *name,
+ const gchar *type,
+ const gchar *tags,
+ gboolean is_private,
+ GError **error)
+{
+ SalutSelfPrivate *priv;
+ SalutOlpcActivity *activity;
+
+ g_return_val_if_fail (SALUT_IS_SELF (self), FALSE);
+
+ priv = self->priv;
+
+ activity = g_hash_table_lookup (priv->olpc_activities,
+ GUINT_TO_POINTER (handle));
+
+ if (activity == NULL)
+ {
+ /* User have to call org.laptop.Telepathy.BuddyInfo.SetActivities
+ * to create the activity */
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No activity associated with room having handle %d", handle);
+ return FALSE;
+ }
+
+ salut_olpc_activity_update (activity, handle, activity->id,
+ name, type, color, tags, is_private);
+
+ return TRUE;
+}
+
+gboolean
+salut_self_set_olpc_properties (SalutSelf *self,
+ const GArray *key,
+ const gchar *color,
+ const gchar *jid,
+ GError **error)
+{
+ GError *err = NULL;
+
+ if (key != NULL)
+ {
+ if (self->olpc_key == NULL)
+ {
+ self->olpc_key = g_array_sized_new (FALSE, FALSE, sizeof (guint8),
+ key->len);
+ }
+ else
+ {
+ g_array_remove_range (self->olpc_key, 0, self->olpc_key->len);
+ }
+
+ g_array_append_vals (self->olpc_key, key->data, key->len);
+ }
+
+ if (color != NULL)
+ {
+ g_free (self->olpc_color);
+ self->olpc_color = g_strdup (color);
+ }
+
+ if (jid != NULL)
+ {
+ g_free (self->jid);
+ self->jid = g_strdup (jid);
+ }
+
+ if (!SALUT_SELF_GET_CLASS (self)->set_olpc_properties (self, key, color, jid,
+ &err))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s",
+ err->message);
+ g_error_free (err);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+typedef struct
+{
+ SalutSelfOLPCActivityFunc foreach;
+ gpointer user_data;
+} foreach_olpc_activity_ctx;
+
+static void
+foreach_olpc_activity (gpointer key, gpointer value, gpointer user_data)
+{
+ foreach_olpc_activity_ctx *ctx = user_data;
+ SalutOlpcActivity *activity = value;
+
+ DEBUG ("%s -> %u", activity->id, GPOINTER_TO_UINT (key));
+ (ctx->foreach) (activity, ctx->user_data);
+}
+
+void
+salut_self_foreach_olpc_activity (SalutSelf *self,
+ SalutSelfOLPCActivityFunc foreach,
+ gpointer user_data)
+{
+ foreach_olpc_activity_ctx ctx = { foreach, user_data };
+
+ g_return_if_fail (SALUT_IS_SELF (self));
+
+ DEBUG ("called");
+
+ g_hash_table_foreach (self->priv->olpc_activities, foreach_olpc_activity,
+ &ctx);
+
+ DEBUG ("end");
+}
+
+void
+salut_self_olpc_augment_invitation (SalutSelf *self,
+ TpHandle room,
+ TpHandle contact,
+ WockyNode *invite_node)
+{
+ SalutOlpcActivity *activity;
+
+ g_return_if_fail (SALUT_IS_SELF (self));
+
+ activity = g_hash_table_lookup (self->priv->olpc_activities,
+ GUINT_TO_POINTER (room));
+ if (activity == NULL)
+ return;
+
+ salut_olpc_activity_augment_invitation (activity, contact, invite_node);
+}
+
+typedef struct
+{
+ GHashTable *olpc_activities;
+ TpHandle contact_handle;
+} remove_from_invited_ctx;
+
+static void
+remove_from_invited (SalutOlpcActivity *act,
+ gpointer user_data)
+{
+ SalutOlpcActivity *activity;
+ remove_from_invited_ctx *data = (remove_from_invited_ctx *) user_data;
+
+ activity = g_hash_table_lookup (data->olpc_activities,
+ GUINT_TO_POINTER (act->room));
+ if (activity == NULL)
+ return;
+
+ if (salut_olpc_activity_remove_invited (activity, data->contact_handle))
+ DEBUG ("contact %d joined activity %s. Remove it from the invited list",
+ data->contact_handle, activity->id);
+}
+
+/* when a buddy changes his activity list, check if we invited him
+ * to this activity and remove him from the invited set */
+void
+contact_manager_contact_change_cb (SalutContactManager *mgr,
+ SalutContact *contact,
+ int changes,
+ gpointer user_data)
+{
+ SalutSelf *self = SALUT_SELF (user_data);
+ SalutSelfPrivate *priv = self->priv;
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ TP_BASE_CONNECTION (self->connection), TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle;
+ remove_from_invited_ctx data;
+
+ if (!(changes & SALUT_CONTACT_OLPC_ACTIVITIES))
+ return;
+
+ handle = tp_handle_lookup (handle_repo, contact->name, NULL, NULL);
+
+ data.olpc_activities = priv->olpc_activities;
+ data.contact_handle = handle;
+ salut_contact_foreach_olpc_activity (contact, remove_from_invited, &data);
+}
+#endif /* ENABLE_OLPC */
+
+void
+salut_self_established (SalutSelf *self)
+{
+ g_signal_emit (self, signals[ESTABLISHED], 0, NULL);
+}
+
+const GabbleCapabilitySet *
+salut_self_get_caps (SalutSelf *self)
+{
+ return self->priv->caps;
+}
+
+static const GPtrArray *
+salut_self_get_data_forms (WockyXep0115Capabilities *caps)
+{
+ SalutSelf *self = SALUT_SELF (caps);
+
+ return self->priv->data_forms;
+}
+
+void
+salut_self_take_caps (SalutSelf *self,
+ GabbleCapabilitySet *set)
+{
+ g_return_if_fail (SALUT_IS_SELF (self));
+ g_return_if_fail (set != NULL);
+
+ gabble_capability_set_free (self->priv->caps);
+ self->priv->caps = set;
+}
+
+void
+salut_self_take_data_forms (SalutSelf *self,
+ GPtrArray *data_forms)
+{
+ g_return_if_fail (SALUT_IS_SELF (self));
+ g_return_if_fail (data_forms != NULL);
+
+ g_ptr_array_unref (self->priv->data_forms);
+ self->priv->data_forms = data_forms;
+}
+
+static void
+xep_0115_capabilities_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ WockyXep0115CapabilitiesInterface *iface = g_iface;
+
+ iface->get_data_forms = salut_self_get_data_forms;
+}
diff --git a/salut/src/self.h b/salut/src/self.h
new file mode 100644
index 000000000..afe7e52eb
--- /dev/null
+++ b/salut/src/self.h
@@ -0,0 +1,172 @@
+/*
+ * self.h - Header for SalutSelf
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_SELF_H__
+#define __SALUT_SELF_H__
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include <telepathy-glib/handle-repo.h>
+
+#include <wocky/wocky-stanza.h>
+
+#include <salut/capability-set.h>
+
+#include "connection.h"
+#include "presence.h"
+#ifdef ENABLE_OLPC
+#include "olpc-activity.h"
+#endif
+
+G_BEGIN_DECLS
+
+typedef struct _SalutSelf SalutSelf;
+typedef struct _SalutSelfClass SalutSelfClass;
+typedef struct _SalutSelfPrivate SalutSelfPrivate;
+
+struct _SalutSelfClass {
+ GObjectClass parent_class;
+
+ /* public abstract methods */
+ gboolean (*announce) (SalutSelf *self, guint16 port, GError **error);
+ gboolean (*set_presence) (SalutSelf *self, GError **error);
+ gboolean (*set_caps) (SalutSelf *self, GError **error);
+ gboolean (*set_alias) (SalutSelf *self, GError **error);
+ gboolean (*set_avatar) (SalutSelf *self, guint8 *data, gsize size,
+ GError **error);
+#ifdef ENABLE_OLPC
+ gboolean (*set_olpc_properties) (SalutSelf *self, const GArray *key,
+ const gchar *color, const gchar *jid, GError **error);
+#endif
+
+ /* private abstract methods */
+ void (*remove_avatar) (SalutSelf *self);
+ gboolean (*update_current_activity) (SalutSelf *self,
+ const gchar *room_name, GError **error);
+};
+
+struct _SalutSelf {
+ GObject parent;
+ gchar *name;
+ SalutPresenceId status;
+ gchar *status_message;
+ gchar *avatar_token;
+ guint8 *avatar;
+ gsize avatar_size;
+ gchar *jid;
+#ifdef ENABLE_OLPC
+ GArray *olpc_key;
+ gchar *olpc_cur_act;
+ TpHandle olpc_cur_act_room;
+ gchar *olpc_color;
+#endif
+ gchar *node;
+ gchar *hash;
+ gchar *ver;
+
+ /* private */
+ SalutConnection *connection;
+ gchar *nickname;
+ gchar *first_name;
+ gchar *last_name;
+ gchar *email;
+ gchar *published_name;
+ gchar *alias;
+
+ SalutSelfPrivate *priv;
+};
+
+GType salut_self_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_SELF \
+ (salut_self_get_type ())
+#define SALUT_SELF(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_SELF, SalutSelf))
+#define SALUT_SELF_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_SELF, SalutSelfClass))
+#define SALUT_IS_SELF(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_SELF))
+#define SALUT_IS_SELF_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_SELF))
+#define SALUT_SELF_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_SELF, SalutSelfClass))
+
+/* Start announcing our presence on the network */
+gboolean salut_self_announce (SalutSelf *self, guint16 port, GError **error);
+
+gboolean salut_self_set_presence (SalutSelf *self,
+ SalutPresenceId status, const gchar *message, GError **error);
+
+gboolean salut_self_set_caps (SalutSelf *self, const gchar *node,
+ const gchar *hash, const gchar *ver, GError **error);
+
+gboolean salut_self_set_avatar (SalutSelf *self, guint8 *data,
+ gsize size, GError **error);
+
+gboolean salut_self_set_alias (SalutSelf *self, const gchar *alias,
+ GError **error);
+
+const gchar *salut_self_get_alias (SalutSelf *self);
+
+#ifdef ENABLE_OLPC
+gboolean salut_self_set_olpc_properties (SalutSelf *self,
+ const GArray *key, const gchar *color, const gchar *jid, GError **error);
+
+gboolean salut_self_set_olpc_activity_properties (SalutSelf *self,
+ TpHandle handle,
+ const gchar *color, const gchar *name, const gchar *type,
+ const gchar *tags, gboolean is_private, GError **error);
+
+gboolean salut_self_set_olpc_activities (SalutSelf *self,
+ GHashTable *act_id_to_room, GError **error);
+
+gboolean salut_self_add_olpc_activity (SalutSelf *self,
+ const gchar *activity_id, TpHandle room, GError **error);
+
+gboolean salut_self_remove_olpc_activity (SalutSelf *self,
+ SalutOlpcActivity *activity);
+
+gboolean salut_self_set_olpc_current_activity (SalutSelf *self,
+ const gchar *id, TpHandle room, GError **error);
+
+typedef void (*SalutSelfOLPCActivityFunc)
+ (SalutOlpcActivity *activity, gpointer user_data);
+
+void salut_self_foreach_olpc_activity (SalutSelf *self,
+ SalutSelfOLPCActivityFunc foreach, gpointer user_data);
+
+void salut_self_olpc_augment_invitation (SalutSelf *self,
+ TpHandle room, TpHandle contact, WockyNode *invite_node);
+#endif
+
+const GabbleCapabilitySet *salut_self_get_caps (SalutSelf *self);
+
+void salut_self_take_caps (SalutSelf *self, GabbleCapabilitySet *caps);
+
+void salut_self_take_data_forms (SalutSelf *self, GPtrArray *data_forms);
+
+/* protected methods */
+void salut_self_established (SalutSelf *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_SELF_H__*/
diff --git a/salut/src/sha1/sha1-util.c b/salut/src/sha1/sha1-util.c
new file mode 100644
index 000000000..40550c6da
--- /dev/null
+++ b/salut/src/sha1/sha1-util.c
@@ -0,0 +1,57 @@
+/*
+ * sha1-util.c - sha1-utils
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sha1/sha1-util.h"
+
+#include <stdio.h>
+#include <stdint.h>
+
+
+gchar *
+sha1_hex (const guint8 *bytes,
+ guint len)
+{
+ gchar *hex = g_compute_checksum_for_data (G_CHECKSUM_SHA1, bytes, len);
+ guint i;
+
+ for (i = 0; i < SHA1_HASH_SIZE * 2; i++)
+ {
+ g_assert (hex[i] != '\0');
+ hex[i] = g_ascii_tolower (hex[i]);
+ }
+
+ g_assert (hex[SHA1_HASH_SIZE * 2] == '\0');
+
+ return hex;
+}
+
+void
+sha1_bin (const gchar *bytes,
+ guint len,
+ guchar out[SHA1_HASH_SIZE])
+{
+ GChecksum *checksum = g_checksum_new (G_CHECKSUM_SHA1);
+ gsize out_len = SHA1_HASH_SIZE;
+
+ g_assert (g_checksum_type_get_length (G_CHECKSUM_SHA1) == SHA1_HASH_SIZE);
+ g_checksum_update (checksum, (const guchar *) bytes, len);
+ g_checksum_get_digest (checksum, out, &out_len);
+ g_assert (out_len == SHA1_HASH_SIZE);
+ g_checksum_free (checksum);
+}
diff --git a/salut/src/sha1/sha1-util.h b/salut/src/sha1/sha1-util.h
new file mode 100644
index 000000000..0d99f5a08
--- /dev/null
+++ b/salut/src/sha1/sha1-util.h
@@ -0,0 +1,32 @@
+/*
+ * sha1-util.h - Header for sha1-utils
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_SHA1_UTIL__
+#define __SALUT_SHA1_UTIL__
+
+#include <glib.h>
+
+/* Guarantees that the resulting hash is in lower-case */
+gchar *sha1_hex (const guint8 *bytes, guint len);
+
+/* A SHA1 digest is 20 bytes long */
+#define SHA1_HASH_SIZE 20
+void sha1_bin (const gchar *bytes, guint len, guchar out[SHA1_HASH_SIZE]);
+
+#endif /* #ifndef __SALUT_SHA1_UTIL__ */
diff --git a/salut/src/si-bytestream-manager.c b/salut/src/si-bytestream-manager.c
new file mode 100644
index 000000000..0b951dcd4
--- /dev/null
+++ b/salut/src/si-bytestream-manager.c
@@ -0,0 +1,884 @@
+/*
+ * si-bytestream-manager.c - Source for SalutSiBytestreamManager
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "si-bytestream-manager.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gibber/gibber-bytestream-oob.h>
+
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-meta-porter.h>
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-xmpp-error.h>
+
+#include "im-manager.h"
+#include "muc-manager.h"
+#include "tubes-manager.h"
+
+#define DEBUG_FLAG DEBUG_SI_BYTESTREAM_MGR
+#include "debug.h"
+
+G_DEFINE_TYPE (SalutSiBytestreamManager, salut_si_bytestream_manager,
+ G_TYPE_OBJECT)
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_HOST_NAME_FQDN,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutSiBytestreamManagerPrivate SalutSiBytestreamManagerPrivate;
+
+struct _SalutSiBytestreamManagerPrivate
+{
+ SalutConnection *connection;
+ SalutImManager *im_manager;
+ SalutMucManager *muc_manager;
+ gchar *host_name_fqdn;
+
+ guint si_request_id;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE(obj) \
+ ((SalutSiBytestreamManagerPrivate *) ((SalutSiBytestreamManager *) obj)->priv)
+
+static void
+salut_si_bytestream_manager_init (SalutSiBytestreamManager *self)
+{
+ SalutSiBytestreamManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_SI_BYTESTREAM_MANAGER, SalutSiBytestreamManagerPrivate);
+
+ self->priv = priv;
+
+ priv->dispose_has_run = FALSE;
+}
+
+static gboolean
+streaminit_parse_request (WockyStanza *stanza,
+ const gchar **profile,
+ const gchar **from,
+ const gchar **stream_id,
+ const gchar **stream_init_id,
+ const gchar **mime_type,
+ GSList **stream_methods)
+{
+ WockyNode *iq, *si, *feature, *x;
+ GSList *x_children, *field_children;
+
+ iq = wocky_stanza_get_top_node (stanza);
+
+ if (stream_init_id != NULL)
+ *stream_init_id = wocky_node_get_attribute (iq, "id");
+
+ *from = wocky_node_get_attribute (iq, "from");
+ if (*from == NULL)
+ {
+ DEBUG ("got a message without a from field");
+ return FALSE;
+ }
+
+ /* Parse <si> */
+ si = wocky_node_get_child_ns (iq, "si", WOCKY_XMPP_NS_SI);
+ if (si == NULL)
+ return FALSE;
+
+ *stream_id = wocky_node_get_attribute (si, "id");
+ if (*stream_id == NULL)
+ {
+ DEBUG ("got a SI request without a stream id field");
+ return FALSE;
+ }
+
+ *mime_type = wocky_node_get_attribute (si, "mime-type");
+ /* if no mime_type is defined, XEP-0095 says to assume
+ * "application/octet-stream" */
+
+ *profile = wocky_node_get_attribute (si, "profile");
+ if (*profile == NULL)
+ {
+ DEBUG ("got a SI request without a profile field");
+ return FALSE;
+ }
+
+ /* Parse <feature> */
+ feature = wocky_node_get_child_ns (si, "feature",
+ WOCKY_XMPP_NS_FEATURENEG);
+ if (feature == NULL)
+ {
+ DEBUG ("got a SI request without a feature field");
+ return FALSE;
+ }
+
+ x = wocky_node_get_child_ns (feature, "x", WOCKY_XMPP_NS_DATA);
+ if (x == NULL)
+ {
+ DEBUG ("got a SI request without a X data field");
+ return FALSE;
+ }
+
+ for (x_children = x->children; x_children;
+ x_children = g_slist_next (x_children))
+ {
+ WockyNode *field = x_children->data;
+
+ if (tp_strdiff (wocky_node_get_attribute (field, "var"),
+ "stream-method"))
+ /* some future field, ignore it */
+ continue;
+
+ if (tp_strdiff (wocky_node_get_attribute (field, "type"),
+ "list-single"))
+ {
+ DEBUG ( "SI request's stream-method field was "
+ "not of type list-single");
+ return FALSE;
+ }
+
+ /* Get the stream methods offered */
+ *stream_methods = NULL;
+ for (field_children = field->children; field_children;
+ field_children = g_slist_next (field_children))
+ {
+ WockyNode *stream_method, *value;
+ const gchar *stream_method_str;
+
+ stream_method = (WockyNode *) field_children->data;
+
+ value = wocky_node_get_child (stream_method, "value");
+ if (value == NULL)
+ continue;
+
+ stream_method_str = value->content;
+ if (!tp_strdiff (stream_method_str, ""))
+ continue;
+
+ DEBUG ("Got stream-method %s", stream_method_str);
+
+ /* Append to the stream_methods list */
+ *stream_methods = g_slist_append (*stream_methods,
+ (gchar *) stream_method_str);
+ }
+
+ /* no need to parse the rest of the fields, we've found the one we
+ * wanted */
+ break;
+ }
+
+ if (*stream_methods == NULL)
+ {
+ DEBUG ("got a SI request without stream method proposed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+bytestream_state_changed (GibberBytestreamIface *bytestream,
+ GibberBytestreamState state,
+ WockyContact *contact)
+{
+ if (state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ {
+ WockyMetaPorter *porter;
+
+ DEBUG ("bytestream closed, release the connection");
+ g_object_get (bytestream, "porter", &porter, NULL);
+
+ wocky_meta_porter_unhold (porter, contact);
+
+ g_object_unref (porter);
+ g_object_unref (bytestream);
+ }
+}
+
+static GibberBytestreamIface *
+choose_bytestream_method (SalutSiBytestreamManager *self,
+ GSList *stream_methods,
+ WockyPorter *porter,
+ SalutContact *contact,
+ const gchar *stream_id,
+ WockyStanza *stream_init_iq)
+{
+ SalutSiBytestreamManagerPrivate *priv =
+ SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self);
+ GSList *l;
+
+ /* We create the stream according the stream method chosen.
+ * User has to accept it */
+
+ /* check OOB */
+ for (l = stream_methods; l != NULL; l = l->next)
+ {
+ if (!tp_strdiff (l->data, WOCKY_XMPP_NS_IQ_OOB))
+ {
+ DEBUG ("choose OOB in methods list");
+ return g_object_new (GIBBER_TYPE_BYTESTREAM_OOB,
+ "porter", porter,
+ "stream-id", stream_id,
+ "state", GIBBER_BYTESTREAM_STATE_LOCAL_PENDING,
+ "self-id", priv->connection->name,
+ "peer-id", contact->name,
+ "contact", contact,
+ "stream-init-iq", stream_init_iq,
+ NULL);
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+si_request_cb (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (user_data);
+ SalutSiBytestreamManagerPrivate *priv =
+ SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->connection, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->connection, TP_HANDLE_TYPE_ROOM);
+ TpHandle peer_handle;
+ GibberBytestreamIface *bytestream = NULL;
+ WockyNode *top_node = wocky_stanza_get_top_node (stanza);
+ WockyNode *si, *node;
+ const gchar *profile, *from, *stream_id, *mime_type;
+ GSList *stream_methods = NULL;
+ WockyContact *contact = wocky_stanza_get_from_contact (stanza);
+
+ /* after this point, the message is for us, so in all cases we either handle
+ * it or send an error reply */
+
+ if (!streaminit_parse_request (stanza, &profile, &from, &stream_id,
+ NULL, &mime_type, &stream_methods))
+ {
+ GError err = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "failed to parse SI request" };
+ WockyStanza *reply;
+
+ reply = wocky_stanza_build_iq_error (stanza, NULL);
+ wocky_stanza_error_to_node (&err, wocky_stanza_get_top_node (reply));
+
+ wocky_porter_send (porter, reply);
+
+ g_object_unref (reply);
+ return TRUE;
+ }
+
+ si = wocky_node_get_child_ns (top_node, "si", WOCKY_XMPP_NS_SI);
+ g_assert (si != NULL);
+
+ DEBUG ("received a SI request");
+
+ peer_handle = tp_handle_lookup (contact_repo, from, NULL, NULL);
+ if (peer_handle == 0)
+ {
+ goto out;
+ }
+
+ /* check stream method */
+ bytestream = choose_bytestream_method (self, stream_methods, porter,
+ SALUT_CONTACT (contact), stream_id, stanza);
+
+ if (bytestream == NULL)
+ {
+ GError err = { WOCKY_XMPP_ERROR, WOCKY_SI_ERROR_NO_VALID_STREAMS,
+ NULL };
+ WockyStanza *reply;
+
+ DEBUG ("SI request doesn't contain any supported stream method.");
+
+ reply = wocky_stanza_build_iq_error (stanza, NULL);
+ wocky_stanza_error_to_node (&err, wocky_stanza_get_top_node (reply));
+
+ wocky_porter_send (porter, reply);
+
+ g_object_unref (reply);
+ goto out;
+ }
+
+ /* Now that we have a bytestream, it's responsible for declining the IQ
+ * if needed. */
+
+ /* As bytestreams are not porter aware, they can't take/release
+ * the connection so we do it for them.
+ * We'll release it when the bytestream will be closed */
+ wocky_meta_porter_hold (WOCKY_META_PORTER (porter), contact);
+
+ g_signal_connect (bytestream, "state-changed",
+ G_CALLBACK (bytestream_state_changed), contact);
+
+ /* We inform the right manager we received a SI request */
+ if (tp_strdiff (profile, WOCKY_TELEPATHY_NS_TUBES))
+ {
+ GError e = { WOCKY_SI_ERROR, WOCKY_SI_ERROR_BAD_PROFILE, "" };
+ DEBUG ("SI profile unsupported: %s", profile);
+
+ gibber_bytestream_iface_close (bytestream, &e);
+ goto out;
+ }
+
+ /* A Tubes SI request can only be a muc tube extra bytestream offer.
+ * We don't use SI for 1-1 tubes
+ */
+
+ if ((node = wocky_node_get_child_ns (si, "muc-stream",
+ WOCKY_TELEPATHY_NS_TUBES)))
+ {
+ const gchar *muc;
+ TpHandle room_handle;
+ SalutMucManager *muc_mgr;
+
+ muc = wocky_node_get_attribute (node, "muc");
+ if (muc == NULL)
+ {
+ DEBUG ("muc-stream SI doesn't contain muc attribute");
+ gibber_bytestream_iface_close (bytestream, NULL);
+ goto out;
+ }
+
+ room_handle = tp_handle_lookup (room_repo, muc, NULL, NULL);
+ if (room_handle == 0)
+ {
+ DEBUG ("Unknown room: %s\n", muc);
+ gibber_bytestream_iface_close (bytestream, NULL);
+ goto out;
+ }
+
+ g_object_get (priv->connection, "muc-manager", &muc_mgr, NULL);
+ g_assert (muc_mgr != NULL);
+
+ salut_muc_manager_handle_si_stream_request (muc_mgr,
+ bytestream, room_handle, stream_id, stanza);
+ g_object_unref (muc_mgr);
+ }
+ else
+ {
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "Invalid tube SI request: expected <tube>, <stream> or "
+ "<muc-stream>" };
+
+ DEBUG ("Invalid tube SI request");
+ gibber_bytestream_iface_close (bytestream, &e);
+ goto out;
+ }
+
+out:
+ g_slist_free (stream_methods);
+ return TRUE;
+}
+
+static void
+salut_si_bytestream_manager_dispose (GObject *object)
+{
+ SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (object);
+ SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE
+ (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (priv->connection->porter != NULL)
+ {
+ wocky_porter_unregister_handler (priv->connection->porter,
+ priv->si_request_id);
+ priv->si_request_id = 0;
+ }
+
+ g_object_unref (priv->im_manager);
+ g_object_unref (priv->muc_manager);
+
+ if (G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->dispose (object);
+}
+
+static void
+salut_si_bytestream_manager_finalize (GObject *object)
+{
+ SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (object);
+ SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (
+ self);
+
+ g_free (priv->host_name_fqdn);
+
+ if (G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->finalize)
+ G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->finalize
+ (object);
+}
+
+static void
+salut_si_bytestream_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (object);
+ SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (
+ self);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->connection);
+ break;
+ case PROP_HOST_NAME_FQDN:
+ g_value_set_string (value, priv->host_name_fqdn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_si_bytestream_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (object);
+ SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (
+ self);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->connection = g_value_get_object (value);
+ break;
+ case PROP_HOST_NAME_FQDN:
+ g_free (priv->host_name_fqdn);
+ priv->host_name_fqdn = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+salut_si_bytestream_manager_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutSiBytestreamManager *self;
+ SalutSiBytestreamManagerPrivate *priv;
+
+ obj = G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_SI_BYTESTREAM_MANAGER (obj);
+ priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self);
+
+ g_assert (priv->connection != NULL);
+ g_object_get (priv->connection,
+ "im-manager", &(priv->im_manager),
+ "muc-manager", &(priv->muc_manager),
+ NULL);
+ g_assert (priv->im_manager != NULL);
+ g_assert (priv->muc_manager != NULL);
+ g_assert (priv->host_name_fqdn != NULL);
+
+ priv->si_request_id = wocky_porter_register_handler_from_anyone (
+ priv->connection->porter, WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_SET, WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ si_request_cb, self,
+ '(', "si",
+ ':', WOCKY_XMPP_NS_SI,
+ ')', NULL);
+
+ return obj;
+}
+
+static void
+salut_si_bytestream_manager_class_init (
+ SalutSiBytestreamManagerClass *salut_si_bytestream_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS
+ (salut_si_bytestream_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_si_bytestream_manager_class,
+ sizeof (SalutSiBytestreamManagerPrivate));
+
+ object_class->constructor = salut_si_bytestream_manager_constructor;
+ object_class->dispose = salut_si_bytestream_manager_dispose;
+ object_class->finalize = salut_si_bytestream_manager_finalize;
+
+ object_class->get_property = salut_si_bytestream_manager_get_property;
+ object_class->set_property = salut_si_bytestream_manager_set_property;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "Salut Connection that owns the connection for this bytestream channel",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_string (
+ "host-name-fqdn",
+ "host name FQDN",
+ "The FQDN host name that will be used by OOB bytestreams",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HOST_NAME_FQDN,
+ param_spec);
+}
+
+SalutSiBytestreamManager *
+salut_si_bytestream_manager_new (SalutConnection *conn,
+ const gchar *host_name_fqdn)
+{
+ g_return_val_if_fail (SALUT_IS_CONNECTION (conn), NULL);
+
+ return g_object_new (
+ SALUT_TYPE_SI_BYTESTREAM_MANAGER,
+ "connection", conn,
+ "host-name-fqdn", host_name_fqdn,
+ NULL);
+}
+
+/**
+ * salut_si_bytestream_manager_make_stream_init_iq
+ *
+ * @from: your contact
+ * @to: the contact to who you want to offer the stream
+ * @stream_id: the stream ID of the new stream
+ * @profile: the profile associated with the stream
+ *
+ * Create a SI request IQ as described in XEP-0095.
+ *
+ */
+WockyStanza *
+salut_si_bytestream_manager_make_stream_init_iq (const gchar *from,
+ const gchar *to,
+ const gchar *stream_id,
+ const gchar *profile)
+{
+ return wocky_stanza_build (
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
+ from, to,
+ '(', "si",
+ ':', WOCKY_XMPP_NS_SI,
+ '@', "id", stream_id,
+ '@', "profile", profile,
+ '@', "mime-type", "application/octet-stream",
+ '(', "feature",
+ ':', WOCKY_XMPP_NS_FEATURENEG,
+ '(', "x",
+ ':', WOCKY_XMPP_NS_DATA,
+ '@', "type", "form",
+ '(', "field",
+ '@', "var", "stream-method",
+ '@', "type", "list-single",
+
+ '(', "option",
+ '(', "value",
+ '$', WOCKY_XMPP_NS_IQ_OOB,
+ ')',
+ ')',
+
+ ')',
+ ')',
+ ')',
+ ')', NULL);
+}
+
+struct streaminit_reply_cb_data
+{
+ SalutSiBytestreamManager *self;
+ gchar *stream_id;
+ SalutSiBytestreamManagerNegotiateReplyFunc func;
+ gpointer user_data;
+ SalutContact *contact;
+ WockyStanza *stanza;
+};
+
+static struct streaminit_reply_cb_data *
+streaminit_reply_cb_data_new (void)
+{
+ return g_slice_new0 (struct streaminit_reply_cb_data);
+}
+
+static void
+streaminit_reply_cb_data_free (struct streaminit_reply_cb_data *data)
+{
+ g_free (data->stream_id);
+
+ if (data->contact != NULL)
+ g_object_unref (data->contact);
+
+ if (data->stanza != NULL)
+ g_object_unref (data->stanza);
+
+ g_slice_free (struct streaminit_reply_cb_data, data);
+}
+
+static gboolean
+check_bytestream_oob_peer_addr (GibberBytestreamOOB *bytestream,
+ struct sockaddr *addr,
+ socklen_t addrlen,
+ gpointer user_data)
+{
+ SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (user_data);
+ SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (
+ self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->connection, TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle;
+ SalutContactManager *contact_mgr;
+ SalutContact *contact;
+ gchar *peer;
+ gboolean result;
+
+ g_object_get (bytestream, "peer-id", &peer, NULL);
+ g_assert (peer != NULL);
+
+ handle = tp_handle_lookup (contact_repo, peer, NULL, NULL);
+ g_assert (handle != 0);
+ g_free (peer);
+
+ g_object_get (priv->connection, "contact-manager", &contact_mgr, NULL);
+ g_assert (contact_mgr != NULL);
+
+ contact = salut_contact_manager_get_contact (contact_mgr, handle);
+ g_object_unref (contact_mgr);
+ if (contact == NULL)
+ return FALSE;
+
+ result = salut_contact_has_address (contact, addr, addrlen);
+ g_object_unref (contact);
+
+ return result;
+}
+
+static void
+si_request_sent_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ struct streaminit_reply_cb_data *data =
+ (struct streaminit_reply_cb_data *) user_data;
+ SalutSiBytestreamManagerPrivate *priv =
+ SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (data->self);
+ WockyStanza *stanza;
+ GError *error = NULL;
+
+ WockyStanzaSubType sub_type;
+ WockyNode *si, *feature, *x;
+ GibberBytestreamIface *bytestream = NULL;
+ const gchar *from, *stream_method;
+ GSList *x_children;
+ WockyNode *node;
+
+ stanza = wocky_porter_send_iq_finish (porter, result, &error);
+
+ if (result == NULL)
+ {
+ DEBUG ("sending SI request failed: %s", error->message);
+ g_clear_error (&error);
+ goto END;
+ }
+
+ DEBUG ("received SI request response");
+
+ node = wocky_stanza_get_top_node (stanza);
+
+ wocky_stanza_get_type_info (stanza, NULL, &sub_type);
+ if (sub_type != WOCKY_STANZA_SUB_TYPE_RESULT)
+ {
+ DEBUG ("stream %s declined", data->stream_id);
+ goto END;
+ }
+
+ /* stream accepted */
+ from = wocky_node_get_attribute (node, "from");
+ if (from == NULL)
+ {
+ DEBUG ("got a message without a from field");
+ goto END;
+ }
+
+ si = wocky_node_get_child_ns (node, "si",
+ WOCKY_XMPP_NS_SI);
+ if (si == NULL)
+ {
+ DEBUG ("got a SI reply without a si field");
+ goto END;
+ }
+
+ feature = wocky_node_get_child_ns (si, "feature",
+ WOCKY_XMPP_NS_FEATURENEG);
+ if (feature == NULL)
+ {
+ DEBUG ("got a SI reply without a feature field");
+ goto END;
+ }
+
+ x = wocky_node_get_child_ns (feature, "x", WOCKY_XMPP_NS_DATA);
+ if (x == NULL)
+ {
+ DEBUG ("got a SI reply without a x field");
+ goto END;
+ }
+
+ for (x_children = x->children; x_children;
+ x_children = g_slist_next (x_children))
+ {
+ WockyNode *value, *field = x_children->data;
+
+ if (tp_strdiff (wocky_node_get_attribute (field, "var"),
+ "stream-method"))
+ /* some future field, ignore it */
+ continue;
+
+ value = wocky_node_get_child (field, "value");
+ if (value == NULL)
+ {
+ DEBUG ("SI reply's stream-method field "
+ "doesn't contain stream-method value");
+ goto END;
+ }
+
+ stream_method = value->content;
+
+ if (!tp_strdiff (stream_method, WOCKY_XMPP_NS_IQ_OOB))
+ {
+ /* Remote user have accepted the stream */
+ DEBUG ("remote user chose a OOB bytestream");
+ bytestream = g_object_new (GIBBER_TYPE_BYTESTREAM_OOB,
+ "porter", porter,
+ "stream-id", data->stream_id,
+ "state", GIBBER_BYTESTREAM_STATE_INITIATING,
+ "self-id", priv->connection->name,
+ "peer-id", from,
+ "contact", wocky_stanza_get_from_contact (stanza),
+ "stream-init-iq", NULL,
+ "host", priv->host_name_fqdn,
+ NULL);
+ gibber_bytestream_oob_set_check_addr_func (
+ GIBBER_BYTESTREAM_OOB (bytestream), check_bytestream_oob_peer_addr,
+ data->self);
+ }
+ else
+ {
+ DEBUG ("Remote user chose an unsupported stream method");
+ goto END;
+ }
+
+ /* no need to parse the rest of the fields, we've found the one we
+ * wanted */
+ break;
+
+ }
+
+ if (bytestream == NULL)
+ goto END;
+
+ DEBUG ("stream %s accepted. Start to initiate it", data->stream_id);
+
+ /* As bytestreams are not porter aware, they can't take/release
+ * the connection so we do it for them.
+ * We'll release it when the bytestream will be closed */
+ wocky_meta_porter_hold (WOCKY_META_PORTER (porter),
+ WOCKY_CONTACT (data->contact));
+
+ g_signal_connect (bytestream, "state-changed",
+ G_CALLBACK (bytestream_state_changed), data->contact);
+
+ /* Let's start the initiation of the stream */
+ if (!gibber_bytestream_iface_initiate (bytestream))
+ {
+ /* Initiation failed. */
+ gibber_bytestream_iface_close (bytestream, NULL);
+ bytestream = NULL;
+ }
+
+END:
+ /* user callback */
+ data->func (bytestream, data->user_data);
+
+ streaminit_reply_cb_data_free (data);
+
+ if (stanza != NULL)
+ g_object_unref (stanza);
+}
+
+/*
+ * salut_si_bytestream_manager_negotiate_stream:
+ *
+ * @contact: the contact to who send the SI request
+ * @stanza: the SI negotiation IQ (created using
+ * salut_si_bytestream_manager_make_stream_init_iq)
+ * @stream_id: the stream identifier
+ * @func: the callback to call when we receive the answser of the request
+ * @user_data: user data to pass to the callback
+ * @error: pointer in which to return a GError in case of failure.
+ *
+ * Send a Stream Initiation (XEP-0095) request.
+ */
+gboolean
+salut_si_bytestream_manager_negotiate_stream (SalutSiBytestreamManager *self,
+ SalutContact *contact,
+ WockyStanza *stanza,
+ const gchar *stream_id,
+ SalutSiBytestreamManagerNegotiateReplyFunc func,
+ gpointer user_data,
+ GError **error)
+{
+ SalutSiBytestreamManagerPrivate *priv;
+ struct streaminit_reply_cb_data *data;
+
+ g_assert (SALUT_IS_SI_BYTESTREAM_MANAGER (self));
+ g_assert (stream_id != NULL);
+ g_assert (func != NULL);
+
+ priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self);
+
+ data = streaminit_reply_cb_data_new ();
+ data->self = self;
+ data->stream_id = g_strdup (stream_id);
+ data->func = func;
+ data->user_data = user_data;
+ data->contact = g_object_ref (contact);
+ data->stanza = g_object_ref (stanza);
+
+ DEBUG ("send an SI request to %s", contact->name);
+
+ wocky_porter_send_iq_async (priv->connection->porter,
+ stanza, NULL, si_request_sent_cb, data);
+
+ return TRUE;
+}
diff --git a/salut/src/si-bytestream-manager.h b/salut/src/si-bytestream-manager.h
new file mode 100644
index 000000000..67a552a77
--- /dev/null
+++ b/salut/src/si-bytestream-manager.h
@@ -0,0 +1,81 @@
+/*
+ * si-bytestream-manager.h - Header for SalutSiBytestreamManager
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_SI_BYTESTREAM_MANAGER_H__
+#define __SALUT_SI_BYTESTREAM_MANAGER_H__
+
+#include <glib-object.h>
+#include "contact.h"
+
+#include <gibber/gibber-linklocal-transport.h>
+#include <gibber/gibber-bytestream-iface.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutSiBytestreamManager SalutSiBytestreamManager;
+typedef struct _SalutSiBytestreamManagerClass SalutSiBytestreamManagerClass;
+
+struct _SalutSiBytestreamManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _SalutSiBytestreamManager {
+ GObject parent;
+
+ gpointer priv;
+};
+
+
+GType salut_si_bytestream_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_SI_BYTESTREAM_MANAGER \
+ (salut_si_bytestream_manager_get_type ())
+#define SALUT_SI_BYTESTREAM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_SI_BYTESTREAM_MANAGER, \
+ SalutSiBytestreamManager))
+#define SALUT_SI_BYTESTREAM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_SI_BYTESTREAM_MANAGER, \
+ SalutSiBytestreamManagerClass))
+#define SALUT_IS_SI_BYTESTREAM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_SI_BYTESTREAM_MANAGER))
+#define SALUT_IS_SI_BYTESTREAM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_SI_BYTESTREAM_MANAGER))
+#define SALUT_SI_BYTESTREAM_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_SI_BYTESTREAM_MANAGER, \
+ SalutSiBytestreamManagerClass))
+
+typedef void (* SalutSiBytestreamManagerNegotiateReplyFunc) (
+ GibberBytestreamIface *bytestream, gpointer user_data);
+
+SalutSiBytestreamManager *
+salut_si_bytestream_manager_new (SalutConnection *connection,
+ const gchar *host_name_fqdn);
+
+WockyStanza *
+salut_si_bytestream_manager_make_stream_init_iq (const gchar *from,
+ const gchar *to, const gchar *stream_id, const gchar *profile);
+
+gboolean
+salut_si_bytestream_manager_negotiate_stream (SalutSiBytestreamManager *self,
+ SalutContact *contact, WockyStanza *stanza, const gchar *stream_id,
+ SalutSiBytestreamManagerNegotiateReplyFunc func, gpointer user_data,
+ GError **error);
+
+#endif /* #ifndef __SALUT_SI_BYTESTREAM_MANAGER_H__*/
diff --git a/salut/src/sidecar.c b/salut/src/sidecar.c
new file mode 100644
index 000000000..481c68df2
--- /dev/null
+++ b/salut/src/sidecar.c
@@ -0,0 +1,47 @@
+/*
+ * sidecar.c — interface for connection sidecars
+ * Copyright © 2009-2011 Collabora Ltd.
+ * Copyright © 2009 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "salut/sidecar.h"
+
+G_DEFINE_INTERFACE (SalutSidecar, salut_sidecar, G_TYPE_OBJECT)
+
+static void
+salut_sidecar_default_init (SalutSidecarInterface *iface)
+{
+}
+
+const gchar *
+salut_sidecar_get_interface (SalutSidecar *sidecar)
+{
+ SalutSidecarInterface *iface = SALUT_SIDECAR_GET_INTERFACE (sidecar);
+
+ return iface->interface;
+}
+
+GHashTable *
+salut_sidecar_get_immutable_properties (SalutSidecar *sidecar)
+{
+ SalutSidecarInterface *iface = SALUT_SIDECAR_GET_INTERFACE (sidecar);
+
+ if (iface->get_immutable_properties)
+ return iface->get_immutable_properties (sidecar);
+ else
+ return g_hash_table_new (NULL, NULL);
+}
diff --git a/salut/src/symbol-hacks.c b/salut/src/symbol-hacks.c
new file mode 100644
index 000000000..1825d530e
--- /dev/null
+++ b/salut/src/symbol-hacks.c
@@ -0,0 +1,115 @@
+/* This is pretty horrible. If we don't use a symbol in a wocky object
+ * from its static library then libtool will not include said object
+ * from the binary, so we can't use any symbols from that object in a
+ * plugin.
+ *
+ * This is a hack that X does. They can generate their file though as
+ * they have an _X_EXPORT macro. This'll all disappear when Wocky
+ * becomes a shared library...
+ *
+ * http://cgit.freedesktop.org/xorg/xserver/tree/hw/xfree86/loader/sdksyms.sh
+ */
+
+#include "symbol-hacks.h"
+
+/* First include all the public headers. */
+#include <wocky/wocky-auth-handler.h>
+#include <wocky/wocky-auth-registry.h>
+#include <wocky/wocky-bare-contact.h>
+#include <wocky/wocky-c2s-porter.h>
+#include <wocky/wocky-caps-cache.h>
+#include <wocky/wocky-connector.h>
+#include <wocky/wocky-contact-factory.h>
+#include <wocky/wocky-contact.h>
+#include <wocky/wocky-data-form.h>
+#include <wocky/wocky.h>
+#include <wocky/wocky-heartbeat-source.h>
+#include <wocky/wocky-jabber-auth-digest.h>
+#include <wocky/wocky-jabber-auth.h>
+#include <wocky/wocky-jabber-auth-password.h>
+#include <wocky/wocky-ll-connection-factory.h>
+#include <wocky/wocky-ll-connector.h>
+#include <wocky/wocky-ll-contact.h>
+#include <wocky/wocky-loopback-stream.h>
+#include <wocky/wocky-meta-porter.h>
+#include <wocky/wocky-muc.h>
+#include <wocky/wocky-node.h>
+#include <wocky/wocky-node-tree.h>
+#include <wocky/wocky-pep-service.h>
+#include <wocky/wocky-ping.h>
+#include <wocky/wocky-porter.h>
+#include <wocky/wocky-pubsub-helpers.h>
+#include <wocky/wocky-pubsub-node.h>
+#include <wocky/wocky-pubsub-service.h>
+#include <wocky/wocky-resource-contact.h>
+#include <wocky/wocky-roster.h>
+#include <wocky/wocky-sasl-auth.h>
+#include <wocky/wocky-sasl-digest-md5.h>
+#include <wocky/wocky-sasl-plain.h>
+#include <wocky/wocky-sasl-scram.h>
+#include <wocky/wocky-session.h>
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-tls-connector.h>
+#include <wocky/wocky-tls.h>
+#include <wocky/wocky-tls-handler.h>
+#include <wocky/wocky-utils.h>
+#include <wocky/wocky-xmpp-connection.h>
+#include <wocky/wocky-xmpp-error.h>
+#include <wocky/wocky-xmpp-reader.h>
+#include <wocky/wocky-xmpp-writer.h>
+
+/* Reference one symbol from each of the above headers to include each
+ * object in the final binary. */
+static void *hacks[] = {
+ wocky_auth_handler_get_type,
+ wocky_auth_registry_get_type,
+ wocky_bare_contact_get_type,
+ wocky_c2s_porter_get_type,
+ wocky_caps_cache_get_type,
+ wocky_connector_get_type,
+ wocky_contact_factory_get_type,
+ wocky_contact_get_type,
+ wocky_data_form_get_type,
+ wocky_init,
+ wocky_heartbeat_source_new,
+ wocky_jabber_auth_digest_get_type,
+ wocky_jabber_auth_get_type,
+ wocky_jabber_auth_password_get_type,
+ wocky_ll_connection_factory_get_type,
+ wocky_ll_connector_get_type,
+ wocky_ll_contact_get_type,
+ wocky_loopback_stream_get_type,
+ wocky_meta_porter_get_type,
+ wocky_muc_get_type,
+ wocky_node_new,
+ wocky_node_tree_get_type,
+ wocky_pep_service_get_type,
+ wocky_ping_get_type,
+ wocky_porter_get_type,
+ wocky_pubsub_make_stanza,
+ wocky_pubsub_node_get_type,
+ wocky_pubsub_service_get_type,
+ wocky_resource_contact_get_type,
+ wocky_roster_get_type,
+ wocky_sasl_auth_get_type,
+ wocky_sasl_digest_md5_get_type,
+ wocky_sasl_plain_get_type,
+ wocky_sasl_scram_get_type,
+ wocky_session_get_type,
+ wocky_stanza_get_type,
+ wocky_tls_connector_get_type,
+ wocky_tls_connection_get_type,
+ wocky_tls_handler_get_type,
+ wocky_strdiff,
+ wocky_xmpp_connection_get_type,
+ wocky_xmpp_error_quark,
+ wocky_xmpp_reader_get_type,
+ wocky_xmpp_writer_get_type,
+ NULL,
+};
+
+gpointer
+salut_symbol_hacks (void)
+{
+ return hacks;
+}
diff --git a/salut/src/symbol-hacks.h b/salut/src/symbol-hacks.h
new file mode 100644
index 000000000..682277106
--- /dev/null
+++ b/salut/src/symbol-hacks.h
@@ -0,0 +1,8 @@
+#ifndef SALUT_SYMBOL_HACK_H
+#define SALUT_SYMBOL_HACK_H
+
+#include <glib.h>
+
+gpointer salut_symbol_hacks (void);
+
+#endif
diff --git a/salut/src/text-helper.c b/salut/src/text-helper.c
new file mode 100644
index 000000000..460e8b63e
--- /dev/null
+++ b/salut/src/text-helper.c
@@ -0,0 +1,361 @@
+/*
+ * text-helper.c - Source for TextHelper
+ * Copyright (C) 2006,2010 Collabora Ltd.
+ * Copyright (C) 2006 Nokia Corporation
+ * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+ * @author Robert McQueen <robert.mcqueen@collabora.co.uk>
+ * @author Senko Rasic <senko@senko.net>
+ * @author Sjoerd Simons <sjoerd@luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define _GNU_SOURCE /* Needed for strptime (_XOPEN_SOURCE can also be used). */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-namespaces.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/message-mixin.h>
+
+#define DEBUG_FLAG DEBUG_IM
+#include "debug.h"
+
+#include "text-helper.h"
+
+#include "util.h"
+
+static void
+add_text (WockyStanza *stanza, const gchar *text)
+{
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+ WockyNode *htmlnode;
+
+ wocky_node_add_child_with_content (node, "body", text);
+
+ /* Add plain xhtml-im node */
+ htmlnode = wocky_node_add_child_ns (node, "html",
+ WOCKY_XMPP_NS_XHTML_IM);
+ wocky_node_add_child_with_content_ns (htmlnode, "body", text,
+ WOCKY_W3C_NS_XHTML);
+}
+
+static WockyStanza *
+create_message_stanza (const gchar *from, const gchar *to,
+ SalutContact *contact, TpChannelTextMessageType type, const gchar *text,
+ GError **error)
+{
+ WockyStanza *stanza;
+ WockyNode *node;
+
+ if (type > TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE)
+ {
+ DEBUG ("invalid message type %u", type);
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "invalid message type: %u", type);
+
+ return NULL;
+ }
+ stanza = wocky_stanza_new ("message", WOCKY_XMPP_NS_JABBER_CLIENT);
+ node = wocky_stanza_get_top_node (stanza);
+
+ wocky_node_set_attribute (node, "from", from);
+ wocky_node_set_attribute (node, "to", to);
+
+ if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)
+ {
+ gchar *tmp;
+ tmp = g_strconcat ("/me ", text, NULL);
+ add_text (stanza, tmp);
+ g_free (tmp);
+ }
+ else
+ {
+ add_text (stanza, text);
+ }
+
+ if (contact != NULL)
+ wocky_stanza_set_to_contact (stanza, WOCKY_CONTACT (contact));
+
+ return stanza;
+}
+
+WockyStanza *
+text_helper_create_message (const gchar *from,
+ SalutContact *to,
+ TpChannelTextMessageType type,
+ const gchar *text,
+ GError **error)
+{
+ WockyStanza *stanza;
+ WockyNode *node;
+
+ stanza = create_message_stanza (from, to->name, to, type, text, error);
+ node = wocky_stanza_get_top_node (stanza);
+
+ if (stanza == NULL)
+ return NULL;
+
+ switch (type)
+ {
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL:
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION:
+ wocky_node_set_attribute (node, "type", "chat");
+ break;
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE:
+ wocky_node_set_attribute (node, "type", "normal");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return stanza;
+}
+
+WockyStanza *
+text_helper_create_message_groupchat (const gchar *from,
+ const gchar *to,
+ TpChannelTextMessageType type,
+ const gchar *text,
+ GError **error)
+{
+ WockyStanza *stanza;
+ WockyNode *node;
+
+ stanza = create_message_stanza (from, to, NULL, type, text, error);
+ if (stanza == NULL)
+ return NULL;
+
+ node = wocky_stanza_get_top_node (stanza);
+
+ switch (type)
+ {
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL:
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION:
+ wocky_node_set_attribute (node, "type", "groupchat");
+ break;
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE:
+ wocky_node_set_attribute (node, "type", "normal");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return stanza;
+}
+
+gboolean
+text_helper_parse_incoming_message (WockyStanza *stanza,
+ const gchar **from,
+ TpChannelTextMessageType *msgtype,
+ const gchar **body,
+ const gchar **body_offset)
+{
+ const gchar *type;
+ WockyNode *node;
+ WockyNode *event;
+ WockyNode *top_node = wocky_stanza_get_top_node (stanza);
+
+ *from = wocky_node_get_attribute (top_node, "from");
+ type = wocky_node_get_attribute (top_node, "type");
+ /* Work around iChats strange way of doing typing notification */
+ event = wocky_node_get_child_ns (top_node, "x",
+ WOCKY_XMPP_NS_EVENT);
+
+ if (event != NULL)
+ {
+ /* If the event has a composing and an id child, this is a typing
+ * notification and it should be dropped */
+ if (wocky_node_get_child (event, "composing") != NULL &&
+ wocky_node_get_child (event, "id") != NULL)
+ {
+ return FALSE;
+ }
+ }
+ /*
+ * Parse body if it exists.
+ */
+ node = wocky_node_get_child (top_node, "body");
+
+ if (node)
+ {
+ *body = node->content;
+ }
+ else
+ {
+ *body = NULL;
+ }
+
+
+ /* Messages starting with /me are ACTION messages, and the /me should be
+ * removed. type="chat" messages are NORMAL. everything else is
+ * something that doesn't necessarily expect a reply or ongoing
+ * conversation ("normal") or has been auto-sent, so we make it NOTICE in
+ * all other cases. */
+
+ *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE;
+ *body_offset = *body;
+
+ if (*body)
+ {
+ if (0 == strncmp (*body, "/me ", 4))
+ {
+ *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION;
+ *body_offset = *body + 4;
+ }
+ else if (type != NULL && (0 == strcmp (type, "chat") ||
+ 0 == strcmp (type, "groupchat")))
+ {
+ *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
+ *body_offset = *body;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+text_helper_validate_tp_message (TpMessage *message,
+ guint *type,
+ gchar **token,
+ gchar **text,
+ GError **error)
+{
+ const GHashTable *part;
+ guint msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
+ const gchar *msgtext;
+ gchar *msgtoken;
+ gboolean valid = TRUE;
+
+ if (tp_message_count_parts (message) != 2)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid number of message parts, expected 2, got %d",
+ tp_message_count_parts (message));
+ return FALSE;
+ }
+
+ part = tp_message_peek (message, 0);
+
+ if (tp_asv_lookup (part, "message-type"))
+ msgtype = tp_asv_get_uint32 (part, "message-type", &valid);
+
+ if (!valid || msgtype > TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid message type");
+ return FALSE;
+ }
+
+ part = tp_message_peek (message, 1);
+ msgtext = tp_asv_get_string (part, "content");
+
+ if (msgtext == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Empty message content");
+ return FALSE;
+ }
+
+ msgtoken = salut_generate_id ();
+ tp_message_set_string (message, 0, "message-token", msgtoken);
+
+ if (text != NULL)
+ *text = g_strdup (msgtext);
+
+ if (type != NULL)
+ *type = msgtype;
+
+ if (token != NULL)
+ *token = msgtoken;
+ else
+ g_free (msgtoken);
+
+ return TRUE;
+}
+
+void
+text_helper_report_delivery_error (TpSvcChannel *self,
+ guint error_type,
+ guint timestamp,
+ guint type,
+ const gchar *text,
+ const gchar *token)
+{
+ TpBaseConnection *base_conn;
+ TpHandle handle;
+ guint handle_type;
+ TpMessage *message;
+ TpMessage *delivery_echo;
+
+ g_object_get (self,
+ "connection", &base_conn,
+ "handle", &handle,
+ "handle-type", &handle_type,
+ NULL);
+
+ delivery_echo = tp_message_new (base_conn, 2, 2);
+ tp_message_set_handle (delivery_echo, 0, "message-sender",
+ TP_HANDLE_TYPE_CONTACT, base_conn->self_handle);
+ tp_message_set_uint32 (delivery_echo, 0, "message-type", type);
+ tp_message_set_int64 (delivery_echo, 0, "message-sent", (gint64)timestamp);
+ tp_message_set_string (delivery_echo, 1, "content-type", "text/plain");
+ tp_message_set_string (delivery_echo, 1, "content", text);
+
+ message = tp_message_new (base_conn, 1, 1);
+ tp_message_set_handle (message, 0, "message-sender", handle_type, handle);
+ tp_message_set_uint32 (message, 0, "message-type",
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT);
+ tp_message_set_uint32 (message, 0, "delivery-status",
+ TP_DELIVERY_STATUS_TEMPORARILY_FAILED);
+ tp_message_set_uint32 (message, 0, "delivery-error",
+ TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE);
+ tp_message_set_string (message, 0, "delivery-token", token);
+ tp_message_take_message (message, 0, "delivery-echo", delivery_echo);
+
+ g_object_unref (base_conn);
+
+ tp_message_mixin_take_received (G_OBJECT (self), message);
+}
+
+TpMessage *
+text_helper_create_received_message (TpBaseConnection *base_conn,
+ guint sender_handle,
+ guint timestamp,
+ guint type,
+ const gchar *text)
+{
+ TpMessage *message = tp_message_new (base_conn, 2, 2);
+
+ tp_message_set_uint32 (message, 0, "message-type", type);
+ tp_message_set_handle (message, 0, "message-sender",
+ TP_HANDLE_TYPE_CONTACT, sender_handle);
+ tp_message_set_int64 (message, 0, "message-received", timestamp);
+
+ tp_message_set_string (message, 1, "content-type", "text/plain");
+ tp_message_set_string (message, 1, "content", text);
+
+ return message;
+}
diff --git a/salut/src/text-helper.h b/salut/src/text-helper.h
new file mode 100644
index 000000000..64a1a4f16
--- /dev/null
+++ b/salut/src/text-helper.h
@@ -0,0 +1,60 @@
+/*
+ * text-helper.h - Header for TextHelper
+ * Copyright (C) 2006 Collabora Ltd.
+ * Copyright (C) 2006 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TEXT_HELPER_H__
+#define __TEXT_HELPER_H__
+
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/channel.h>
+#include <wocky/wocky-stanza.h>
+
+#include "contact.h"
+
+
+/* Utility functions for the helper user */
+gboolean
+text_helper_parse_incoming_message (WockyStanza *stanza,
+ const gchar **from, TpChannelTextMessageType *msgtype,
+ const gchar **body, const gchar **body_offset);
+
+WockyStanza *
+text_helper_create_message (const gchar *from,
+ SalutContact *to, TpChannelTextMessageType type,
+ const gchar *text, GError **error);
+
+WockyStanza *
+text_helper_create_message_groupchat (const gchar *from, const gchar *to,
+ TpChannelTextMessageType type, const gchar *text, GError **error);
+
+gboolean
+text_helper_validate_tp_message (TpMessage *message,
+ guint *type, gchar **token, gchar **text, GError **error);
+
+void
+text_helper_report_delivery_error (TpSvcChannel *self, guint error_type,
+ guint timestamp, guint type, const gchar *text, const gchar *token);
+
+TpMessage *
+text_helper_create_received_message (TpBaseConnection *base_conn,
+ guint sender_handle, guint timestamp, guint type, const gchar *text);
+
+G_END_DECLS
+
+#endif /* #ifndef __TEXT_HELPER_H__ */
diff --git a/salut/src/tube-dbus.c b/salut/src/tube-dbus.c
new file mode 100644
index 000000000..6ed2dec9c
--- /dev/null
+++ b/salut/src/tube-dbus.c
@@ -0,0 +1,1883 @@
+/*
+ * tube-dbus.c - Source for SalutTubeDBus
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tube-dbus.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+#include <gibber/gibber-bytestream-muc.h>
+#include <gibber/gibber-muc-connection.h>
+
+#define DEBUG_FLAG DEBUG_TUBES
+#include "debug.h"
+#include "connection.h"
+#include "tube-iface.h"
+#include "sha1/sha1-util.h"
+
+/* When we receive D-Bus messages to be delivered to the application and the
+ * application is not yet connected to the D-Bus tube, theses D-Bus messages
+ * are queued and delivered when the application connects to the D-Bus tube.
+ *
+ * If the application never connects, there is a risk that the contact sends
+ * too many messages and eat all the memory. To avoid this, there is an
+ * arbitrary limit on the queue size set to 4MB. */
+#define MAX_QUEUE_SIZE (4096*1024)
+
+static void channel_iface_init (gpointer, gpointer);
+static void tube_iface_init (gpointer g_iface, gpointer iface_data);
+static void dbustube_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SalutTubeDBus, salut_tube_dbus, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_TUBE_IFACE, tube_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_DBUS_TUBE,
+ dbustube_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_TUBE,
+ NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));
+
+static const gchar *salut_tube_dbus_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ /* If more interfaces are added, either keep Group as the first, or change
+ * the implementations of salut_tube_dbus_get_interfaces () and
+ * salut_tube_dbus_get_property () too */
+ TP_IFACE_CHANNEL_INTERFACE_TUBE,
+ NULL
+};
+
+static const gchar * const salut_tube_dbus_channel_allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ TP_IFACE_CHANNEL ".TargetID",
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName",
+ NULL
+};
+
+/* signals */
+enum
+{
+ OPENED,
+ CLOSED,
+ OFFERED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_TUBES_CHANNEL,
+ PROP_HANDLE,
+ PROP_HANDLE_TYPE,
+ PROP_SELF_HANDLE,
+ PROP_MUC_CONNECTION,
+ PROP_ID,
+ PROP_BYTESTREAM,
+ PROP_STREAM_ID,
+ PROP_TYPE,
+ PROP_INITIATOR_HANDLE,
+ PROP_SERVICE,
+ PROP_PARAMETERS,
+ PROP_STATE,
+ PROP_DBUS_ADDRESS,
+ PROP_DBUS_NAME,
+ PROP_DBUS_NAMES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_REQUESTED,
+ PROP_TARGET_ID,
+ PROP_INITIATOR_ID,
+ PROP_SUPPORTED_ACCESS_CONTROLS,
+ LAST_PROPERTY
+};
+
+typedef struct _SalutTubeDBusPrivate SalutTubeDBusPrivate;
+struct _SalutTubeDBusPrivate
+{
+ SalutConnection *conn;
+ gchar *object_path;
+ SalutTubesChannel *tubes_channel;
+ TpHandle handle;
+ TpHandleType handle_type;
+ TpHandle self_handle;
+ GibberMucConnection *muc_connection;
+ guint id;
+ GibberBytestreamIface *bytestream;
+ gchar *stream_id;
+ TpHandle initiator;
+ gchar *service;
+ GHashTable *parameters;
+ TpSocketAccessControl access_control;
+ /* GArray of guint */
+ GArray *supported_access_controls;
+
+ /* For outgoing tubes, TRUE if the offer has been sent over the network. For
+ * incoming tubes, always TRUE.
+ */
+ gboolean offered;
+
+
+ /* our unique D-Bus name on the virtual tube bus */
+ gchar *dbus_local_name;
+ /* the address that we are listening for D-Bus connections on */
+ gchar *dbus_srv_addr;
+ /* the path of the UNIX socket used by the D-Bus server */
+ gchar *socket_path;
+ /* the server that's listening on dbus_srv_addr */
+ DBusServer *dbus_srv;
+ /* the connection to dbus_srv from a local client, or NULL */
+ DBusConnection *dbus_conn;
+ /* the queue of D-Bus messages to be delivered to a local client when it
+ * will connect */
+ GSList *dbus_msg_queue;
+ /* current size of the queue in bytes. The maximum is MAX_QUEUE_SIZE */
+ unsigned long dbus_msg_queue_size;
+ /* mapping of contact handle -> D-Bus name (NULL for 1-1 D-Bus tubes) */
+ GHashTable *dbus_names;
+
+ /* Message reassembly buffer (CONTACT tubes only) */
+ GString *reassembly_buffer;
+ /* Number of bytes that will be in the next message, 0 if unknown */
+ guint32 reassembly_bytes_needed;
+
+ gboolean closed;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_TUBE_DBUS_GET_PRIVATE(obj) \
+ ((SalutTubeDBusPrivate *) ((SalutTubeDBus *) obj)->priv)
+
+static void data_received_cb (GibberBytestreamIface *bytestream,
+ const gchar *from, GString *data, gpointer user_data);
+
+/*
+ * Characters used are permissible both in filenames and in D-Bus names. (See
+ * D-Bus specification for restrictions.)
+ */
+static void
+generate_ascii_string (guint len,
+ gchar *buf)
+{
+ const gchar *chars =
+ "0123456789"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "_-";
+ guint i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = chars[g_random_int_range (0, 64)];
+}
+
+static gchar *
+generate_dbus_unique_name (const gchar *nick)
+{
+ gchar *encoded, *result;
+ size_t len;
+ guint i;
+
+ len = strlen (nick);
+
+ if (len <= 186)
+ {
+ encoded = g_base64_encode ((const guchar *) nick, strlen (nick));
+ }
+ else
+ {
+ guchar sha1[20];
+ GString *tmp;
+
+ sha1_bin (nick, len, sha1);
+ tmp = g_string_sized_new (169 + 20);
+
+ g_string_append_len (tmp, nick, 169);
+ g_string_append_len (tmp, (const gchar *) sha1, 20);
+
+ encoded = g_base64_encode ((const guchar *) tmp->str, tmp->len);
+
+ g_string_free (tmp, TRUE);
+ }
+
+ for (i = 0; encoded[i] != '\0'; i++)
+ {
+ switch (encoded[i])
+ {
+ case '+':
+ encoded[i] = '_';
+ break;
+ case '/':
+ encoded[i] = '-';
+ break;
+ case '=':
+ encoded[i] = 'A';
+ break;
+ }
+ }
+
+ result = g_strdup_printf (":2.%s", encoded);
+
+ g_free (encoded);
+ return result;
+}
+
+struct _find_contact_data
+{
+ const gchar *contact;
+ TpHandle handle;
+};
+
+static DBusHandlerResult
+filter_cb (DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ SalutTubeDBus *tube = SALUT_TUBE_DBUS (data);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (tube);
+ gchar *marshalled = NULL;
+ gint len;
+ const gchar *destination;
+ dbus_uint32_t serial;
+
+ if (dbus_message_get_type (msg) == DBUS_MESSAGE_TYPE_SIGNAL &&
+ !tp_strdiff (dbus_message_get_interface (msg),
+ "org.freedesktop.DBus.Local") &&
+ !tp_strdiff (dbus_message_get_member (msg), "Disconnected"))
+ {
+ /* connection was disconnected */
+ DEBUG ("connection was disconnected");
+ dbus_connection_close (priv->dbus_conn);
+ dbus_connection_unref (priv->dbus_conn);
+ priv->dbus_conn = NULL;
+ goto out;
+ }
+
+ if (priv->dbus_local_name != NULL)
+ {
+ dbus_message_set_sender (msg, priv->dbus_local_name);
+ }
+
+ if (!dbus_message_marshal (msg, &marshalled, &len))
+ goto out;
+
+ gibber_bytestream_iface_send (priv->bytestream, len, marshalled);
+
+ if (GIBBER_IS_BYTESTREAM_MUC (priv->bytestream))
+ {
+ /* In a Salut MUC we never receive messages we sent, so we need to
+ * artificially receive our own messages. */
+ destination = dbus_message_get_destination (msg);
+ if (destination == NULL || !tp_strdiff (priv->dbus_local_name,
+ destination))
+ {
+ dbus_connection_send (priv->dbus_conn, msg, &serial);
+ }
+ }
+
+out:
+ if (marshalled != NULL)
+ g_free (marshalled);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static dbus_bool_t
+allow_all_connections (DBusConnection *conn,
+ unsigned long uid,
+ void *data)
+{
+ return TRUE;
+}
+
+static void
+new_connection_cb (DBusServer *server,
+ DBusConnection *conn,
+ void *data)
+{
+ SalutTubeDBus *tube = SALUT_TUBE_DBUS (data);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (tube);
+ guint32 serial;
+ GSList *i;
+
+ if (priv->dbus_conn != NULL)
+ /* we already have a connection; drop this new one */
+ /* return without reffing conn means it will be dropped */
+ return;
+
+ DEBUG ("got connection");
+
+ dbus_connection_ref (conn);
+ dbus_connection_setup_with_g_main (conn, NULL);
+ dbus_connection_add_filter (conn, filter_cb, tube, NULL);
+ priv->dbus_conn = conn;
+
+ if (priv->access_control == TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
+ {
+ /* By default libdbus use Credentials access control. If user wants
+ * to use the Localhost access control, we need to bypass this check. */
+ dbus_connection_set_unix_user_function (conn, allow_all_connections,
+ NULL, NULL);
+ }
+
+ /* We may have received messages to deliver before the local connection is
+ * established. Theses messages are kept in the dbus_msg_queue list and are
+ * delivered as soon as we get the connection. */
+ DEBUG ("%u messages in the queue (%lu bytes)",
+ g_slist_length (priv->dbus_msg_queue), priv->dbus_msg_queue_size);
+ priv->dbus_msg_queue = g_slist_reverse (priv->dbus_msg_queue);
+ for (i = priv->dbus_msg_queue; i != NULL; i = g_slist_delete_link (i, i))
+ {
+ DBusMessage *msg = i->data;
+ DEBUG ("delivering queued message from '%s' to '%s' on the "
+ "new connection",
+ dbus_message_get_sender (msg),
+ dbus_message_get_destination (msg));
+ dbus_connection_send (priv->dbus_conn, msg, &serial);
+ dbus_message_unref (msg);
+ }
+ priv->dbus_msg_queue = NULL;
+ priv->dbus_msg_queue_size = 0;
+}
+
+static void
+do_close (SalutTubeDBus *self)
+{
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ if (priv->closed)
+ return;
+ priv->closed = TRUE;
+
+ if (priv->bytestream != NULL)
+ {
+ gibber_bytestream_iface_close (priv->bytestream, NULL);
+ }
+ else
+ {
+ g_signal_emit (G_OBJECT (self), signals[CLOSED], 0);
+ }
+}
+
+/* There is two step to enable receiving a D-Bus connection from the local
+ * application:
+ * - listen on the socket
+ * - add the socket in the mainloop
+ *
+ * We need to know the socket path to return from the AcceptDBusTube D-Bus
+ * call but the socket in the mainloop must be added only when we are ready
+ * to receive connections, that is when the bytestream is fully open with the
+ * remote contact.
+ *
+ * See also Bug 13891:
+ * https://bugs.freedesktop.org/show_bug.cgi?id=13891
+ * */
+static gboolean
+create_dbus_server (SalutTubeDBus *self,
+ GError **err)
+{
+#define SERVER_LISTEN_MAX_TRIES 5
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+ guint i;
+
+ if (priv->dbus_srv != NULL)
+ return TRUE;
+
+ for (i = 0; i < SERVER_LISTEN_MAX_TRIES; i++)
+ {
+ gchar suffix[8];
+ DBusError error;
+
+ g_free (priv->dbus_srv_addr);
+ g_free (priv->socket_path);
+
+ generate_ascii_string (8, suffix);
+ priv->socket_path = g_strdup_printf ("%s/dbus-%.8s",
+ g_get_tmp_dir (), suffix);
+ priv->dbus_srv_addr = g_strdup_printf ("unix:path=%s",
+ priv->socket_path);
+
+ dbus_error_init (&error);
+ priv->dbus_srv = dbus_server_listen (priv->dbus_srv_addr, &error);
+
+ if (priv->dbus_srv != NULL)
+ break;
+
+ DEBUG ("dbus_server_listen failed (try %u): %s: %s", i, error.name,
+ error.message);
+ dbus_error_free (&error);
+ }
+
+ if (priv->dbus_srv == NULL)
+ {
+ DEBUG ("all attempts failed. Close the tube");
+
+ g_free (priv->dbus_srv_addr);
+ priv->dbus_srv_addr = NULL;
+
+ g_free (priv->socket_path);
+ priv->socket_path = NULL;
+
+ g_set_error (err, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Can't create D-Bus server");
+ return FALSE;
+ }
+
+ DEBUG ("listening on %s", priv->dbus_srv_addr);
+
+ dbus_server_set_new_connection_function (priv->dbus_srv, new_connection_cb,
+ self, NULL);
+
+ return TRUE;
+}
+
+static void
+tube_dbus_open (SalutTubeDBus *self)
+{
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ g_signal_connect (priv->bytestream, "data-received",
+ G_CALLBACK (data_received_cb), self);
+
+ if (!create_dbus_server (self, NULL))
+ do_close (self);
+
+ if (priv->dbus_srv != NULL)
+ {
+ dbus_server_setup_with_g_main (priv->dbus_srv, NULL);
+ }
+}
+
+static void
+salut_tube_dbus_init (SalutTubeDBus *self)
+{
+ SalutTubeDBusPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_TUBE_DBUS, SalutTubeDBusPrivate);
+
+ self->priv = priv;
+}
+
+static void
+unref_handle_foreach (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ TpHandle handle = GPOINTER_TO_UINT (key);
+ TpHandleRepoIface *contact_repo = (TpHandleRepoIface *) user_data;
+
+ tp_handle_unref (contact_repo, handle);
+}
+
+static TpTubeChannelState
+get_tube_state (SalutTubeDBus *self)
+{
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+ GibberBytestreamState bytestream_state;
+
+ if (!priv->offered)
+ return TP_TUBE_CHANNEL_STATE_NOT_OFFERED;
+
+ if (priv->bytestream == NULL)
+ /* bytestream not yet created as we're waiting for the SI reply */
+ return TP_TUBE_STATE_REMOTE_PENDING;
+
+ g_object_get (priv->bytestream, "state", &bytestream_state, NULL);
+
+ switch (bytestream_state)
+ {
+ case GIBBER_BYTESTREAM_STATE_OPEN:
+ return TP_TUBE_STATE_OPEN;
+ break;
+ case GIBBER_BYTESTREAM_STATE_LOCAL_PENDING:
+ case GIBBER_BYTESTREAM_STATE_ACCEPTED:
+ return TP_TUBE_STATE_LOCAL_PENDING;
+ break;
+ case GIBBER_BYTESTREAM_STATE_INITIATING:
+ return TP_TUBE_STATE_REMOTE_PENDING;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+bytestream_state_changed_cb (GibberBytestreamIface *bytestream,
+ GibberBytestreamState state,
+ gpointer user_data)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (user_data);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ if (state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ {
+ if (priv->bytestream != NULL)
+ {
+ g_object_unref (priv->bytestream);
+ priv->bytestream = NULL;
+ }
+
+ priv->closed = TRUE;
+ g_signal_emit (G_OBJECT (self), signals[CLOSED], 0);
+ }
+ else if (state == GIBBER_BYTESTREAM_STATE_OPEN)
+ {
+ tube_dbus_open (self);
+
+ tp_svc_channel_interface_tube_emit_tube_channel_state_changed (self,
+ TP_TUBE_CHANNEL_STATE_OPEN);
+
+ g_signal_emit (G_OBJECT (self), signals[OPENED], 0);
+ }
+}
+
+static void
+salut_tube_dbus_dispose (GObject *object)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (object);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ if (priv->dispose_has_run)
+ return;
+
+ if (priv->bytestream)
+ {
+ gibber_bytestream_iface_close (priv->bytestream, NULL);
+ }
+
+ if (priv->dbus_conn)
+ {
+ dbus_connection_close (priv->dbus_conn);
+ dbus_connection_unref (priv->dbus_conn);
+ }
+
+ if (priv->dbus_srv)
+ dbus_server_unref (priv->dbus_srv);
+
+ if (priv->socket_path != NULL)
+ {
+ if (g_unlink (priv->socket_path) != 0)
+ {
+ DEBUG ("unlink of %s failed: %s", priv->socket_path,
+ g_strerror (errno));
+ }
+ }
+
+ if (priv->dbus_msg_queue != NULL)
+ {
+ GSList *i;
+ for (i = priv->dbus_msg_queue; i != NULL; i = g_slist_delete_link (i, i))
+ {
+ DBusMessage *msg = i->data;
+ dbus_message_unref (msg);
+ }
+ priv->dbus_msg_queue = NULL;
+ priv->dbus_msg_queue_size = 0;
+ }
+
+ g_free (priv->dbus_srv_addr);
+ priv->dbus_srv_addr = NULL;
+ g_free (priv->socket_path);
+ priv->socket_path = NULL;
+ g_free (priv->dbus_local_name);
+ priv->dbus_local_name = NULL;
+
+ if (priv->dbus_names)
+ {
+ g_hash_table_foreach (priv->dbus_names, unref_handle_foreach,
+ contact_repo);
+ g_hash_table_unref (priv->dbus_names);
+ }
+
+ if (priv->muc_connection != NULL)
+ {
+ g_object_unref (priv->muc_connection);
+ priv->muc_connection = NULL;
+ }
+
+ if (priv->reassembly_buffer)
+ g_string_free (priv->reassembly_buffer, TRUE);
+
+ tp_handle_unref (contact_repo, priv->initiator);
+
+ priv->dispose_has_run = TRUE;
+
+ if (G_OBJECT_CLASS (salut_tube_dbus_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_tube_dbus_parent_class)->dispose (object);
+}
+
+static void
+salut_tube_dbus_finalize (GObject *object)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (object);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ g_free (priv->object_path);
+ g_free (priv->stream_id);
+ g_free (priv->service);
+ g_hash_table_unref (priv->parameters);
+ g_array_unref (priv->supported_access_controls);
+
+ G_OBJECT_CLASS (salut_tube_dbus_parent_class)->finalize (object);
+}
+
+static void
+salut_tube_dbus_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (object);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ case PROP_INTERFACES:
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ /* 1-1 tubes - omit the Group interface */
+ g_value_set_boxed (value, salut_tube_dbus_interfaces + 1);
+ }
+ else
+ {
+ /* MUC tubes */
+ g_value_set_boxed (value, salut_tube_dbus_interfaces);
+ }
+ break;
+ case PROP_TUBES_CHANNEL:
+ g_value_set_object (value, priv->tubes_channel);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, priv->handle);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, priv->handle_type);
+ break;
+ case PROP_SELF_HANDLE:
+ g_value_set_uint (value, priv->self_handle);
+ break;
+ case PROP_MUC_CONNECTION:
+ g_value_set_object (value, priv->muc_connection);
+ break;
+ case PROP_ID:
+ g_value_set_uint (value, priv->id);
+ break;
+ case PROP_BYTESTREAM:
+ g_value_set_object (value, priv->bytestream);
+ break;
+ case PROP_STREAM_ID:
+ g_value_set_string (value, priv->stream_id);
+ break;
+ case PROP_TYPE:
+ g_value_set_uint (value, TP_TUBE_TYPE_DBUS);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, priv->initiator);
+ break;
+ case PROP_SERVICE:
+ g_value_set_string (value, priv->service);
+ break;
+ case PROP_PARAMETERS:
+ g_value_set_boxed (value, priv->parameters);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, get_tube_state (self));
+ break;
+ case PROP_DBUS_ADDRESS:
+ g_value_set_string (value, priv->dbus_srv_addr);
+ break;
+ case PROP_DBUS_NAME:
+ g_value_set_string (value, priv->dbus_local_name);
+ break;
+ case PROP_DBUS_NAMES:
+ g_value_set_boxed (value, priv->dbus_names);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ {
+ GHashTable *properties;
+
+ properties = tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE, "ServiceName",
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE, "SupportedAccessControls",
+ NULL);
+
+ if (priv->initiator != priv->self_handle)
+ {
+ /* channel has not been requested so Parameters is immutable */
+ GValue *prop_value = g_slice_new0 (GValue);
+
+ /* FIXME: use tp_dbus_properties_mixin_add_properties once it's
+ * added in tp-glib */
+ tp_dbus_properties_mixin_get (object,
+ TP_IFACE_CHANNEL_INTERFACE_TUBE, "Parameters",
+ prop_value, NULL);
+ g_assert (G_IS_VALUE (prop_value));
+
+ g_hash_table_insert (properties,
+ g_strdup_printf ("%s.%s", TP_IFACE_CHANNEL_INTERFACE_TUBE,
+ "Parameters"), prop_value);
+ }
+
+ g_value_take_boxed (value, properties);
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value,
+ (priv->initiator == priv->self_handle));
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+
+ /* some channel can have o.f.T.Channel.InitiatorHandle == 0 but
+ * tubes always have an initiator */
+ g_assert (priv->initiator != 0);
+
+ g_value_set_string (value,
+ tp_handle_inspect (repo, priv->initiator));
+ }
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, priv->handle_type);
+
+ g_value_set_string (value,
+ tp_handle_inspect (repo, priv->handle));
+ }
+ break;
+ case PROP_SUPPORTED_ACCESS_CONTROLS:
+ g_value_set_boxed (value, priv->supported_access_controls);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_tube_dbus_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (object);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_CHANNEL_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changeable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ case PROP_TUBES_CHANNEL:
+ priv->tubes_channel = g_value_get_object (value);
+ break;
+ case PROP_HANDLE:
+ priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ priv->handle_type = g_value_get_uint (value);
+ break;
+ case PROP_SELF_HANDLE:
+ priv->self_handle = g_value_get_uint (value);
+ break;
+ case PROP_MUC_CONNECTION:
+ priv->muc_connection = g_value_get_object (value);
+ g_object_ref (priv->muc_connection);
+ break;
+ case PROP_ID:
+ priv->id = g_value_get_uint (value);
+ break;
+ case PROP_BYTESTREAM:
+ if (priv->bytestream == NULL)
+ {
+ GibberBytestreamState state;
+
+ priv->bytestream = g_value_get_object (value);
+ g_object_ref (priv->bytestream);
+
+ g_object_get (priv->bytestream, "state", &state, NULL);
+ if (state == GIBBER_BYTESTREAM_STATE_OPEN)
+ {
+ tube_dbus_open (self);
+ }
+
+ g_signal_connect (priv->bytestream, "state-changed",
+ G_CALLBACK (bytestream_state_changed_cb), self);
+ }
+ break;
+ case PROP_INITIATOR_HANDLE:
+ priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_SERVICE:
+ g_free (priv->service);
+ priv->service = g_value_dup_string (value);
+ break;
+ case PROP_PARAMETERS:
+ if (priv->parameters != NULL)
+ g_hash_table_unref (priv->parameters);
+ priv->parameters = g_value_dup_boxed (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+salut_tube_dbus_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutTubeDBus *self;
+ SalutTubeDBusPrivate *priv;
+ TpDBusDaemon *bus;
+ TpBaseConnection *base_conn;
+ TpHandleRepoIface *contact_repo;
+ TpHandleRepoIface *handles_repo;
+ TpSocketAccessControl access_control;
+
+ obj = G_OBJECT_CLASS (salut_tube_dbus_parent_class)->
+ constructor (type, n_props, props);
+ self = SALUT_TUBE_DBUS (obj);
+
+ priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ base_conn = TP_BASE_CONNECTION (priv->conn);
+ handles_repo = tp_base_connection_get_handles (base_conn, priv->handle_type);
+
+ /* Ref the initiator handle */
+ g_assert (priv->conn != NULL);
+ g_assert (priv->initiator != 0);
+ contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ tp_handle_ref (contact_repo, priv->initiator);
+
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+
+ DEBUG ("Registering at '%s'", priv->object_path);
+
+ g_assert (priv->self_handle != 0);
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM)
+ {
+ /*
+ * We have to create an MUC bytestream that will be
+ * used by this MUC tube to communicate.
+ *
+ * We don't create the bytestream of private D-Bus tube yet.
+ * It will be when we'll receive the answer of the SI request
+ */
+ GibberBytestreamMuc *bytestream;
+ const gchar *peer_id;
+
+ g_assert (priv->muc_connection != NULL);
+
+ priv->dbus_names = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_free);
+
+ priv->dbus_local_name = generate_dbus_unique_name (priv->conn->name);
+
+ DEBUG ("local name: %s", priv->dbus_local_name);
+
+ peer_id = tp_handle_inspect (handles_repo, priv->handle);
+ bytestream = g_object_new (GIBBER_TYPE_BYTESTREAM_MUC,
+ "muc-connection", priv->muc_connection,
+ "state", GIBBER_BYTESTREAM_STATE_LOCAL_PENDING,
+ "self-id", priv->conn->name,
+ "peer-id", peer_id,
+ NULL);
+
+ g_object_get (bytestream, "stream-id", &priv->stream_id, NULL);
+
+ g_object_set (self, "bytestream", bytestream, NULL);
+ g_object_unref (bytestream);
+ }
+ else
+ {
+ /* Private tube */
+ g_assert (priv->muc_connection == NULL);
+
+ /* The D-Bus names mapping is used in muc tubes only */
+ priv->dbus_local_name = NULL;
+ priv->dbus_names = NULL;
+
+ /* For contact tubes we need to be able to reassemble messages. */
+ priv->reassembly_buffer = g_string_new ("");
+ priv->reassembly_bytes_needed = 0;
+ }
+
+ if (priv->initiator == priv->self_handle)
+ {
+ priv->offered = FALSE;
+ }
+ else
+ {
+ /* Incoming tubes have already been offered, as it were. */
+ priv->offered = TRUE;
+ }
+
+ /* default access control is Credentials as that's the one used by the old
+ * tube API */
+ priv->access_control = TP_SOCKET_ACCESS_CONTROL_CREDENTIALS;
+
+ /* Set SupportedAccessesControl */
+ priv->supported_access_controls = g_array_sized_new (FALSE, FALSE,
+ sizeof (guint), 1);
+ access_control = TP_SOCKET_ACCESS_CONTROL_CREDENTIALS;
+ g_array_append_val (priv->supported_access_controls, access_control);
+ access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val (priv->supported_access_controls, access_control);
+
+ return obj;
+}
+
+static void
+salut_tube_dbus_class_init (SalutTubeDBusClass *salut_tube_dbus_class)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl dbus_tube_props[] = {
+ { "ServiceName", "service", NULL },
+ { "DBusNames", "dbus-names", NULL },
+ { "SupportedAccessControls", "supported-access-controls", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl tube_iface_props[] = {
+ { "Parameters", "parameters", NULL },
+ { "State", "state", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { TP_IFACE_CHANNEL_TYPE_DBUS_TUBE,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ dbus_tube_props,
+ },
+ { TP_IFACE_CHANNEL_INTERFACE_TUBE,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ tube_iface_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_tube_dbus_class);
+ GParamSpec *param_spec;
+
+ object_class->get_property = salut_tube_dbus_get_property;
+ object_class->set_property = salut_tube_dbus_set_property;
+ object_class->constructor = salut_tube_dbus_constructor;
+
+ g_type_class_add_private (salut_tube_dbus_class,
+ sizeof (SalutTubeDBusPrivate));
+
+ object_class->dispose = salut_tube_dbus_dispose;
+ object_class->finalize = salut_tube_dbus_finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_CONNECTION,
+ "connection");
+ g_object_class_override_property (object_class, PROP_TUBES_CHANNEL,
+ "tubes-channel");
+ g_object_class_override_property (object_class, PROP_HANDLE,
+ "handle");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_SELF_HANDLE,
+ "self-handle");
+ g_object_class_override_property (object_class, PROP_ID,
+ "id");
+ g_object_class_override_property (object_class, PROP_TYPE,
+ "type");
+ g_object_class_override_property (object_class, PROP_INITIATOR_HANDLE,
+ "initiator-handle");
+ g_object_class_override_property (object_class, PROP_SERVICE,
+ "service");
+ g_object_class_override_property (object_class, PROP_PARAMETERS,
+ "parameters");
+ g_object_class_override_property (object_class, PROP_STATE,
+ "state");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object (
+ "muc-connection",
+ "GibberMucConnection object",
+ "Gibber MUC connection object used to carry messages for this "
+ "tube if it has a HANDLE_TYPE_ROOM handle",
+ GIBBER_TYPE_MUC_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MUC_CONNECTION,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "bytestream",
+ "Object implementing the GibberBytestreamIface interface",
+ "Bytestream object used for streaming data for this D-Bus",
+ G_TYPE_OBJECT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_BYTESTREAM, param_spec);
+
+ param_spec = g_param_spec_string (
+ "stream-id",
+ "stream id",
+ "The identifier of this tube's bytestream",
+ "",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAM_ID, param_spec);
+
+ param_spec = g_param_spec_string (
+ "dbus-address",
+ "D-Bus address",
+ "The D-Bus address on which this tube will listen for connections",
+ "",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DBUS_ADDRESS,
+ param_spec);
+
+ param_spec = g_param_spec_string (
+ "dbus-name",
+ "D-Bus name",
+ "The local D-Bus name on the virtual bus.",
+ "",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DBUS_NAME, param_spec);
+
+ param_spec = g_param_spec_boxed (
+ "dbus-names",
+ "D-Bus names",
+ "Mapping of contact handles to D-Bus names.",
+ TP_HASH_TYPE_DBUS_TUBE_PARTICIPANTS,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DBUS_NAMES, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Target JID",
+ "The string obtained by inspecting the target handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's bare JID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_boxed (
+ "supported-access-controls",
+ "Supported access-controls",
+ "GArray containing supported access controls.",
+ DBUS_TYPE_G_UINT_ARRAY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_SUPPORTED_ACCESS_CONTROLS, param_spec);
+
+ signals[OPENED] =
+ g_signal_new ("tube-opened",
+ G_OBJECT_CLASS_TYPE (salut_tube_dbus_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CLOSED] =
+ g_signal_new ("tube-closed",
+ G_OBJECT_CLASS_TYPE (salut_tube_dbus_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[OFFERED] =
+ g_signal_new ("tube-offered",
+ G_OBJECT_CLASS_TYPE (salut_tube_dbus_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ salut_tube_dbus_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutTubeDBusClass, dbus_props_class));
+}
+
+gboolean
+salut_tube_dbus_offer (SalutTubeDBus *self,
+ GError **error)
+{
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ if (priv->offered)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube has already been offered");
+ return FALSE;
+ }
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ /* TODO: we don't implement 1-1 D-Bus tube atm */
+ ;
+ }
+ else
+ {
+ priv->offered = TRUE;
+ g_object_set (priv->bytestream,
+ "state", GIBBER_BYTESTREAM_STATE_OPEN,
+ NULL);
+ }
+
+ if (!create_dbus_server (self, error))
+ return FALSE;
+
+ g_signal_emit (G_OBJECT (self), signals[OFFERED], 0);
+
+ return TRUE;
+}
+
+static void
+message_received (SalutTubeDBus *tube,
+ TpHandle sender,
+ const char *data,
+ size_t len)
+{
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (tube);
+ DBusMessage *msg;
+ DBusError error = {0,};
+ guint32 serial;
+
+ msg = dbus_message_demarshal (data, len, &error);
+
+ if (msg == NULL)
+ {
+ /* message was corrupted */
+ DEBUG ("received corrupted message from %d: %s: %s", sender,
+ error.name, error.message);
+ dbus_error_free (&error);
+ return;
+ }
+
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM)
+ {
+ const gchar *destination;
+ const gchar *sender_name;
+
+ destination = dbus_message_get_destination (msg);
+ /* If destination is NULL this msg is broadcasted (signals) so we don't
+ * have to check it */
+ if (destination != NULL && tp_strdiff (priv->dbus_local_name,
+ destination))
+ {
+ /* This message is not intended to this tube.
+ * Discard it. */
+ DEBUG ("message not intended to this tube (destination = %s)",
+ destination);
+ goto unref;
+ }
+
+ sender_name = g_hash_table_lookup (priv->dbus_names,
+ GUINT_TO_POINTER (sender));
+
+ if (tp_strdiff (sender_name, dbus_message_get_sender (msg)))
+ {
+ DEBUG ("invalid sender %s (expected %s for sender handle %d)",
+ dbus_message_get_sender (msg), sender_name, sender);
+ goto unref;
+ }
+ }
+
+ if (!priv->dbus_conn)
+ {
+ DEBUG ("no D-Bus connection: queuing the message");
+
+ /* If the application never connects to the private dbus connection, we
+ * don't want to eat all the memory. Only queue MAX_QUEUE_SIZE bytes. If
+ * there are more messages, drop them. */
+ if (priv->dbus_msg_queue_size + len > MAX_QUEUE_SIZE)
+ {
+ DEBUG ("D-Bus message queue size limit reached (%u bytes). "
+ "Ignore this message.",
+ MAX_QUEUE_SIZE);
+ goto unref;
+ }
+
+ priv->dbus_msg_queue = g_slist_prepend (priv->dbus_msg_queue, msg);
+ priv->dbus_msg_queue_size += len;
+
+ /* returns without unref the message */
+ return;
+ }
+
+ DEBUG ("delivering message from '%s' to '%s'",
+ dbus_message_get_sender (msg),
+ dbus_message_get_destination (msg));
+
+ /* XXX: what do do if this returns FALSE? */
+ dbus_connection_send (priv->dbus_conn, msg, &serial);
+
+unref:
+ dbus_message_unref (msg);
+}
+
+static guint32
+collect_le32 (char *str)
+{
+ unsigned char *bytes = (unsigned char *) str;
+
+ return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
+}
+
+static guint32
+collect_be32 (char *str)
+{
+ unsigned char *bytes = (unsigned char *) str;
+
+ return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 24) | bytes[3];
+}
+
+static void
+data_received_cb (GibberBytestreamIface *stream,
+ const gchar *from,
+ GString *data,
+ gpointer user_data)
+{
+ SalutTubeDBus *tube = SALUT_TUBE_DBUS (user_data);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (tube);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle sender;
+
+ sender = tp_handle_lookup (contact_repo, from, NULL, NULL);
+ if (sender == 0)
+ {
+ DEBUG ("unknown sender: %s", from);
+ return;
+ }
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ GString *buf = priv->reassembly_buffer;
+
+ g_assert (buf != NULL);
+
+ g_string_append_len (buf, data->str, data->len);
+ DEBUG ("Received %" G_GSIZE_FORMAT " bytes, so we now have %"
+ G_GSIZE_FORMAT " bytes in reassembly buffer", data->len, buf->len);
+
+ /* Each D-Bus message has a 16-byte fixed header, in which
+ *
+ * * byte 0 is 'l' (ell) or 'B' for endianness
+ * * bytes 4-7 are body length "n" in bytes in that endianness
+ * * bytes 12-15 are length "m" of param array in bytes in that
+ * endianness
+ *
+ * followed by m + n + ((8 - (m % 8)) % 8) bytes of other content.
+ */
+
+ while (buf->len >= 16)
+ {
+ guint32 body_length, params_length, m;
+
+ /* see if we have a whole message and have already calculated
+ * how many bytes it needs */
+
+ if (priv->reassembly_bytes_needed != 0)
+ {
+ if (buf->len >= priv->reassembly_bytes_needed)
+ {
+ DEBUG ("Received complete D-Bus message of size %"
+ G_GINT32_FORMAT, priv->reassembly_bytes_needed);
+ message_received (tube, sender, buf->str,
+ priv->reassembly_bytes_needed);
+ g_string_erase (buf, 0, priv->reassembly_bytes_needed);
+ priv->reassembly_bytes_needed = 0;
+ }
+ else
+ {
+ /* we'll have to wait for more data */
+ break;
+ }
+ }
+
+ if (buf->len < 16)
+ break;
+
+ /* work out how big the next message is going to be */
+
+ if (buf->str[0] == DBUS_BIG_ENDIAN)
+ {
+ body_length = collect_be32 (buf->str + 4);
+ m = collect_be32 (buf->str + 12);
+ }
+ else if (buf->str[0] == DBUS_LITTLE_ENDIAN)
+ {
+ body_length = collect_le32 (buf->str + 4);
+ m = collect_le32 (buf->str + 12);
+ }
+ else
+ {
+ DEBUG ("D-Bus message has unknown endianness byte 0x%x, "
+ "closing tube", (unsigned int) buf->str[0]);
+ salut_tube_iface_close (SALUT_TUBE_IFACE (tube), FALSE);
+ return;
+ }
+
+ /* pad to 8-byte boundary */
+ params_length = m + ((8 - (m % 8)) % 8);
+ g_assert (params_length % 8 == 0);
+ g_assert (params_length >= m);
+ g_assert (params_length < m + 8);
+
+ priv->reassembly_bytes_needed = params_length + body_length + 16;
+
+ /* n.b.: this looks as if it could be simplified to just the third
+ * test, but that would be wrong if the addition had overflowed, so
+ * don't do that. The first and second tests are sufficient to
+ * ensure no overflow on 32-bit platforms */
+ if (body_length > DBUS_MAXIMUM_MESSAGE_LENGTH ||
+ params_length > DBUS_MAXIMUM_ARRAY_LENGTH ||
+ priv->reassembly_bytes_needed > DBUS_MAXIMUM_MESSAGE_LENGTH)
+ {
+ DEBUG ("D-Bus message is too large to be valid, closing tube");
+ salut_tube_iface_close (SALUT_TUBE_IFACE (tube), FALSE);
+ return;
+ }
+
+ g_assert (priv->reassembly_bytes_needed != 0);
+ DEBUG ("We need %" G_GINT32_FORMAT " bytes for the next full "
+ "message", priv->reassembly_bytes_needed);
+ }
+ }
+ else
+ {
+ /* MUC bytestreams are message-boundary preserving, which is necessary,
+ * because we can't assume we started at the beginning */
+ g_assert (GIBBER_IS_BYTESTREAM_MUC (priv->bytestream));
+ message_received (tube, sender, data->str, data->len);
+ }
+}
+
+SalutTubeDBus *
+salut_tube_dbus_new (SalutConnection *conn,
+ SalutTubesChannel *tubes_channel,
+ TpHandle handle,
+ TpHandleType handle_type,
+ TpHandle self_handle,
+ GibberMucConnection *muc_connection,
+ TpHandle initiator,
+ const gchar *service,
+ GHashTable *parameters,
+ guint id)
+{
+ SalutTubeDBus *tube;
+ gchar *object_path;
+
+ object_path = g_strdup_printf ("%s/DBusTubeChannel_%u_%u",
+ conn->parent.object_path, handle, id);
+
+ tube = g_object_new (SALUT_TYPE_TUBE_DBUS,
+ "connection", conn,
+ "object-path", object_path,
+ "tubes-channel", tubes_channel,
+ "handle", handle,
+ "handle-type", handle_type,
+ "self-handle", self_handle,
+ "muc-connection", muc_connection,
+ "initiator-handle", initiator,
+ "service", service,
+ "parameters", parameters,
+ "id", id,
+ NULL);
+
+ return tube;
+}
+
+/*
+ * salut_tube_dbus_accept
+ *
+ * Implements salut_tube_iface_accept on SalutTubeIface
+ */
+static gboolean
+salut_tube_dbus_accept (SalutTubeIface *tube,
+ GError **error)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (tube);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+ GibberBytestreamState state;
+
+ g_assert (priv->bytestream != NULL);
+
+ g_object_get (priv->bytestream,
+ "state", &state,
+ NULL);
+
+ if (state != GIBBER_BYTESTREAM_STATE_LOCAL_PENDING)
+ return TRUE;
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ /* TODO: SI reply */
+ }
+ else
+ {
+ /* No SI so the bytestream is open */
+ DEBUG ("no SI, bytestream open");
+ g_object_set (priv->bytestream,
+ "state", GIBBER_BYTESTREAM_STATE_OPEN,
+ NULL);
+ }
+
+ return TRUE;
+}
+
+/*
+ * salut_tube_dbus_close
+ *
+ * Implements salut_tube_iface_close on SalutTubeIface
+ */
+static void
+salut_tube_dbus_close (SalutTubeIface *tube, gboolean closed_remotely)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (tube);
+
+ do_close (self);
+}
+
+/**
+ * salut_tube_dbus_add_bytestream
+ *
+ * Implements salut_tube_iface_add_bytestream on SalutTubeIface
+ */
+
+static void
+salut_tube_dbus_add_bytestream (SalutTubeIface *tube,
+ GibberBytestreamIface *bytestream)
+{
+ DEBUG ("D-Bus doesn't support extra bytestream");
+ gibber_bytestream_iface_close (bytestream, NULL);
+}
+
+gboolean
+salut_tube_dbus_add_name (SalutTubeDBus *self,
+ TpHandle handle,
+ const gchar *name)
+{
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+ GHashTable *added;
+ GArray *removed;
+
+ g_assert (priv->handle_type == TP_HANDLE_TYPE_ROOM);
+
+ if (g_hash_table_lookup (priv->dbus_names, GUINT_TO_POINTER (handle))
+ != NULL)
+ {
+ DEBUG ("contact %d has already announced his D-Bus name", handle);
+ return FALSE;
+ }
+
+ if (g_str_has_prefix (name, ":2."))
+ {
+ gchar *supposed_name;
+ const gchar *contact_name;
+
+ contact_name = tp_handle_inspect (contact_repo, handle);
+ supposed_name = generate_dbus_unique_name (contact_name);
+
+ if (tp_strdiff (name, supposed_name))
+ {
+ DEBUG ("contact %s announces %s as D-Bus name but it should be %s",
+ contact_name, name, supposed_name);
+ g_free (supposed_name);
+ return FALSE;
+ }
+ g_free (supposed_name);
+ }
+
+ g_hash_table_insert (priv->dbus_names, GUINT_TO_POINTER (handle),
+ g_strdup (name));
+ tp_handle_ref (contact_repo, handle);
+
+ /* Fire DBusNamesChanged (new API) */
+ added = g_hash_table_new (g_direct_hash, g_direct_equal);
+ removed = g_array_new (FALSE, FALSE, sizeof (TpHandle));
+
+ g_hash_table_insert (added, GUINT_TO_POINTER (handle), (gchar *) name);
+
+ tp_svc_channel_type_dbus_tube_emit_dbus_names_changed (self, added,
+ removed);
+
+ g_hash_table_unref (added);
+ g_array_unref (removed);
+
+ return TRUE;
+}
+
+gboolean
+salut_tube_dbus_remove_name (SalutTubeDBus *self,
+ TpHandle handle)
+{
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+ const gchar *name;
+ GHashTable *added;
+ GArray *removed;
+
+ g_assert (priv->handle_type == TP_HANDLE_TYPE_ROOM);
+
+ name = g_hash_table_lookup (priv->dbus_names, GUINT_TO_POINTER (handle));
+ if (name == NULL)
+ return FALSE;
+
+ g_hash_table_remove (priv->dbus_names, GUINT_TO_POINTER (handle));
+
+ /* Fire DBusNamesChanged (new API) */
+ added = g_hash_table_new (g_direct_hash, g_direct_equal);
+ removed = g_array_new (FALSE, FALSE, sizeof (TpHandle));
+
+ g_array_append_val (removed, handle);
+
+ tp_svc_channel_type_dbus_tube_emit_dbus_names_changed (self, added,
+ removed);
+
+ g_hash_table_unref (added);
+ g_array_unref (removed);
+ tp_handle_unref (contact_repo, handle);
+ return TRUE;
+}
+
+gboolean
+salut_tube_dbus_handle_in_names (SalutTubeDBus *self,
+ TpHandle handle)
+{
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ g_assert (priv->handle_type == TP_HANDLE_TYPE_ROOM);
+
+ return (g_hash_table_lookup (priv->dbus_names, GUINT_TO_POINTER (handle))
+ != NULL);
+}
+
+const gchar * const *
+salut_tube_dbus_channel_get_allowed_properties (void)
+{
+ return salut_tube_dbus_channel_allowed_properties;
+}
+
+/**
+ * salut_tube_dbus_close_async:
+ *
+ * Implements D-Bus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tube_dbus_close_async (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ salut_tube_dbus_close (SALUT_TUBE_IFACE (iface), FALSE);
+ tp_svc_channel_return_from_close (context);
+}
+
+/**
+ * salut_tube_dbus_get_channel_type
+ *
+ * Implements D-Bus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tube_dbus_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE);
+}
+
+/**
+ * salut_tube_dbus_get_handle
+ *
+ * Implements D-Bus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tube_dbus_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (iface);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ tp_svc_channel_return_from_get_handle (context, priv->handle_type,
+ priv->handle);
+}
+
+/**
+ * salut_tube_dbus_get_interfaces
+ *
+ * Implements D-Bus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tube_dbus_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubeDBus *self = SALUT_TUBE_DBUS (iface);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (self);
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ /* omit the Group interface */
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_tube_dbus_interfaces + 1);
+ }
+ else
+ {
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_tube_dbus_interfaces);
+ }
+}
+
+static gboolean
+salut_tube_dbus_check_access_control (SalutTubeDBus *self,
+ guint access_control,
+ GError **error)
+{
+ switch (access_control)
+ {
+ case TP_SOCKET_ACCESS_CONTROL_CREDENTIALS:
+ case TP_SOCKET_ACCESS_CONTROL_LOCALHOST:
+ break;
+
+ default:
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "%u socket access control is not supported", access_control);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * salut_tube_dbus_offer_async
+ *
+ * Implement D-Bus method Offer on interface
+ * org.freedesktop.Telepathy.Channel.Type.DBusTube
+ */
+static void
+salut_tube_dbus_offer_async (TpSvcChannelTypeDBusTube *self,
+ GHashTable *parameters,
+ guint access_control,
+ DBusGMethodInvocation *context)
+{
+ SalutTubeDBus *tube = SALUT_TUBE_DBUS (self);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (tube);
+ GError *error = NULL;
+
+ if (!salut_tube_dbus_check_access_control (tube, access_control, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ priv->access_control = access_control;
+
+ g_object_set (self, "parameters", parameters, NULL);
+
+ if (salut_tube_dbus_offer (tube, &error))
+ {
+ tp_svc_channel_type_dbus_tube_return_from_offer (context,
+ priv->dbus_srv_addr);
+ }
+ else
+ {
+ g_assert (error != NULL);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+/**
+ * salut_tube_dbus_accept_async
+ *
+ * Implements D-Bus method Accept on interface
+ * org.freedesktop.Telepathy.Channel.Type.DBusTube
+ */
+static void
+salut_tube_dbus_accept_async (TpSvcChannelTypeDBusTube *self,
+ guint access_control,
+ DBusGMethodInvocation *context)
+{
+ SalutTubeDBus *tube = SALUT_TUBE_DBUS (self);
+ SalutTubeDBusPrivate *priv = SALUT_TUBE_DBUS_GET_PRIVATE (tube);
+ GError *error = NULL;
+
+ if (!salut_tube_dbus_check_access_control (tube, access_control, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ if (salut_tube_dbus_accept (SALUT_TUBE_IFACE (tube), &error))
+ {
+ tp_svc_channel_type_dbus_tube_return_from_accept (context,
+ priv->dbus_srv_addr);
+ }
+ else
+ {
+ g_assert (error != NULL);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+static void
+channel_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface;
+
+#define IMPLEMENT(x, suffix) tp_svc_channel_implement_##x (\
+ klass, salut_tube_dbus_##x##suffix)
+ IMPLEMENT(close,_async);
+ IMPLEMENT(get_channel_type,);
+ IMPLEMENT(get_handle,);
+ IMPLEMENT(get_interfaces,);
+#undef IMPLEMENT
+}
+
+static void
+tube_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutTubeIfaceClass *klass = (SalutTubeIfaceClass *) g_iface;
+
+ klass->accept = salut_tube_dbus_accept;
+ klass->offer_needed = NULL;
+ klass->close = salut_tube_dbus_close;
+ klass->add_bytestream = salut_tube_dbus_add_bytestream;
+}
+
+static void
+dbustube_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelTypeDBusTubeClass *klass =
+ (TpSvcChannelTypeDBusTubeClass *) g_iface;
+
+#define IMPLEMENT(x, suffix) tp_svc_channel_type_dbus_tube_implement_##x (\
+ klass, salut_tube_dbus_##x##suffix)
+ IMPLEMENT(offer,_async);
+ IMPLEMENT(accept,_async);
+#undef IMPLEMENT
+}
diff --git a/salut/src/tube-dbus.h b/salut/src/tube-dbus.h
new file mode 100644
index 000000000..e8a64eb19
--- /dev/null
+++ b/salut/src/tube-dbus.h
@@ -0,0 +1,88 @@
+/*
+ * tube-dbus.h - Header for SalutTubeDBus
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_TUBE_DBUS_H__
+#define __SALUT_TUBE_DBUS_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "connection.h"
+#include "tubes-channel.h"
+#include <gibber/gibber-muc-connection.h>
+#include <gibber/gibber-bytestream-iface.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutTubeDBus SalutTubeDBus;
+typedef struct _SalutTubeDBusClass SalutTubeDBusClass;
+
+struct _SalutTubeDBusClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _SalutTubeDBus {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType salut_tube_dbus_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_TUBE_DBUS \
+ (salut_tube_dbus_get_type ())
+#define SALUT_TUBE_DBUS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_TUBE_DBUS, SalutTubeDBus))
+#define SALUT_TUBE_DBUS_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_TUBE_DBUS,\
+ SalutTubeDBusClass))
+#define SALUT_IS_TUBE_DBUS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_TUBE_DBUS))
+#define SALUT_IS_TUBE_DBUS_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_TUBE_DBUS))
+#define SALUT_TUBE_DBUS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_TUBE_DBUS,\
+ SalutTubeDBusClass))
+
+SalutTubeDBus *
+salut_tube_dbus_new (SalutConnection *conn, SalutTubesChannel *tubes_channel,
+ TpHandle handle, TpHandleType handle_type, TpHandle self_handle,
+ GibberMucConnection *muc_connection, TpHandle initiator,
+ const gchar *service, GHashTable *parameters, guint id);
+
+gboolean salut_tube_dbus_add_name (SalutTubeDBus *self, TpHandle handle,
+ const gchar *name);
+
+gboolean salut_tube_dbus_remove_name (SalutTubeDBus *self, TpHandle handle);
+
+gboolean salut_tube_dbus_handle_in_names (SalutTubeDBus *self,
+ TpHandle handle);
+
+gboolean salut_tube_dbus_offer (SalutTubeDBus *self, GError **error);
+
+const gchar * const * salut_tube_dbus_channel_get_allowed_properties (void);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_TUBE_DBUS_H__ */
diff --git a/salut/src/tube-iface.c b/salut/src/tube-iface.c
new file mode 100644
index 000000000..18d39877b
--- /dev/null
+++ b/salut/src/tube-iface.c
@@ -0,0 +1,224 @@
+/*
+ * tube-iface.c - Source for SalutTube interface
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tube-iface.h"
+
+#include <telepathy-glib/gtypes.h>
+
+#include "connection.h"
+#include "tubes-channel.h"
+
+#include <glib.h>
+
+gboolean
+salut_tube_iface_accept (SalutTubeIface *self,
+ GError **error)
+{
+ gboolean (*virtual_method)(SalutTubeIface *, GError **) =
+ SALUT_TUBE_IFACE_GET_CLASS (self)->accept;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, error);
+}
+
+void
+salut_tube_iface_accepted (SalutTubeIface *self)
+{
+ void (*virtual_method)(SalutTubeIface *) =
+ SALUT_TUBE_IFACE_GET_CLASS (self)->accepted;
+ if (virtual_method != NULL)
+ virtual_method (self);
+ /* else nothing to do */
+}
+
+gboolean
+salut_tube_iface_offer_needed (SalutTubeIface *self)
+{
+ gboolean (*virtual_method)(SalutTubeIface *) =
+ SALUT_TUBE_IFACE_GET_CLASS (self)->offer_needed;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self);
+}
+
+int
+salut_tube_iface_listen (SalutTubeIface *self)
+{
+ gboolean (*virtual_method)(SalutTubeIface *) =
+ SALUT_TUBE_IFACE_GET_CLASS (self)->listen;
+ g_assert (virtual_method != NULL);
+ return virtual_method (self);
+}
+
+void
+salut_tube_iface_close (SalutTubeIface *self, gboolean closed_remotely)
+{
+ void (*virtual_method)(SalutTubeIface *, gboolean) =
+ SALUT_TUBE_IFACE_GET_CLASS (self)->close;
+ g_assert (virtual_method != NULL);
+ virtual_method (self, closed_remotely);
+}
+
+void
+salut_tube_iface_add_bytestream (SalutTubeIface *self,
+ GibberBytestreamIface *bytestream)
+{
+ void (*virtual_method)(SalutTubeIface *, GibberBytestreamIface *) =
+ SALUT_TUBE_IFACE_GET_CLASS (self)->add_bytestream;
+ g_assert (virtual_method != NULL);
+ virtual_method (self, bytestream);
+}
+
+static void
+salut_tube_iface_base_init (gpointer klass)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ GParamSpec *param_spec;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "Salut connection object that owns this D-Bus tube object.",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_object (
+ "tubes-channel",
+ "SalutTubesChannel object",
+ "Salut tubes object that implements the old interface.",
+ SALUT_TYPE_TUBES_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "handle",
+ "Handle",
+ "The TpHandle associated with the tubes channel that"
+ "owns this D-Bus tube object.",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "handle-type",
+ "Handle type",
+ "The TpHandleType of the handle associated with the tubes channel"
+ "that owns this D-Bus tube object.",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "self-handle",
+ "Self handle",
+ "The handle to use for ourself. This can be different from the "
+ "connection's self handle if our handle is a room handle.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "id",
+ "id",
+ "The unique identifier of this tube",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "type",
+ "Tube type",
+ "The TpTubeType this D-Bus tube object.",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "initiator-handle",
+ "Initiator handle",
+ "The TpHandle of the initiator of tube object.",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_string (
+ "service",
+ "service name",
+ "the service associated with this D-BUS tube object.",
+ "",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_boxed (
+ "parameters",
+ "parameters GHashTable",
+ "GHashTable containing parameters of this DBUS tube object.",
+ TP_HASH_TYPE_STRING_VARIANT_MAP,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "state",
+ "Tube state",
+ "The TpTubeChannelState of this DBUS tube object",
+ 0, G_MAXUINT32, TP_TUBE_STATE_REMOTE_PENDING,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (klass, param_spec);
+
+ initialized = TRUE;
+ }
+}
+
+GType
+salut_tube_iface_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof (SalutTubeIfaceClass),
+ salut_tube_iface_base_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE, "SalutTubeIface",
+ &info, 0);
+ }
+
+ return type;
+}
diff --git a/salut/src/tube-iface.h b/salut/src/tube-iface.h
new file mode 100644
index 000000000..189c97e6c
--- /dev/null
+++ b/salut/src/tube-iface.h
@@ -0,0 +1,75 @@
+/*
+ * tube-iface.h - Header for SalutTube interface
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_TUBE_IFACE_H__
+#define __SALUT_TUBE_IFACE_H__
+
+#include <glib-object.h>
+
+#include <gibber/gibber-bytestream-iface.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutTubeIface SalutTubeIface;
+typedef struct _SalutTubeIfaceClass SalutTubeIfaceClass;
+
+struct _SalutTubeIfaceClass {
+ GTypeInterface parent;
+
+ gboolean (*accept) (SalutTubeIface *tube, GError **error);
+ void (*accepted) (SalutTubeIface *tube);
+ gboolean (*offer_needed) (SalutTubeIface *tube);
+ int (*listen) (SalutTubeIface *tube);
+ void (*close) (SalutTubeIface *tube, gboolean local);
+ void (*add_bytestream) (SalutTubeIface *tube,
+ GibberBytestreamIface *bytestream);
+};
+
+GType salut_tube_iface_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_TUBE_IFACE \
+ (salut_tube_iface_get_type ())
+#define SALUT_TUBE_IFACE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_TUBE_IFACE, SalutTubeIface))
+#define SALUT_IS_TUBE_IFACE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_TUBE_IFACE))
+#define SALUT_TUBE_IFACE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SALUT_TYPE_TUBE_IFACE,\
+ SalutTubeIfaceClass))
+
+/* return TRUE if the <iq> to offer the tube has never been sent */
+gboolean salut_tube_iface_offer_needed (SalutTubeIface *tube);
+
+int salut_tube_iface_listen (SalutTubeIface *tube);
+
+/* accept the tube offered by the contact */
+gboolean salut_tube_iface_accept (SalutTubeIface *tube, GError **error);
+
+/* the contact accepted our tube offer */
+void salut_tube_iface_accepted (SalutTubeIface *tube);
+
+void salut_tube_iface_close (SalutTubeIface *tube, gboolean closed_remotely);
+
+void salut_tube_iface_add_bytestream (SalutTubeIface *tube,
+ GibberBytestreamIface *bytestream);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_TUBE_IFACE_H__ */
diff --git a/salut/src/tube-stream.c b/salut/src/tube-stream.c
new file mode 100644
index 000000000..64f561370
--- /dev/null
+++ b/salut/src/tube-stream.c
@@ -0,0 +1,2511 @@
+/*
+ * tube-stream.c - Source for SalutTubeStream
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tube-stream.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <netdb.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <telepathy-glib/gtypes.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-stanza.h>
+
+#include <gibber/gibber-bytestream-direct.h>
+#include <gibber/gibber-bytestream-iface.h>
+#include <gibber/gibber-bytestream-oob.h>
+#include <gibber/gibber-fd-transport.h>
+#include <gibber/gibber-listener.h>
+#include <gibber/gibber-tcp-transport.h>
+#include <gibber/gibber-transport.h>
+#include <gibber/gibber-unix-transport.h>
+
+#define DEBUG_FLAG DEBUG_TUBES
+
+#include "debug.h"
+#include "signals-marshal.h"
+#include "connection.h"
+#include "tube-iface.h"
+#include "si-bytestream-manager.h"
+#include "contact-manager.h"
+#include "tubes-channel.h"
+
+static void tube_iface_init (gpointer g_iface, gpointer iface_data);
+static void channel_iface_init (gpointer g_iface, gpointer iface_data);
+static void streamtube_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SalutTubeStream, salut_tube_stream, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_TUBE_IFACE, tube_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAM_TUBE,
+ streamtube_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_TUBE,
+ NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));
+
+static const gchar *salut_tube_stream_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ /* If more interfaces are added, either keep Tube as the first, or change
+ * the implementations of salut_tube_stream_get_interfaces () and
+ * salut_tube_stream_get_property () too */
+ TP_IFACE_CHANNEL_INTERFACE_TUBE,
+ NULL
+};
+
+static const gchar * const salut_tube_stream_channel_allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ TP_IFACE_CHANNEL ".TargetID",
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service",
+ NULL
+};
+
+/* Linux glibc bits/socket.h suggests that struct sockaddr_storage is
+ * not guaranteed to be big enough for AF_UNIX addresses */
+typedef union
+{
+ /* we'd call this unix, but gcc predefines that. Thanks, gcc */
+ struct sockaddr_un un;
+ struct sockaddr_in ipv4;
+ struct sockaddr_in6 ipv6;
+} SockAddr;
+
+/* signals */
+enum
+{
+ OPENED,
+ NEW_CONNECTION,
+ CLOSED,
+ OFFERED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_TUBES_CHANNEL,
+ PROP_INTERFACES,
+ PROP_HANDLE,
+ PROP_HANDLE_TYPE,
+ PROP_SELF_HANDLE,
+ PROP_ID,
+ PROP_TYPE,
+ PROP_INITIATOR_HANDLE,
+ PROP_SERVICE,
+ PROP_PARAMETERS,
+ PROP_STATE,
+ PROP_OFFERED,
+ PROP_ADDRESS_TYPE,
+ PROP_ADDRESS,
+ PROP_ACCESS_CONTROL,
+ PROP_ACCESS_CONTROL_PARAM,
+ PROP_PORT,
+ PROP_IQ_REQ,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_OBJECT_PATH,
+ PROP_CHANNEL_TYPE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_ID,
+ PROP_SUPPORTED_SOCKET_TYPES,
+ LAST_PROPERTY
+};
+
+typedef struct _SalutTubeStreamPrivate SalutTubeStreamPrivate;
+struct _SalutTubeStreamPrivate
+{
+ SalutConnection *conn;
+ SalutTubesChannel *tubes_channel;
+ TpHandle handle;
+ TpHandleType handle_type;
+ TpHandle self_handle;
+ guint id;
+ guint port;
+ WockyStanza *iq_req;
+ gchar *object_path;
+
+ /* Bytestreams for MUC tubes (using stream initiation) or 1-1 tubes (using
+ * direct TCP connections). One tube can have several bytestreams. The
+ * mapping between the tube bytestream and the transport to the local
+ * application is stored in the transport_to_bytestream and
+ * bytestream_to_transport fields. This is used both on initiator-side and
+ * on recipient-side. */
+
+ /* (GibberBytestreamIface *) -> (GibberTransport *)
+ *
+ * The (b->t) is inserted as soon as they are created. On initiator side,
+ * we receive an incoming bytestream, create a transport and insert (b->t).
+ * On recipient side, we receive an incoming transport, create a bytestream
+ * and insert (b->t).
+ */
+ GHashTable *bytestream_to_transport;
+
+ /* (GibberTransport *) -> (GibberBytestreamIface *)
+ *
+ * The (t->b) is inserted when the bytestream is open.
+ */
+ GHashTable *transport_to_bytestream;
+
+ /* (GibberTransport *) -> guint */
+ GHashTable *transport_to_id;
+ guint last_connection_id;
+
+ TpHandle initiator;
+ gchar *service;
+ GHashTable *parameters;
+ TpTubeChannelState state;
+ /* whether the tube is already offered at construct-time (with the
+ * Channel.Type.Tubes interface) */
+ gboolean offered;
+
+ TpSocketAddressType address_type;
+ GValue *address;
+ TpSocketAccessControl access_control;
+ GValue *access_control_param;
+
+ /* listen for connections from local applications */
+ GibberListener *local_listener;
+
+ /* listen for connections from the remote CM */
+ GibberListener *contact_listener;
+
+ gboolean closed;
+
+ gboolean offer_needed;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_TUBE_STREAM_GET_PRIVATE(obj) \
+ ((SalutTubeStreamPrivate *) ((SalutTubeStream *) obj)->priv)
+
+static void data_received_cb (GibberBytestreamIface *ibb, TpHandle sender,
+ GString *data, gpointer user_data);
+
+static void salut_tube_stream_add_bytestream (SalutTubeIface *tube,
+ GibberBytestreamIface *bytestream);
+
+static void
+generate_ascii_string (guint len,
+ gchar *buf)
+{
+ const gchar *chars =
+ "0123456789"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "_-";
+ guint i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = chars[g_random_int_range (0, 64)];
+}
+
+static void
+transport_handler (GibberTransport *transport,
+ GibberBuffer *data,
+ gpointer user_data)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (user_data);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GibberBytestreamIface *bytestream;
+
+ bytestream = g_hash_table_lookup (priv->transport_to_bytestream, transport);
+ if (bytestream == NULL)
+ {
+ DEBUG ("no open bytestream associated with this transport");
+ return;
+ }
+
+ DEBUG ("read %" G_GSIZE_FORMAT " bytes from socket", data->length);
+
+ gibber_bytestream_iface_send (bytestream, data->length,
+ (const gchar *) data->data);
+}
+
+static void
+fire_connection_closed (SalutTubeStream *self,
+ GibberTransport *transport,
+ const gchar *error,
+ const gchar *debug_msg)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ guint connection_id;
+
+ connection_id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->transport_to_id,
+ transport));
+ if (connection_id == 0)
+ {
+ DEBUG ("ConnectionClosed has already been fired for this connection");
+ return;
+ }
+
+ /* remove the ID so we are sure we won't fire ConnectionClosed twice for the
+ * same connection. */
+ g_hash_table_remove (priv->transport_to_id, transport);
+
+ tp_svc_channel_type_stream_tube_emit_connection_closed (self,
+ connection_id, error, debug_msg);
+}
+
+static void
+transport_disconnected_cb (GibberTransport *transport,
+ SalutTubeStream *self)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GibberBytestreamIface *bytestream;
+
+ fire_connection_closed (self, transport, TP_ERROR_STR_CANCELLED,
+ "local socket has been disconnected");
+
+ bytestream = g_hash_table_lookup (priv->transport_to_bytestream, transport);
+ if (bytestream == NULL)
+ return;
+
+ DEBUG ("transport disconnected. close the extra bytestream");
+
+ gibber_bytestream_iface_close (bytestream, NULL);
+}
+
+static void
+remove_transport (SalutTubeStream *self,
+ GibberBytestreamIface *bytestream,
+ GibberTransport *transport)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ DEBUG ("disconnect and remove transport");
+ g_signal_handlers_disconnect_matched (transport, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+
+ gibber_transport_disconnect (transport);
+
+ fire_connection_closed (self, transport, TP_ERROR_STR_CONNECTION_LOST,
+ "bytestream has been broken");
+
+ /* the transport may not be in transport_to_bytestream if the bytestream was
+ * not fully open */
+ g_hash_table_remove (priv->transport_to_bytestream, transport);
+
+ g_hash_table_remove (priv->bytestream_to_transport, bytestream);
+ g_hash_table_remove (priv->transport_to_id, transport);
+}
+
+static void
+transport_buffer_empty_cb (GibberTransport *transport,
+ SalutTubeStream *self)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GibberBytestreamIface *bytestream;
+ GibberBytestreamState state;
+
+ bytestream = g_hash_table_lookup (priv->transport_to_bytestream, transport);
+ g_assert (bytestream != NULL);
+ g_object_get (bytestream, "state", &state, NULL);
+
+ if (state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ {
+ DEBUG ("buffer is now empty. Transport can be removed");
+ remove_transport (self, bytestream, transport);
+ return;
+ }
+
+ /* Buffer is empty so we can unblock the buffer if it was blocked */
+ DEBUG ("tube buffer is empty. Unblock the bytestream");
+ gibber_bytestream_iface_block_reading (bytestream, FALSE);
+}
+
+static void
+add_transport (SalutTubeStream *self,
+ GibberTransport *transport,
+ GibberBytestreamIface *bytestream)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ gibber_transport_set_handler (transport, transport_handler, self);
+
+ g_hash_table_insert (priv->transport_to_bytestream,
+ g_object_ref (transport), g_object_ref (bytestream));
+
+ g_signal_connect (transport, "disconnected",
+ G_CALLBACK (transport_disconnected_cb), self);
+ g_signal_connect (transport, "buffer-empty",
+ G_CALLBACK (transport_buffer_empty_cb), self);
+
+ /* We can transfer transport's data; unblock it. */
+ gibber_transport_block_receiving (transport, FALSE);
+}
+
+static void
+bytestream_write_blocked_cb (GibberBytestreamIface *bytestream,
+ gboolean blocked,
+ SalutTubeStream *self)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GibberTransport *transport;
+
+ transport = g_hash_table_lookup (priv->bytestream_to_transport,
+ bytestream);
+ g_assert (transport != NULL);
+
+ if (blocked)
+ {
+ DEBUG ("bytestream blocked, stop to read data from the tube socket");
+ }
+ else
+ {
+ DEBUG ("bytestream unblocked, restart to read data from the tube socket");
+ }
+
+ gibber_transport_block_receiving (transport, blocked);
+}
+
+static void
+extra_bytestream_state_changed_cb (GibberBytestreamIface *bytestream,
+ GibberBytestreamState state,
+ gpointer user_data)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (user_data);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ if (state == GIBBER_BYTESTREAM_STATE_OPEN)
+ {
+ GibberTransport *transport;
+
+ DEBUG ("extra bytestream open");
+
+ g_signal_connect (bytestream, "data-received",
+ G_CALLBACK (data_received_cb), self);
+ g_signal_connect (bytestream, "write-blocked",
+ G_CALLBACK (bytestream_write_blocked_cb), self);
+
+ transport = g_hash_table_lookup (priv->bytestream_to_transport,
+ bytestream);
+ g_assert (transport != NULL);
+
+ add_transport (self, transport, bytestream);
+ }
+ else if (state == GIBBER_BYTESTREAM_STATE_CLOSED)
+ {
+ GibberTransport *transport;
+
+ DEBUG ("extra bytestream closed");
+ transport = g_hash_table_lookup (priv->bytestream_to_transport,
+ bytestream);
+ if (transport != NULL)
+ {
+ if (gibber_transport_buffer_is_empty (transport))
+ {
+ DEBUG ("Buffer is empty, we can remove the transport");
+ remove_transport (self, bytestream, transport);
+ }
+ else
+ {
+ DEBUG ("Wait buffer is empty before disconnect the transport");
+ }
+ }
+ }
+}
+
+struct _extra_bytestream_negotiate_cb_data
+{
+ SalutTubeStream *self;
+ /* transport from the local application */
+ GibberTransport *transport;
+};
+
+static void
+extra_bytestream_negotiate_cb (GibberBytestreamIface *bytestream,
+ gpointer user_data)
+{
+ struct _extra_bytestream_negotiate_cb_data *data =
+ (struct _extra_bytestream_negotiate_cb_data *) user_data;
+ SalutTubeStream *self = data->self;
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ if (bytestream == NULL)
+ {
+ DEBUG ("initiator refused new bytestream");
+
+ fire_connection_closed (self, data->transport,
+ TP_ERROR_STR_CONNECTION_REFUSED, "connection has been refused");
+
+ g_object_unref (data->transport);
+ g_slice_free (struct _extra_bytestream_negotiate_cb_data, data);
+ return;
+ }
+
+ DEBUG ("extra bytestream accepted");
+
+ g_hash_table_insert (priv->bytestream_to_transport, g_object_ref (bytestream),
+ data->transport);
+
+ g_signal_connect (bytestream, "state-changed",
+ G_CALLBACK (extra_bytestream_state_changed_cb), self);
+
+ g_slice_free (struct _extra_bytestream_negotiate_cb_data, data);
+}
+
+/* XXX we should move that in some kind of bytestream factory */
+static gchar *
+generate_stream_id (SalutTubeStream *self)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ gchar *stream_id;
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ stream_id = g_strdup_printf ("%lu-%u", (unsigned long) time (NULL),
+ g_random_int ());
+ }
+ else
+ {
+ /* GibberMucConnection's stream-id is a guint8 */
+ stream_id = g_strdup_printf ("%u", g_random_int_range (1, G_MAXUINT8));
+ }
+
+ return stream_id;
+}
+
+static gboolean
+start_stream_initiation (SalutTubeStream *self,
+ GibberTransport *transport,
+ GError **error)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ WockyNode *node, *si_node;
+ WockyStanza *msg;
+ WockyNode *msg_node;
+ TpHandleRepoIface *contact_repo;
+ const gchar *jid;
+ gchar *stream_id, *id_str;
+ gboolean result;
+ struct _extra_bytestream_negotiate_cb_data *data;
+ SalutContact *contact;
+ SalutContactManager *contact_mgr;
+ SalutSiBytestreamManager *si_bytestream_mgr;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_ROOM);
+
+ contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ jid = tp_handle_inspect (contact_repo, priv->initiator);
+
+ stream_id = generate_stream_id (self);
+
+ msg = salut_si_bytestream_manager_make_stream_init_iq (priv->conn->name, jid,
+ stream_id, WOCKY_TELEPATHY_NS_TUBES);
+ msg_node = wocky_stanza_get_top_node (msg);
+
+ si_node = wocky_node_get_child_ns (msg_node, "si", WOCKY_XMPP_NS_SI);
+ g_assert (si_node != NULL);
+
+ id_str = g_strdup_printf ("%u", priv->id);
+
+ g_assert (priv->handle_type == TP_HANDLE_TYPE_ROOM);
+
+ /* FIXME: this needs standardizing */
+ node = wocky_node_add_child_ns (si_node, "muc-stream",
+ WOCKY_TELEPATHY_NS_TUBES);
+ wocky_node_set_attribute (node, "muc", tp_handle_inspect (
+ room_repo, priv->handle));
+
+ wocky_node_set_attribute (node, "tube", id_str);
+
+ data = g_slice_new (struct _extra_bytestream_negotiate_cb_data);
+ data->self = self;
+ data->transport = g_object_ref (transport);
+
+ g_object_get (priv->conn,
+ "si-bytestream-manager", &si_bytestream_mgr,
+ "contact-manager", &contact_mgr,
+ NULL);
+ g_assert (si_bytestream_mgr != NULL);
+ g_assert (contact_mgr != NULL);
+
+ contact = salut_contact_manager_get_contact (contact_mgr, priv->initiator);
+ if (contact == NULL)
+ {
+ result = FALSE;
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ "can't find contact with handle %d", priv->initiator);
+ g_object_unref (transport);
+ g_slice_free (struct _extra_bytestream_negotiate_cb_data, data);
+ }
+ else
+ {
+ wocky_stanza_set_to_contact (msg, WOCKY_CONTACT (contact));
+
+ result = salut_si_bytestream_manager_negotiate_stream (
+ si_bytestream_mgr,
+ contact,
+ msg,
+ stream_id,
+ extra_bytestream_negotiate_cb,
+ data,
+ error);
+
+ /* FIXME: data and one ref on data->transport are leaked if the tube is
+ * closed before we got the SI reply. */
+
+ g_object_unref (contact);
+ }
+
+ if (!result)
+ {
+ g_object_unref (data->transport);
+ g_slice_free (struct _extra_bytestream_negotiate_cb_data, data);
+ }
+
+ g_object_unref (si_bytestream_mgr);
+ g_object_unref (contact_mgr);
+ g_object_unref (msg);
+ g_free (stream_id);
+ g_free (id_str);
+
+ return result;
+}
+
+/* start a new stream in a tube from the recipient side */
+static gboolean
+start_stream_direct (SalutTubeStream *self,
+ GibberTransport *transport,
+ GError **error)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ SalutContact *contact;
+ SalutContactManager *contact_mgr;
+ GibberBytestreamIface *bytestream;
+
+ g_assert (priv->handle_type == TP_HANDLE_TYPE_CONTACT);
+
+ g_object_get (priv->conn,
+ "contact-manager", &contact_mgr,
+ NULL);
+ g_assert (contact_mgr != NULL);
+
+ contact = salut_contact_manager_get_contact (contact_mgr, priv->initiator);
+ if (contact == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ "can't find contact with handle %d", priv->initiator);
+
+ g_object_unref (contact_mgr);
+
+ return FALSE;
+ }
+
+ bytestream = g_object_new (GIBBER_TYPE_BYTESTREAM_DIRECT,
+ "addresses", salut_contact_get_addresses (contact),
+ "state", GIBBER_BYTESTREAM_STATE_LOCAL_PENDING,
+ "peer-id", contact->name,
+ "port", priv->port,
+ NULL);
+
+ g_assert (bytestream != NULL);
+
+ g_hash_table_insert (priv->bytestream_to_transport,
+ bytestream, g_object_ref (transport));
+
+ g_signal_connect (bytestream, "state-changed",
+ G_CALLBACK (extra_bytestream_state_changed_cb), self);
+
+ /* Let's start the initiation of the stream */
+ if (!gibber_bytestream_iface_initiate (bytestream))
+ {
+ /* Initiation failed. */
+ gibber_bytestream_iface_close (bytestream, NULL);
+
+ g_object_unref (contact);
+ g_object_unref (contact_mgr);
+
+ return FALSE;
+ }
+
+ g_object_unref (contact);
+ g_object_unref (contact_mgr);
+
+ return TRUE;
+}
+
+static guint
+generate_connection_id (SalutTubeStream *self,
+ GibberTransport *transport)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ priv->last_connection_id++;
+
+ g_hash_table_insert (priv->transport_to_id, transport,
+ GUINT_TO_POINTER (priv->last_connection_id));
+
+ return priv->last_connection_id;
+}
+
+static void
+fire_new_local_connection (SalutTubeStream *self,
+ GibberTransport *transport)
+{
+ guint connection_id;
+
+ connection_id = generate_connection_id (self, transport);
+
+ tp_svc_channel_type_stream_tube_emit_new_local_connection (self,
+ connection_id);
+}
+
+/* callback for listening connections from the local application */
+static void
+local_new_connection_cb (GibberListener *listener,
+ GibberTransport *transport,
+ struct sockaddr_storage *addr,
+ guint size,
+ gpointer user_data)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (user_data);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ /* Block the transport while there is no open bytestream to transfer
+ * its data. */
+ gibber_transport_block_receiving (transport, TRUE);
+
+ /* Streams in MUC tubes are established with stream initiation (XEP-0095).
+ * We use SalutSiBytestreamManager.
+ *
+ * Streams in P2P tubes are established directly with a TCP connection. We
+ * use SalutDirectBytestreamManager.
+ */
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ if (!start_stream_direct (self, transport, NULL))
+ {
+ DEBUG ("closing new client connection");
+ return;
+ }
+ }
+ else
+ {
+ if (!start_stream_initiation (self, transport, NULL))
+ {
+ DEBUG ("closing new client connection");
+ return;
+ }
+ }
+
+ fire_new_local_connection (self, transport);
+}
+
+static void
+fire_new_remote_connection (SalutTubeStream *self,
+ GibberTransport *transport,
+ TpHandle contact)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GValue access_control_param = {0,};
+ guint connection_id;
+
+ g_assert (priv->access_control == TP_SOCKET_ACCESS_CONTROL_LOCALHOST);
+
+ /* set a dummy value */
+ g_value_init (&access_control_param, G_TYPE_INT);
+ g_value_set_int (&access_control_param, 0);
+
+ connection_id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->transport_to_id,
+ transport));
+ g_assert (connection_id != 0);
+
+ tp_svc_channel_type_stream_tube_emit_new_remote_connection (self,
+ contact, &access_control_param, connection_id);
+ g_value_unset (&access_control_param);
+}
+
+static GibberTransport *
+new_connection_to_socket (SalutTubeStream *self,
+ GibberBytestreamIface *bytestream)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GibberTransport *transport;
+
+ g_assert (priv->initiator == priv->self_handle);
+
+ if (priv->address_type == TP_SOCKET_ADDRESS_TYPE_UNIX)
+ {
+ GArray *array;
+ array = g_value_get_boxed (priv->address);
+ DEBUG ("Will try to connect to socket: %s", (const gchar *) array->data);
+
+ transport = GIBBER_TRANSPORT (gibber_unix_transport_new ());
+ gibber_unix_transport_connect (GIBBER_UNIX_TRANSPORT (transport),
+ array->data, NULL);
+ }
+ else if (priv->address_type == TP_SOCKET_ADDRESS_TYPE_IPV4 ||
+ priv->address_type == TP_SOCKET_ADDRESS_TYPE_IPV6)
+ {
+ gchar *ip;
+ gchar *port_str;
+ guint port;
+
+ dbus_g_type_struct_get (priv->address,
+ 0, &ip,
+ 1, &port,
+ G_MAXUINT);
+ port_str = g_strdup_printf ("%d", port);
+
+ transport = GIBBER_TRANSPORT (gibber_tcp_transport_new ());
+ gibber_tcp_transport_connect (GIBBER_TCP_TRANSPORT (transport), ip,
+ port_str);
+
+ g_free (ip);
+ g_free (port_str);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ /* Block the transport while there is no open bytestream to transfer
+ * its data. */
+ gibber_transport_block_receiving (transport, TRUE);
+
+ generate_connection_id (self, transport);
+
+ g_hash_table_insert (priv->bytestream_to_transport, g_object_ref (bytestream),
+ g_object_ref (transport));
+
+ g_signal_connect (bytestream, "state-changed",
+ G_CALLBACK (extra_bytestream_state_changed_cb), self);
+
+ g_object_unref (transport);
+ return transport;
+}
+
+static gboolean
+tube_stream_open (SalutTubeStream *self,
+ GError **error)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ DEBUG ("called");
+
+ if (priv->initiator == priv->self_handle)
+ /* Nothing to do if we are the initiator of this tube.
+ * We'll connect to the socket each time request a new bytestream. */
+ return TRUE;
+
+ /* We didn't create this tube so it doesn't have
+ * a socket associated with it. Let's create one */
+ g_assert (priv->address == NULL);
+ g_assert (priv->local_listener == NULL);
+ priv->local_listener = gibber_listener_new ();
+
+ g_signal_connect (priv->local_listener, "new-connection",
+ G_CALLBACK (local_new_connection_cb), self);
+
+ if (priv->address_type == TP_SOCKET_ADDRESS_TYPE_UNIX)
+ {
+ GArray *array;
+ gchar suffix[8];
+ gchar *path;
+ int ret;
+
+ generate_ascii_string (8, suffix);
+ path = g_strdup_printf ("/tmp/stream-%.8s", suffix);
+
+ DEBUG ("create socket: %s", path);
+
+ array = g_array_sized_new (TRUE, FALSE, sizeof (gchar), strlen (path));
+ g_array_insert_vals (array, 0, path, strlen (path));
+
+ priv->address = tp_g_value_slice_new (DBUS_TYPE_G_UCHAR_ARRAY);
+ g_value_set_boxed (priv->address, array);
+
+ g_array_unref (array);
+
+ ret = gibber_listener_listen_socket (priv->local_listener, path, FALSE,
+ error);
+ if (ret != TRUE)
+ {
+ g_assert (error != NULL && *error != NULL);
+ DEBUG ("Error listening on socket %s: %s", path, (*error)->message);
+ g_free (path);
+ return FALSE;
+ }
+
+ if (priv->access_control == TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
+ {
+ /* Everyone can use the socket */
+ chmod (path, 0777);
+ }
+
+ g_free (path);
+ }
+ else if (priv->address_type == TP_SOCKET_ADDRESS_TYPE_IPV4)
+ {
+ int ret;
+
+ ret = gibber_listener_listen_tcp_loopback_af (priv->local_listener, 0,
+ GIBBER_AF_IPV4, error);
+ if (!ret)
+ {
+ g_assert (error != NULL && *error != NULL);
+ DEBUG ("Error listening on socket: %s", (*error)->message);
+ return FALSE;
+ }
+
+ priv->address = tp_g_value_slice_new (TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4);
+ g_value_take_boxed (priv->address,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4));
+
+ dbus_g_type_struct_set (priv->address,
+ 0, "127.0.0.1",
+ 1, gibber_listener_get_port (priv->local_listener),
+ G_MAXUINT);
+ }
+ else if (priv->address_type == TP_SOCKET_ADDRESS_TYPE_IPV6)
+ {
+ int ret;
+
+ ret = gibber_listener_listen_tcp_loopback_af (priv->local_listener, 0,
+ GIBBER_AF_IPV6, error);
+ if (!ret)
+ {
+ g_assert (error != NULL && *error != NULL);
+ DEBUG ("Error listening on socket: %s", (*error)->message);
+ return FALSE;
+ }
+
+ priv->address = tp_g_value_slice_new (TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV6);
+ g_value_take_boxed (priv->address,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV6));
+
+ dbus_g_type_struct_set (priv->address,
+ 0, "::1",
+ 1, gibber_listener_get_port (priv->local_listener),
+ G_MAXUINT);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static void
+salut_tube_stream_init (SalutTubeStream *self)
+{
+ SalutTubeStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_TUBE_STREAM, SalutTubeStreamPrivate);
+
+ self->priv = priv;
+
+ priv->transport_to_bytestream = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_object_unref);
+
+ priv->bytestream_to_transport = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_object_unref);
+
+ priv->transport_to_id = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, NULL);
+ priv->last_connection_id = 0;
+
+ priv->address_type = TP_SOCKET_ADDRESS_TYPE_UNIX;
+ priv->address = NULL;
+ priv->access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ priv->access_control_param = NULL;
+ priv->closed = FALSE;
+ priv->offer_needed = FALSE;
+
+ priv->dispose_has_run = FALSE;
+}
+
+static gboolean
+close_each_extra_bytestream (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (user_data);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GibberTransport *transport = (GibberTransport *) value;
+ GibberBytestreamIface *bytestream = (GibberBytestreamIface *) key;
+
+ /* We are iterating over priv->transport_to_bytestream so we can't modify it.
+ * Disconnect signals so extra_bytestream_state_changed_cb won't be
+ * called */
+ g_signal_handlers_disconnect_matched (bytestream, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+
+ g_signal_handlers_disconnect_matched (transport, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+
+ gibber_bytestream_iface_close (bytestream, NULL);
+ gibber_transport_disconnect (transport);
+ fire_connection_closed (self, transport, TP_ERROR_STR_CANCELLED,
+ "tube is closing");
+
+ g_hash_table_remove (priv->transport_to_bytestream, transport);
+
+ return TRUE;
+}
+
+static void
+salut_tube_stream_dispose (GObject *object)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (object);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ if (priv->dispose_has_run)
+ return;
+
+ salut_tube_iface_close (SALUT_TUBE_IFACE (self), FALSE);
+
+ if (priv->initiator != priv->self_handle &&
+ priv->address_type == TP_SOCKET_ADDRESS_TYPE_UNIX &&
+ priv->address != NULL)
+ {
+ /* We created a new UNIX socket. Let's delete it */
+ GArray *array;
+ GString *path;
+
+ array = g_value_get_boxed (priv->address);
+ path = g_string_new_len (array->data, array->len);
+
+ if (g_unlink (path->str) != 0)
+ {
+ DEBUG ("unlink of %s failed: %s", path->str, g_strerror (errno));
+ }
+
+ g_string_free (path, TRUE);
+ }
+
+ if (priv->transport_to_bytestream != NULL)
+ {
+ g_hash_table_unref (priv->transport_to_bytestream);
+ priv->transport_to_bytestream = NULL;
+ }
+
+ if (priv->bytestream_to_transport != NULL)
+ {
+ g_hash_table_unref (priv->bytestream_to_transport);
+ priv->bytestream_to_transport = NULL;
+ }
+
+ if (priv->transport_to_id != NULL)
+ {
+ g_hash_table_unref (priv->transport_to_id);
+ priv->transport_to_id = NULL;
+ }
+
+ tp_handle_unref (contact_repo, priv->initiator);
+
+ if (priv->local_listener != NULL)
+ {
+ g_object_unref (priv->local_listener);
+ priv->local_listener = NULL;
+ }
+
+ if (priv->contact_listener != NULL)
+ {
+ g_object_unref (priv->contact_listener);
+ priv->contact_listener = NULL;
+ }
+
+ priv->dispose_has_run = TRUE;
+
+ if (G_OBJECT_CLASS (salut_tube_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_tube_stream_parent_class)->dispose (object);
+}
+
+static void
+salut_tube_stream_finalize (GObject *object)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (object);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ g_free (priv->object_path);
+ g_free (priv->service);
+ if (priv->parameters != NULL)
+ {
+ g_hash_table_unref (priv->parameters);
+ priv->parameters = NULL;
+ }
+
+ if (priv->address != NULL)
+ {
+ tp_g_value_slice_free (priv->address);
+ priv->address = NULL;
+ }
+
+ if (priv->access_control_param != NULL)
+ {
+ tp_g_value_slice_free (priv->access_control_param);
+ priv->access_control_param = NULL;
+ }
+
+ G_OBJECT_CLASS (salut_tube_stream_parent_class)->finalize (object);
+}
+
+static void
+salut_tube_stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (object);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn;
+
+ switch (property_id)
+ {
+ case PROP_TUBES_CHANNEL:
+ g_value_set_object (value, priv->tubes_channel);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_INTERFACES:
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM)
+ {
+ /* MUC tubes */
+ g_value_set_boxed (value, salut_tube_stream_interfaces);
+ }
+ else
+ {
+ /* 1-1 tubes - omit the Group interface */
+ g_value_set_boxed (value, salut_tube_stream_interfaces + 1);
+ }
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, priv->handle);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, priv->handle_type);
+ break;
+ case PROP_SELF_HANDLE:
+ g_value_set_uint (value, priv->self_handle);
+ break;
+ case PROP_ID:
+ g_value_set_uint (value, priv->id);
+ break;
+ case PROP_TYPE:
+ g_value_set_uint (value, TP_TUBE_TYPE_STREAM);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, priv->initiator);
+ break;
+ case PROP_SERVICE:
+ g_value_set_string (value, priv->service);
+ break;
+ case PROP_PARAMETERS:
+ g_value_set_boxed (value, priv->parameters);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case PROP_OFFERED:
+ g_value_set_boolean (value, priv->offered);
+ break;
+ case PROP_ADDRESS_TYPE:
+ g_value_set_uint (value, priv->address_type);
+ break;
+ case PROP_ADDRESS:
+ g_value_set_pointer (value, priv->address);
+ break;
+ case PROP_ACCESS_CONTROL:
+ g_value_set_uint (value, priv->access_control);
+ break;
+ case PROP_ACCESS_CONTROL_PARAM:
+ g_value_set_pointer (value, priv->access_control_param);
+ break;
+ case PROP_PORT:
+ g_value_set_uint (value, priv->port);
+ break;
+ case PROP_IQ_REQ:
+ g_value_set_pointer (value, priv->iq_req);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ {
+ GHashTable *properties;
+
+ properties = tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE, "Service",
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE, "SupportedSocketTypes",
+ NULL);
+
+ if (priv->initiator != priv->self_handle)
+ {
+ /* channel has not been requested so Parameters is immutable */
+ GValue *prop_value = g_slice_new0 (GValue);
+
+ /* FIXME: use tp_dbus_properties_mixin_add_properties once it's
+ * added in tp-glib */
+ tp_dbus_properties_mixin_get (object,
+ TP_IFACE_CHANNEL_INTERFACE_TUBE, "Parameters",
+ prop_value, NULL);
+ g_assert (G_IS_VALUE (prop_value));
+
+ g_hash_table_insert (properties,
+ g_strdup_printf ("%s.%s", TP_IFACE_CHANNEL_INTERFACE_TUBE,
+ "Parameters"), prop_value);
+ }
+
+ g_value_take_boxed (value, properties);
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value,
+ (priv->initiator == priv->self_handle));
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+
+ /* some channel can have o.f.T.Channel.InitiatorHandle == 0 but
+ * tubes always have an initiator */
+ g_assert (priv->initiator != 0);
+
+ g_value_set_string (value,
+ tp_handle_inspect (repo, priv->initiator));
+ }
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, priv->handle_type);
+
+ g_value_set_string (value,
+ tp_handle_inspect (repo, priv->handle));
+ }
+ break;
+ case PROP_SUPPORTED_SOCKET_TYPES:
+ g_value_take_boxed (value,
+ salut_tube_stream_get_supported_socket_types ());
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_tube_stream_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (object);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ switch (property_id)
+ {
+ case PROP_TUBES_CHANNEL:
+ priv->tubes_channel = g_value_get_object (value);
+ break;
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_CHANNEL_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changeable on this channel, so we do nothing */
+ break;
+ case PROP_HANDLE:
+ priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ priv->handle_type = g_value_get_uint (value);
+ break;
+ case PROP_SELF_HANDLE:
+ priv->self_handle = g_value_get_uint (value);
+ break;
+ case PROP_ID:
+ priv->id = g_value_get_uint (value);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_SERVICE:
+ g_free (priv->service);
+ priv->service = g_value_dup_string (value);
+ break;
+ case PROP_PARAMETERS:
+ if (priv->parameters != NULL)
+ g_hash_table_unref (priv->parameters);
+ priv->parameters = g_value_dup_boxed (value);
+ break;
+ case PROP_OFFERED:
+ priv->offered = g_value_get_boolean (value);
+ break;
+ case PROP_ADDRESS_TYPE:
+ g_assert (g_value_get_uint (value) == TP_SOCKET_ADDRESS_TYPE_UNIX ||
+ g_value_get_uint (value) == TP_SOCKET_ADDRESS_TYPE_IPV4 ||
+ g_value_get_uint (value) == TP_SOCKET_ADDRESS_TYPE_IPV6);
+ priv->address_type = g_value_get_uint (value);
+ break;
+ case PROP_ADDRESS:
+ if (priv->address == NULL)
+ {
+ priv->address = tp_g_value_slice_dup (g_value_get_pointer (value));
+ }
+ break;
+ case PROP_ACCESS_CONTROL:
+ /* For now, only "localhost" control is implemented */
+ g_assert (g_value_get_uint (value) ==
+ TP_SOCKET_ACCESS_CONTROL_LOCALHOST);
+ priv->access_control = g_value_get_uint (value);
+ break;
+ case PROP_ACCESS_CONTROL_PARAM:
+ if (priv->access_control_param == NULL)
+ {
+ priv->access_control_param = tp_g_value_slice_dup (
+ g_value_get_pointer (value));
+ }
+ break;
+ case PROP_PORT:
+ priv->port = g_value_get_uint (value);
+ break;
+ case PROP_IQ_REQ:
+ priv->iq_req = g_value_get_pointer (value);
+ if (priv->iq_req != NULL)
+ g_object_ref (priv->iq_req);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+salut_tube_stream_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutTubeStreamPrivate *priv;
+ TpHandleRepoIface *contact_repo;
+ TpDBusDaemon *bus;
+ TpBaseConnection *base_conn;
+
+ obj = G_OBJECT_CLASS (salut_tube_stream_parent_class)->
+ constructor (type, n_props, props);
+
+ priv = SALUT_TUBE_STREAM_GET_PRIVATE (SALUT_TUBE_STREAM (obj));
+
+ /* Ref the initiator handle */
+ base_conn = TP_BASE_CONNECTION (priv->conn);
+ g_assert (priv->initiator != 0);
+ contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ tp_handle_ref (contact_repo, priv->initiator);
+
+ if (priv->initiator == priv->self_handle)
+ {
+ /* We initiated this tube */
+ priv->state = TP_TUBE_CHANNEL_STATE_NOT_OFFERED;
+ /* FIXME: we should probably remove this offer_needed */
+ priv->offer_needed = TRUE;
+ }
+ else
+ {
+ priv->state = TP_TUBE_CHANNEL_STATE_LOCAL_PENDING;
+ }
+
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+
+ DEBUG ("Registering at '%s'", priv->object_path);
+
+ return obj;
+}
+
+static void
+salut_tube_stream_class_init (SalutTubeStreamClass *salut_tube_stream_class)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl stream_tube_props[] = {
+ { "Service", "service", NULL },
+ { "SupportedSocketTypes", "supported-socket-types", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl tube_iface_props[] = {
+ { "Parameters", "parameters", NULL },
+ { "State", "state", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { TP_IFACE_CHANNEL_TYPE_STREAM_TUBE,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ stream_tube_props,
+ },
+ { TP_IFACE_CHANNEL_INTERFACE_TUBE,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ tube_iface_props,
+ },
+ { NULL }
+ };
+
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_tube_stream_class);
+ GParamSpec *param_spec;
+
+ object_class->get_property = salut_tube_stream_get_property;
+ object_class->set_property = salut_tube_stream_set_property;
+ object_class->constructor = salut_tube_stream_constructor;
+
+ g_type_class_add_private (salut_tube_stream_class,
+ sizeof (SalutTubeStreamPrivate));
+
+ object_class->dispose = salut_tube_stream_dispose;
+ object_class->finalize = salut_tube_stream_finalize;
+
+ g_object_class_override_property (object_class, PROP_CONNECTION,
+ "connection");
+ g_object_class_override_property (object_class, PROP_TUBES_CHANNEL,
+ "tubes-channel");
+ g_object_class_override_property (object_class, PROP_HANDLE,
+ "handle");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_SELF_HANDLE,
+ "self-handle");
+ g_object_class_override_property (object_class, PROP_ID,
+ "id");
+ g_object_class_override_property (object_class, PROP_TYPE,
+ "type");
+ g_object_class_override_property (object_class, PROP_INITIATOR_HANDLE,
+ "initiator-handle");
+ g_object_class_override_property (object_class, PROP_SERVICE,
+ "service");
+ g_object_class_override_property (object_class, PROP_PARAMETERS,
+ "parameters");
+ g_object_class_override_property (object_class, PROP_STATE,
+ "state");
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NAME);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "address-type",
+ "address type",
+ "a TpSocketAddressType representing the type of the listening"
+ "address of the local service",
+ 0, NUM_TP_SOCKET_ADDRESS_TYPES - 1,
+ TP_SOCKET_ADDRESS_TYPE_UNIX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ADDRESS_TYPE,
+ param_spec);
+
+ param_spec = g_param_spec_pointer (
+ "address",
+ "address",
+ "The listening address of the local service, as indicated by the "
+ "address-type",
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ADDRESS, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "access-control",
+ "access control",
+ "a TpSocketAccessControl representing the access control "
+ "the local service applies to the local socket",
+ 0, NUM_TP_SOCKET_ACCESS_CONTROLS - 1,
+ TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCESS_CONTROL,
+ param_spec);
+
+ param_spec = g_param_spec_pointer (
+ "access-control-param",
+ "access control param",
+ "A parameter for the access control type, to be interpreted as specified"
+ "in the documentation for the Socket_Access_Control enum.",
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCESS_CONTROL_PARAM,
+ param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Target JID",
+ "The string obtained by inspecting the target handle",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NAME);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "port",
+ "port on the initiator's CM",
+ "New stream in this tube will connect to the initiator's CM on this port"
+ " in case of 1-1 tube",
+ 0,
+ G_MAXUINT32,
+ 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_PORT, param_spec);
+
+ param_spec = g_param_spec_pointer (
+ "iq-req",
+ "A reference on the request stanza",
+ "A reference on the request stanza used to reply to "
+ "the iq request later",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_IQ_REQ, param_spec);
+
+ param_spec = g_param_spec_boolean (
+ "offered",
+ "Whether the application asked to offer the tube",
+ "Whether the application asked to offer the tube",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OFFERED, param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's bare JID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_boxed (
+ "supported-socket-types",
+ "Supported socket types",
+ "GHashTable containing supported socket types.",
+ dbus_g_type_get_map ("GHashTable", G_TYPE_UINT, DBUS_TYPE_G_UINT_ARRAY),
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SUPPORTED_SOCKET_TYPES,
+ param_spec);
+
+ signals[OPENED] =
+ g_signal_new ("tube-opened",
+ G_OBJECT_CLASS_TYPE (salut_tube_stream_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ salut_signals_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[NEW_CONNECTION] =
+ g_signal_new ("tube-new-connection",
+ G_OBJECT_CLASS_TYPE (salut_tube_stream_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ salut_signals_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ signals[CLOSED] =
+ g_signal_new ("tube-closed",
+ G_OBJECT_CLASS_TYPE (salut_tube_stream_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ salut_signals_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[OFFERED] =
+ g_signal_new ("tube-offered",
+ G_OBJECT_CLASS_TYPE (salut_tube_stream_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ salut_signals_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ salut_tube_stream_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutTubeStreamClass, dbus_props_class));
+}
+
+static void
+data_received_cb (GibberBytestreamIface *bytestream,
+ TpHandle sender,
+ GString *data,
+ gpointer user_data)
+{
+ SalutTubeStream *tube = SALUT_TUBE_STREAM (user_data);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (tube);
+ GibberTransport *transport;
+ GError *error = NULL;
+
+ DEBUG ("received %" G_GSIZE_FORMAT " bytes from bytestream", data->len);
+
+ transport = g_hash_table_lookup (priv->bytestream_to_transport, bytestream);
+ g_assert (transport != NULL);
+
+ /* If something goes wrong when trying to write the data on the transport,
+ * it could be disconnected, causing its removal from the hash tables.
+ * When removed, the transport would be destroyed as the hash tables keep a
+ * ref on it and so we'll call _buffer_is_empty on a destroyed transport.
+ * We avoid that by reffing the transport between the 2 calls so we keep it
+ * artificially alive if needed. */
+ g_object_ref (transport);
+ if (!gibber_transport_send (transport, (const guint8 *) data->str, data->len,
+ &error))
+ {
+ DEBUG ("sending failed: %s", error->message);
+ g_error_free (error);
+ g_object_unref (transport);
+ return;
+ }
+
+ if (!gibber_transport_buffer_is_empty (transport))
+ {
+ /* We don't want to send more data while the buffer isn't empty */
+ DEBUG ("tube buffer isn't empty. Block the bytestream");
+ gibber_bytestream_iface_block_reading (bytestream, TRUE);
+ }
+ g_object_unref (transport);
+}
+
+SalutTubeStream *
+salut_tube_stream_new (SalutConnection *conn,
+ SalutTubesChannel *tubes_channel,
+ TpHandle handle,
+ TpHandleType handle_type,
+ TpHandle self_handle,
+ TpHandle initiator,
+ gboolean offered,
+ const gchar *service,
+ GHashTable *parameters,
+ guint id,
+ guint portnum,
+ WockyStanza *iq_req)
+{
+ SalutTubeStream *obj;
+ char *object_path;
+
+ object_path = g_strdup_printf ("%s/StreamTubeChannel_%u_%u",
+ conn->parent.object_path, handle, id);
+
+ obj = g_object_new (SALUT_TYPE_TUBE_STREAM,
+ "connection", conn,
+ "tubes-channel", tubes_channel,
+ "object-path", object_path,
+ "handle", handle,
+ "handle-type", handle_type,
+ "self-handle", self_handle,
+ "initiator-handle", initiator,
+ "offered", offered,
+ "service", service,
+ "parameters", parameters,
+ "id", id,
+ "port", portnum,
+ "iq-req", iq_req,
+ NULL);
+
+ g_free (object_path);
+
+ return obj;
+}
+
+/**
+ * salut_tube_stream_accept
+ *
+ * Implements salut_tube_iface_accept on SalutTubeIface
+ */
+static gboolean
+salut_tube_stream_accept (SalutTubeIface *tube,
+ GError **error)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (tube);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ WockyStanza *reply;
+
+ if (priv->state != TP_TUBE_CHANNEL_STATE_LOCAL_PENDING)
+ return TRUE;
+
+ if (!tube_stream_open (self, error))
+ {
+ salut_tube_iface_close (SALUT_TUBE_IFACE (self), FALSE);
+ return FALSE;
+ }
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ reply = wocky_stanza_build_iq_result (priv->iq_req, NULL);
+ wocky_porter_send (priv->conn->porter, reply);
+
+ g_object_unref (priv->iq_req);
+ priv->iq_req = NULL;
+ g_object_unref (reply);
+ }
+
+ priv->state = TP_TUBE_CHANNEL_STATE_OPEN;
+ g_signal_emit (G_OBJECT (self), signals[OPENED], 0);
+
+ tp_svc_channel_interface_tube_emit_tube_channel_state_changed (
+ self, TP_TUBE_CHANNEL_STATE_OPEN);
+
+ return TRUE;
+}
+
+/**
+ * salut_tube_stream_accepted
+ *
+ * Implements salut_tube_iface_accepted on SalutTubeIface
+ */
+static void
+salut_tube_stream_accepted (SalutTubeIface *tube)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (tube);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ if (priv->state == TP_TUBE_CHANNEL_STATE_OPEN)
+ return;
+
+ priv->state = TP_TUBE_CHANNEL_STATE_OPEN;
+ g_signal_emit (G_OBJECT (self), signals[OPENED], 0);
+
+ tp_svc_channel_interface_tube_emit_tube_channel_state_changed (
+ self, TP_TUBE_CHANNEL_STATE_OPEN);
+}
+
+/**
+ * salut_tube_stream_offer_needed
+ *
+ * Implements salut_tube_iface_offer_needed on SalutTubeIface
+ */
+static gboolean
+salut_tube_stream_offer_needed (SalutTubeIface *tube)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (tube);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ gboolean ret = priv->offer_needed;
+
+ priv->offer_needed = FALSE;
+
+ return ret;
+}
+
+/* callback for listening connections from the contact's CM */
+static void
+contact_new_connection_cb (GibberListener *listener,
+ GibberTransport *transport,
+ struct sockaddr_storage *addr,
+ guint size,
+ gpointer user_data)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (user_data);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GibberBytestreamIface *bytestream;
+ SalutContactManager *contact_mgr;
+ SalutContact *contact;
+
+ g_assert (priv->handle_type == TP_HANDLE_TYPE_CONTACT);
+
+ g_object_get (priv->conn,
+ "contact-manager", &contact_mgr,
+ NULL);
+ g_assert (contact_mgr != NULL);
+
+ contact = salut_contact_manager_get_contact (contact_mgr, priv->handle);
+ if (contact == NULL)
+ {
+ DEBUG ("can't find contact with handle %d", priv->handle);
+ g_object_unref (contact_mgr);
+ return;
+ }
+
+ bytestream = g_object_new (GIBBER_TYPE_BYTESTREAM_DIRECT,
+ "state", GIBBER_BYTESTREAM_STATE_LOCAL_PENDING,
+ "self-id", priv->conn->name,
+ "peer-id", contact->name,
+ NULL);
+
+ g_assert (bytestream != NULL);
+
+ salut_tube_stream_add_bytestream (SALUT_TUBE_IFACE (self), bytestream);
+ gibber_bytestream_direct_accept_socket (bytestream, transport);
+
+ g_object_unref (bytestream);
+ g_object_unref (contact);
+ g_object_unref (contact_mgr);
+}
+
+/**
+ * salut_tube_stream_listem
+ *
+ * Implements salut_tube_iface_listen on SalutTubeIface
+ */
+static int
+salut_tube_stream_listen (SalutTubeIface *tube)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (tube);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ int ret;
+
+ g_assert (priv->contact_listener == NULL);
+ priv->contact_listener = gibber_listener_new ();
+
+ g_signal_connect (priv->contact_listener, "new-connection",
+ G_CALLBACK (contact_new_connection_cb), self);
+
+ ret = gibber_listener_listen_tcp (priv->contact_listener, 0, NULL);
+ if (ret == TRUE)
+ return gibber_listener_get_port (priv->contact_listener);
+ return -1;
+}
+
+static void
+iq_close_reply_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ GError *error = NULL;
+ WockyStanza *stanza;
+
+ stanza = wocky_porter_send_iq_finish (porter, result, &error);
+
+ if (stanza == NULL)
+ {
+ DEBUG ("Failed to close IQ: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ g_object_unref (stanza);
+}
+
+/**
+ * salut_tube_stream_close
+ *
+ * Implements salut_tube_iface_close on SalutTubeIface
+ */
+static void
+salut_tube_stream_close (SalutTubeIface *tube, gboolean closed_remotely)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (tube);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ if (priv->closed)
+ return;
+ priv->closed = TRUE;
+
+ g_hash_table_foreach_remove (priv->bytestream_to_transport,
+ close_each_extra_bytestream, self);
+
+ /* do not send the close stanza if the tube was closed due to the remote
+ * contact */
+ if (!closed_remotely && priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ WockyStanza *stanza;
+ const gchar *jid_from;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+ gchar *tube_id_str;
+ SalutContactManager *contact_mgr;
+ SalutContact *contact;
+
+ jid_from = tp_handle_inspect (contact_repo, priv->self_handle);
+ tube_id_str = g_strdup_printf ("%u", priv->id);
+
+ g_object_get (priv->conn, "contact-manager", &contact_mgr, NULL);
+ g_assert (contact_mgr != NULL);
+
+ contact = salut_contact_manager_get_contact (contact_mgr,
+ priv->handle);
+
+ stanza = wocky_stanza_build_to_contact (WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_SET,
+ jid_from, WOCKY_CONTACT (contact),
+ '(', "close",
+ ':', WOCKY_TELEPATHY_NS_TUBES,
+ '@', "id", tube_id_str,
+ ')', NULL);
+
+ wocky_porter_send_iq_async (priv->conn->porter, stanza,
+ NULL, iq_close_reply_cb, tube);
+
+ g_free (tube_id_str);
+
+ g_object_unref (stanza);
+ g_object_unref (contact);
+ g_object_unref (contact_mgr);
+ }
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ if (priv->initiator == priv->self_handle)
+ {
+ if (priv->contact_listener != NULL)
+ {
+ g_object_unref (priv->contact_listener);
+ priv->contact_listener = NULL;
+ }
+ }
+ }
+
+ g_signal_emit (G_OBJECT (self), signals[CLOSED], 0);
+}
+
+static void
+augment_si_accept_iq (WockyNode *si,
+ gpointer user_data)
+{
+ wocky_node_add_child_ns (si, "tube", WOCKY_TELEPATHY_NS_TUBES);
+}
+
+/**
+ * salut_tube_stream_add_bytestream
+ *
+ * Implements salut_tube_iface_add_bytestream on SalutTubeIface
+ */
+static void
+salut_tube_stream_add_bytestream (SalutTubeIface *tube,
+ GibberBytestreamIface *bytestream)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (tube);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GibberTransport *transport;
+
+ if (priv->initiator != priv->self_handle)
+ {
+ DEBUG ("I'm not the initiator of this tube, can't accept "
+ "an extra bytestream");
+
+ gibber_bytestream_iface_close (bytestream, NULL);
+ return;
+ }
+
+ /* New bytestream, let's connect to the socket */
+ transport = new_connection_to_socket (self, bytestream);
+ if (transport != NULL)
+ {
+ TpHandle contact;
+ gchar *peer_id;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ if (priv->state == TP_TUBE_CHANNEL_STATE_REMOTE_PENDING)
+ {
+ DEBUG ("Received first connection. Tube is now open");
+ priv->state = TP_TUBE_CHANNEL_STATE_OPEN;
+ g_signal_emit (G_OBJECT (self), signals[OPENED], 0);
+ }
+
+ DEBUG ("accept the extra bytestream");
+
+ gibber_bytestream_iface_accept (bytestream, augment_si_accept_iq, self);
+
+ g_object_get (bytestream, "peer-id", &peer_id, NULL);
+ contact = tp_handle_ensure (contact_repo, peer_id, NULL, NULL);
+
+ g_signal_emit (G_OBJECT (self), signals[NEW_CONNECTION], 0, contact);
+
+ fire_new_remote_connection (self, transport, contact);
+
+ tp_handle_unref (contact_repo, contact);
+ g_free (peer_id);
+ }
+ else
+ {
+ gibber_bytestream_iface_close (bytestream, NULL);
+ }
+}
+
+static gboolean
+check_unix_params (TpSocketAddressType address_type,
+ const GValue *address,
+ TpSocketAccessControl access_control,
+ const GValue *access_control_param,
+ GError **error)
+{
+ GArray *array;
+ GString *socket_path;
+ struct stat stat_buff;
+ guint i;
+ struct sockaddr_un dummy;
+
+ g_assert (address_type == TP_SOCKET_ADDRESS_TYPE_UNIX);
+
+ /* Check address type */
+ if (G_VALUE_TYPE (address) != DBUS_TYPE_G_UCHAR_ARRAY)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unix socket address is supposed to be ay");
+ return FALSE;
+ }
+
+ array = g_value_get_boxed (address);
+
+ if (array->len > sizeof (dummy.sun_path) - 1)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unix socket path is too long (max length allowed: %"
+ G_GSIZE_FORMAT ")",
+ sizeof (dummy.sun_path) - 1);
+ return FALSE;
+ }
+
+ for (i = 0; i < array->len; i++)
+ {
+ if (g_array_index (array, gchar , i) == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unix socket path can't contain zero bytes");
+ return FALSE;
+ }
+ }
+
+ socket_path = g_string_new_len (array->data, array->len);
+
+ if (g_stat (socket_path->str, &stat_buff) == -1)
+ {
+ DEBUG ("Error calling stat on socket: %s", g_strerror (errno));
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "%s: %s",
+ socket_path->str, g_strerror (errno));
+ g_string_free (socket_path, TRUE);
+ return FALSE;
+ }
+
+ if (!S_ISSOCK (stat_buff.st_mode))
+ {
+ DEBUG ("%s is not a socket", socket_path->str);
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "%s is not a socket", socket_path->str);
+ g_string_free (socket_path, TRUE);
+ return FALSE;
+ }
+
+ g_string_free (socket_path, TRUE);
+
+ if (access_control != TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unix sockets only support localhost control access");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+check_ip_params (TpSocketAddressType address_type,
+ const GValue *address,
+ TpSocketAccessControl access_control,
+ const GValue *access_control_param,
+ GError **error)
+{
+ gchar *ip;
+ guint port;
+ struct addrinfo req, *result = NULL;
+ int ret;
+
+ /* Check address type */
+ if (address_type == TP_SOCKET_ADDRESS_TYPE_IPV4)
+ {
+ if (G_VALUE_TYPE (address) != TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "IPv4 socket address is supposed to be sq");
+ return FALSE;
+ }
+ }
+ else if (address_type == TP_SOCKET_ADDRESS_TYPE_IPV6)
+ {
+ if (G_VALUE_TYPE (address) != TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV6)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "IPv6 socket address is supposed to be sq");
+ return FALSE;
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ dbus_g_type_struct_get (address,
+ 0, &ip,
+ 1, &port,
+ G_MAXUINT);
+
+ memset (&req, 0, sizeof (req));
+ req.ai_flags = AI_NUMERICHOST;
+ req.ai_socktype = SOCK_STREAM;
+ req.ai_protocol = IPPROTO_TCP;
+
+ if (address_type == TP_SOCKET_ADDRESS_TYPE_IPV4)
+ req.ai_family = AF_INET;
+ else
+ req.ai_family = AF_INET6;
+
+ ret = getaddrinfo (ip, NULL, &req, &result);
+ if (ret != 0)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid address: %s", gai_strerror (ret));
+ g_free (ip);
+ return FALSE;
+ }
+
+ g_free (ip);
+ freeaddrinfo (result);
+
+ if (access_control != TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "%s sockets only support localhost control access",
+ (address_type == TP_SOCKET_ADDRESS_TYPE_IPV4 ? "IPv4" : "IPv6"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+salut_tube_stream_check_params (TpSocketAddressType address_type,
+ const GValue *address,
+ TpSocketAccessControl access_control,
+ const GValue *access_control_param,
+ GError **error)
+{
+ switch (address_type)
+ {
+ case TP_SOCKET_ADDRESS_TYPE_UNIX:
+ return check_unix_params (address_type, address, access_control,
+ access_control_param, error);
+
+ case TP_SOCKET_ADDRESS_TYPE_IPV4:
+ case TP_SOCKET_ADDRESS_TYPE_IPV6:
+ return check_ip_params (address_type, address, access_control,
+ access_control_param, error);
+
+ default:
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Address type %d not implemented", address_type);
+ return FALSE;
+ }
+}
+
+/**
+ * salut_tube_stream_offer_async
+ *
+ * Implements D-Bus method Offer
+ * on org.freedesktop.Telepathy.Channel.Type.StreamTube
+ */
+static void
+salut_tube_stream_offer_async (TpSvcChannelTypeStreamTube *iface,
+ guint address_type,
+ const GValue *address,
+ guint access_control,
+ GHashTable *parameters,
+ DBusGMethodInvocation *context)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (iface);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (priv->state != TP_TUBE_CHANNEL_STATE_NOT_OFFERED)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not in the not offered state");
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ if (!salut_tube_stream_check_params (address_type, address,
+ access_control, NULL, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ g_assert (address_type == TP_SOCKET_ADDRESS_TYPE_UNIX ||
+ address_type == TP_SOCKET_ADDRESS_TYPE_IPV4 ||
+ address_type == TP_SOCKET_ADDRESS_TYPE_IPV6);
+ g_assert (priv->address == NULL);
+ priv->address_type = address_type;
+ priv->address = tp_g_value_slice_dup (address);
+ g_assert (priv->access_control == TP_SOCKET_ACCESS_CONTROL_LOCALHOST);
+ priv->access_control = access_control;
+ g_assert (priv->access_control_param == NULL);
+
+ g_object_set (self, "parameters", parameters, NULL);
+
+ if (!salut_tube_stream_offer (self, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_svc_channel_type_stream_tube_return_from_offer (context);
+}
+
+/**
+ * salut_tube_stream_accept_async
+ *
+ * Implements D-Bus method Accept
+ * on org.freedesktop.Telepathy.Channel.Type.StreamTube
+ */
+static void
+salut_tube_stream_accept_async (TpSvcChannelTypeStreamTube *iface,
+ guint address_type,
+ guint access_control,
+ const GValue *access_control_param,
+ DBusGMethodInvocation *context)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (iface);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (priv->state != TP_TUBE_CHANNEL_STATE_LOCAL_PENDING)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not in the local pending state" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ if (address_type != TP_SOCKET_ADDRESS_TYPE_UNIX &&
+ address_type != TP_SOCKET_ADDRESS_TYPE_IPV4 &&
+ address_type != TP_SOCKET_ADDRESS_TYPE_IPV6)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Address type %d not implemented", address_type);
+
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ if (access_control != TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unix sockets only support localhost control access" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ g_object_set (self,
+ "address-type", address_type,
+ "access-control", access_control,
+ "access-control-param", access_control_param,
+ NULL);
+
+ if (!salut_tube_stream_accept (SALUT_TUBE_IFACE (self), &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+#if 0
+ /* TODO: add a property "muc" and set it at initialization */
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM)
+ salut_muc_channel_send_presence (self->muc, NULL);
+#endif
+
+ tp_svc_channel_type_stream_tube_return_from_accept (context,
+ priv->address);
+}
+
+/**
+ * salut_tube_stream_close_async:
+ *
+ * Implements D-Bus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tube_stream_close_async (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ salut_tube_stream_close (SALUT_TUBE_IFACE (iface), FALSE);
+ tp_svc_channel_return_from_close (context);
+}
+
+/**
+ * salut_tube_stream_get_channel_type
+ *
+ * Implements D-Bus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tube_stream_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE);
+}
+
+/**
+ * salut_tube_stream_get_handle
+ *
+ * Implements D-Bus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tube_stream_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (iface);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ tp_svc_channel_return_from_get_handle (context, priv->handle_type,
+ priv->handle);
+}
+
+/**
+ * salut_tube_stream_get_interfaces
+ *
+ * Implements D-Bus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tube_stream_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubeStream *self = SALUT_TUBE_STREAM (iface);
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ /* omit the Group interface */
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_tube_stream_interfaces + 1);
+ }
+ else
+ {
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_tube_stream_interfaces);
+ }
+}
+
+static void
+destroy_socket_control_list (gpointer data)
+{
+ GArray *tab = data;
+ g_array_unref (tab);
+}
+
+GHashTable *
+salut_tube_stream_get_supported_socket_types (void)
+{
+ GHashTable *ret;
+ GArray *unix_tab, *ipv4_tab, *ipv6_tab;
+ TpSocketAccessControl access_control;
+
+ ret = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
+ destroy_socket_control_list);
+
+ /* Socket_Address_Type_Unix */
+ unix_tab = g_array_sized_new (FALSE, FALSE, sizeof (TpSocketAccessControl),
+ 1);
+ access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val (unix_tab, access_control);
+ g_hash_table_insert (ret, GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_UNIX),
+ unix_tab);
+
+ /* Socket_Address_Type_IPv4 */
+ ipv4_tab = g_array_sized_new (FALSE, FALSE, sizeof (TpSocketAccessControl),
+ 1);
+ access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val (ipv4_tab, access_control);
+ g_hash_table_insert (ret, GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_IPV4),
+ ipv4_tab);
+
+ /* Socket_Address_Type_IPv6 */
+ ipv6_tab = g_array_sized_new (FALSE, FALSE, sizeof (TpSocketAccessControl),
+ 1);
+ access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val (ipv6_tab, access_control);
+ g_hash_table_insert (ret, GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_IPV6),
+ ipv6_tab);
+
+ return ret;
+}
+
+gboolean
+salut_tube_stream_offer (SalutTubeStream *self,
+ GError **error)
+{
+ SalutTubeStreamPrivate *priv = SALUT_TUBE_STREAM_GET_PRIVATE (self);
+
+ g_assert (priv->state == TP_TUBE_CHANNEL_STATE_NOT_OFFERED);
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ priv->state = TP_TUBE_CHANNEL_STATE_REMOTE_PENDING;
+ salut_tubes_channel_send_iq_offer (priv->tubes_channel);
+
+ tp_svc_channel_interface_tube_emit_tube_channel_state_changed (
+ self, TP_TUBE_CHANNEL_STATE_REMOTE_PENDING);
+ }
+ else
+ {
+ /* muc tube is open as soon it's offered */
+ priv->state = TP_TUBE_CHANNEL_STATE_OPEN;
+ tp_svc_channel_interface_tube_emit_tube_channel_state_changed (
+ self, TP_TUBE_CHANNEL_STATE_OPEN);
+ g_signal_emit (G_OBJECT (self), signals[OPENED], 0);
+ }
+
+ g_signal_emit (G_OBJECT (self), signals[OFFERED], 0);
+ return TRUE;
+}
+
+const gchar * const *
+salut_tube_stream_channel_get_allowed_properties (void)
+{
+ return salut_tube_stream_channel_allowed_properties;
+}
+
+static void
+tube_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutTubeIfaceClass *klass = (SalutTubeIfaceClass *) g_iface;
+
+ klass->accept = salut_tube_stream_accept;
+ klass->accepted = salut_tube_stream_accepted;
+ klass->offer_needed = salut_tube_stream_offer_needed;
+ klass->listen = salut_tube_stream_listen;
+ klass->close = salut_tube_stream_close;
+ klass->add_bytestream = salut_tube_stream_add_bytestream;
+}
+
+static void
+streamtube_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelTypeStreamTubeClass *klass =
+ (TpSvcChannelTypeStreamTubeClass *) g_iface;
+
+#define IMPLEMENT(x, suffix) tp_svc_channel_type_stream_tube_implement_##x (\
+ klass, salut_tube_stream_##x##suffix)
+ IMPLEMENT(offer,_async);
+ IMPLEMENT(accept,_async);
+#undef IMPLEMENT
+}
+
+static void
+channel_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface;
+
+#define IMPLEMENT(x, suffix) tp_svc_channel_implement_##x (\
+ klass, salut_tube_stream_##x##suffix)
+ IMPLEMENT(close,_async);
+ IMPLEMENT(get_channel_type,);
+ IMPLEMENT(get_handle,);
+ IMPLEMENT(get_interfaces,);
+#undef IMPLEMENT
+}
diff --git a/salut/src/tube-stream.h b/salut/src/tube-stream.h
new file mode 100644
index 000000000..fb908590e
--- /dev/null
+++ b/salut/src/tube-stream.h
@@ -0,0 +1,87 @@
+/*
+ * tube-stream.h - Header for SalutTubeStream
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_TUBE_STREAM_H__
+#define __SALUT_TUBE_STREAM_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "extensions/extensions.h"
+#include "connection.h"
+#include "tubes-channel.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutTubeStream SalutTubeStream;
+typedef struct _SalutTubeStreamClass SalutTubeStreamClass;
+
+struct _SalutTubeStreamClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _SalutTubeStream {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType salut_tube_stream_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_TUBE_STREAM \
+ (salut_tube_stream_get_type ())
+#define SALUT_TUBE_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_TUBE_STREAM, SalutTubeStream))
+#define SALUT_TUBE_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_TUBE_STREAM,\
+ SalutTubeStreamClass))
+#define SALUT_IS_TUBE_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_TUBE_STREAM))
+#define SALUT_IS_TUBE_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_TUBE_STREAM))
+#define SALUT_TUBE_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_TUBE_STREAM,\
+ SalutTubeStreamClass))
+
+SalutTubeStream *salut_tube_stream_new (SalutConnection *conn,
+ SalutTubesChannel *tubes_channel,
+ TpHandle handle,
+ TpHandleType handle_type, TpHandle self_handle, TpHandle initiator,
+ gboolean offered, const gchar *service,
+ GHashTable *parameters, guint id, guint portnum,
+ WockyStanza *iq_req);
+
+gboolean salut_tube_stream_check_params (TpSocketAddressType address_type,
+ const GValue *address, TpSocketAccessControl access_control,
+ const GValue *access_control_param, GError **error);
+
+GHashTable * salut_tube_stream_get_supported_socket_types (void);
+
+gboolean salut_tube_stream_offer (SalutTubeStream *self, GError **error);
+
+const gchar * const * salut_tube_stream_channel_get_allowed_properties (void);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_TUBE_STREAM_H__ */
diff --git a/salut/src/tubes-channel.c b/salut/src/tubes-channel.c
new file mode 100644
index 000000000..9982719e2
--- /dev/null
+++ b/salut/src/tubes-channel.c
@@ -0,0 +1,2606 @@
+/*
+ * tubes-channel.c - Source for SalutTubesChannel
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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 tubesplied 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tubes-channel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+#include <wocky/wocky-stanza.h>
+#include <wocky/wocky-namespaces.h>
+#include <wocky/wocky-xmpp-error.h>
+
+#include <gibber/gibber-muc-connection.h>
+#include <gibber/gibber-bytestream-muc.h>
+
+#define DEBUG_FLAG DEBUG_TUBES
+#include "debug.h"
+#include "extensions/extensions.h"
+#include "util.h"
+#include "connection.h"
+#include "contact.h"
+#include "muc-channel.h"
+#include "muc-manager.h"
+#include "tubes-manager.h"
+#include "tube-iface.h"
+#include "tube-dbus.h"
+#include "tube-stream.h"
+
+#define SALUT_CHANNEL_TUBE_TYPE \
+ (dbus_g_type_get_struct ("GValueArray", \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_STRING, \
+ dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), \
+ G_TYPE_UINT, \
+ G_TYPE_INVALID))
+
+#define DBUS_NAME_PAIR_TYPE \
+ (dbus_g_type_get_struct ("GValueArray", \
+ G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID))
+
+static void channel_iface_init (gpointer g_iface, gpointer iface_data);
+static void tubes_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SalutTubesChannel, salut_tubes_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TUBES, tubes_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_external_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+);
+
+/* Channel state */
+typedef enum
+{
+ CHANNEL_NOT_CONNECTED = 0,
+ CHANNEL_CONNECTING,
+ CHANNEL_CONNECTED,
+ CHANNEL_CLOSING,
+} ChannelState;
+
+/* properties */
+static const char *salut_tubes_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ /* If more interfaces are added, either keep Group as the first, or change
+ * the implementations of salut_tubes_channel_get_interfaces () and
+ * salut_tubes_channel_get_property () too */
+ NULL
+};
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_CONNECTION,
+ PROP_MUC,
+ PROP_INTERFACES,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_ID,
+ PROP_INITIATOR_HANDLE,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+
+ /* only for 1-1 tubes */
+ PROP_CONTACT,
+
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutTubesChannelPrivate SalutTubesChannelPrivate;
+
+struct _SalutTubesChannelPrivate
+{
+ SalutConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandleType handle_type;
+ TpHandle self_handle;
+ TpHandle initiator;
+ gboolean requested;
+ /* Used for MUC tubes channel only */
+ GibberMucConnection *muc_connection;
+
+ /* Used for 1-1 tubes channel */
+ SalutContact *contact;
+ ChannelState state;
+
+ /* guint tube_id -> SalutTubeDBus tube */
+ GHashTable *tubes;
+
+ gboolean closed;
+ gboolean dispose_has_run;
+};
+
+#define SALUT_TUBES_CHANNEL_GET_PRIVATE(obj) \
+ ((SalutTubesChannelPrivate *) ((SalutTubesChannel *) obj)->priv)
+
+static gboolean update_tubes_info (SalutTubesChannel *self);
+static void muc_connection_lost_senders_cb (GibberMucConnection *conn,
+ GArray *senders, gpointer user_data);
+static void muc_connection_new_senders_cb (GibberMucConnection *conn,
+ GArray *senders, gpointer user_data);
+static gboolean extract_tube_information (SalutTubesChannel *self,
+ WockyNode *tube_node, TpTubeType *type, TpHandle *initiator_handle,
+ const gchar **service, GHashTable **parameters, guint *tube_id);
+static SalutTubeIface * create_new_tube (SalutTubesChannel *self,
+ TpTubeType type, TpHandle initiator, gboolean offered,
+ const gchar *service, GHashTable *parameters, guint tube_id, guint portnum,
+ WockyStanza *iq_req);
+
+static void
+salut_tubes_channel_init (SalutTubesChannel *self)
+{
+ SalutTubesChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_TUBES_CHANNEL, SalutTubesChannelPrivate);
+
+ self->priv = priv;
+
+ priv->tubes = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) g_object_unref);
+
+ priv->contact = NULL;
+ priv->state = CHANNEL_NOT_CONNECTED;
+
+ priv->dispose_has_run = FALSE;
+ priv->closed = FALSE;
+}
+
+static GObject *
+salut_tubes_channel_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutTubesChannel *self;
+ SalutTubesChannelPrivate *priv;
+ TpDBusDaemon *bus;
+ TpBaseConnection *base_conn;
+ TpHandleRepoIface *handle_repo;
+
+ obj = G_OBJECT_CLASS (salut_tubes_channel_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_TUBES_CHANNEL (obj);
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ base_conn = TP_BASE_CONNECTION (priv->conn);
+ handle_repo = tp_base_connection_get_handles (
+ base_conn, priv->handle_type);
+
+ tp_handle_ref (handle_repo, priv->handle);
+
+ switch (priv->handle_type)
+ {
+ case TP_HANDLE_TYPE_CONTACT:
+ g_assert (self->muc == NULL);
+ priv->self_handle = ((TpBaseConnection *)
+ (priv->conn))->self_handle;
+ break;
+
+ case TP_HANDLE_TYPE_ROOM:
+ g_assert (self->muc != NULL);
+ priv->self_handle = self->muc->group.self_handle;
+ tp_external_group_mixin_init (obj, (GObject *) self->muc);
+ g_object_get (self->muc,
+ "muc-connection", &(priv->muc_connection),
+ NULL);
+ g_assert (priv->muc_connection != NULL);
+
+ g_signal_connect (priv->muc_connection, "new-senders",
+ G_CALLBACK (muc_connection_new_senders_cb), self);
+ g_signal_connect (priv->muc_connection, "lost-senders",
+ G_CALLBACK (muc_connection_lost_senders_cb), self);
+
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Connect to the bus */
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+
+ DEBUG ("Registering at '%s'", priv->object_path);
+
+ return obj;
+}
+
+static void
+salut_tubes_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutTubesChannel *chan = SALUT_TUBES_CHANNEL (object);
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (chan);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TUBES);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, priv->handle_type);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, priv->handle);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ case PROP_MUC:
+ g_value_set_object (value, chan->muc);
+ break;
+ case PROP_CONTACT:
+ g_value_set_object (value, priv->contact);
+ break;
+ case PROP_INTERFACES:
+ if (chan->muc)
+ g_value_set_static_boxed (value, salut_tubes_channel_interfaces);
+ else
+ g_value_set_static_boxed (value, salut_tubes_channel_interfaces + 1);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, priv->handle_type);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->handle));
+ }
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_assert (priv->initiator != 0);
+ g_value_set_uint (value, priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_assert (priv->initiator != 0);
+ g_value_set_string (value, tp_handle_inspect (repo, priv->initiator));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, priv->requested);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_tubes_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutTubesChannel *chan = SALUT_TUBES_CHANNEL (object);
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (chan);
+ const gchar *value_str;
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_CHANNEL_TYPE:
+ /* this property is writable in the interface (in
+ * telepathy-glib > 0.7.0), but not actually
+ * meaningfully changeable on this channel, so we do nothing */
+ value_str = g_value_get_string (value);
+ g_assert (value_str == NULL || !tp_strdiff (value_str,
+ TP_IFACE_CHANNEL_TYPE_TUBES));
+ break;
+ case PROP_HANDLE_TYPE:
+ priv->handle_type = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE:
+ priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ case PROP_MUC:
+ chan->muc = g_value_get_object (value);
+ break;
+ case PROP_CONTACT:
+ priv->contact = g_value_get_object (value);
+ /* contact is set only for 1-1 tubes */
+ if (priv->contact != NULL)
+ g_object_ref (priv->contact);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ priv->initiator = g_value_get_uint (value);
+ g_assert (priv->initiator != 0);
+ break;
+ case PROP_REQUESTED:
+ priv->requested = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+setup_connection (SalutTubesChannel *self)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ DEBUG ("setting up the tubes channel");
+
+ if (priv->state == CHANNEL_CONNECTING)
+ return;
+
+ priv->state = CHANNEL_CONNECTED;
+ DEBUG ("priv->state = CHANNEL_CONNECTED");
+ salut_tubes_channel_send_iq_offer (self);
+}
+
+static void
+d_bus_names_changed_added (SalutTubesChannel *self,
+ guint tube_id,
+ TpHandle contact,
+ const gchar *new_name)
+{
+ GPtrArray *added = g_ptr_array_sized_new (1);
+ GArray *removed = g_array_new (FALSE, FALSE, sizeof (guint));
+ GValue tmp = {0,};
+ guint i;
+
+ g_value_init (&tmp, DBUS_NAME_PAIR_TYPE);
+ g_value_take_boxed (&tmp,
+ dbus_g_type_specialized_construct (DBUS_NAME_PAIR_TYPE));
+ dbus_g_type_struct_set (&tmp,
+ 0, contact,
+ 1, new_name,
+ G_MAXUINT);
+ g_ptr_array_add (added, g_value_get_boxed (&tmp));
+
+ tp_svc_channel_type_tubes_emit_d_bus_names_changed (self,
+ tube_id, added, removed);
+
+ for (i = 0; i < added->len; i++)
+ g_boxed_free (DBUS_NAME_PAIR_TYPE, added->pdata[i]);
+ g_ptr_array_unref (added);
+ g_array_unref (removed);
+}
+
+static void
+d_bus_names_changed_removed (SalutTubesChannel *self,
+ guint tube_id,
+ TpHandle contact)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ GPtrArray *added;
+ GArray *removed;
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ return;
+
+ added = g_ptr_array_new ();
+ removed = g_array_new (FALSE, FALSE, sizeof (guint));
+
+ g_array_append_val (removed, contact);
+
+ tp_svc_channel_type_tubes_emit_d_bus_names_changed (self,
+ tube_id, added, removed);
+
+ g_ptr_array_unref (added);
+ g_array_unref (removed);
+}
+
+static void
+add_name_in_dbus_names (SalutTubesChannel *self,
+ guint tube_id,
+ TpHandle handle,
+ const gchar *dbus_name)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ SalutTubeDBus *tube;
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ return;
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (tube_id));
+ if (tube == NULL)
+ return;
+
+ if (salut_tube_dbus_add_name (tube, handle, dbus_name))
+ {
+ /* Emit the DBusNamesChanged signal */
+ d_bus_names_changed_added (self, tube_id, handle, dbus_name);
+ }
+}
+
+static void
+add_yourself_in_dbus_names (SalutTubesChannel *self,
+ guint tube_id)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ SalutTubeDBus *tube;
+ gchar *dbus_name;
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ return;
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (tube_id));
+ if (tube == NULL)
+ return;
+
+ g_object_get (tube,
+ "dbus-name", &dbus_name,
+ NULL);
+
+ add_name_in_dbus_names (self, tube_id, priv->self_handle, dbus_name);
+
+ g_free (dbus_name);
+}
+
+/**
+ * salut_tubes_channel_get_available_tube_types
+ *
+ * Implements D-Bus method GetAvailableTubeTypes
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_get_available_tube_types (TpSvcChannelTypeTubes *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ GArray *ret;
+ TpTubeType type;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ ret = g_array_sized_new (FALSE, FALSE, sizeof (TpTubeType), 1);
+ type = TP_TUBE_TYPE_DBUS;
+ g_array_append_val (ret, type);
+ type = TP_TUBE_TYPE_STREAM;
+ g_array_append_val (ret, type);
+
+ tp_svc_channel_type_tubes_return_from_get_available_tube_types (context,
+ ret);
+
+ g_array_unref (ret);
+}
+
+struct _add_in_old_dbus_tubes_data
+{
+ GHashTable *old_dbus_tubes;
+ TpHandle contact;
+};
+
+static void
+add_in_old_dbus_tubes (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ guint tube_id = GPOINTER_TO_UINT (key);
+ SalutTubeIface *tube = SALUT_TUBE_IFACE (value);
+ struct _add_in_old_dbus_tubes_data *data =
+ (struct _add_in_old_dbus_tubes_data *) user_data;
+ TpTubeType type;
+
+ g_object_get (tube, "type", &type, NULL);
+
+ if (type != TP_TUBE_TYPE_DBUS)
+ return;
+
+ if (salut_tube_dbus_handle_in_names (SALUT_TUBE_DBUS (tube),
+ data->contact))
+ {
+ /* contact was in this tube */
+ g_hash_table_insert (data->old_dbus_tubes, GUINT_TO_POINTER (tube_id),
+ tube);
+ }
+}
+
+struct
+emit_d_bus_names_changed_foreach_data
+{
+ SalutTubesChannel *self;
+ TpHandle contact;
+};
+
+static void
+emit_d_bus_names_changed_foreach (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ guint tube_id = GPOINTER_TO_UINT (key);
+ SalutTubeDBus *tube = SALUT_TUBE_DBUS (value);
+ struct emit_d_bus_names_changed_foreach_data *data =
+ (struct emit_d_bus_names_changed_foreach_data *) user_data;
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (
+ data->self);
+
+ if (salut_tube_dbus_remove_name (tube, data->contact))
+ {
+ /* Emit the DBusNamesChanged signal */
+ d_bus_names_changed_removed (data->self, tube_id, data->contact);
+ }
+
+ /* Remove the contact as sender in the muc bytestream */
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM)
+ {
+ GibberBytestreamIface *bytestream;
+
+ g_object_get (tube, "bytestream", &bytestream, NULL);
+ g_assert (bytestream != NULL);
+
+ if (GIBBER_IS_BYTESTREAM_MUC (bytestream))
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+ const gchar *sender;
+
+ sender = tp_handle_inspect (contact_repo, data->contact);
+ if (sender != NULL)
+ gibber_bytestream_muc_remove_sender (
+ GIBBER_BYTESTREAM_MUC (bytestream), sender);
+ }
+
+ g_object_unref (bytestream);
+ }
+}
+
+/* MUC message */
+/* Return an array containing all the SalutTubeIface * channels that have been
+ * created due to this message. These channels have not been announced yet
+ * so it's the responsability of the caller to announce them. */
+GPtrArray *
+salut_tubes_channel_muc_message_received (SalutTubesChannel *self,
+ const gchar *sender,
+ WockyStanza *stanza)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle contact;
+ WockyNode *top_node = wocky_stanza_get_top_node (stanza);
+ WockyNode *tubes_node;
+ GSList *l;
+ GHashTable *old_dbus_tubes;
+ struct _add_in_old_dbus_tubes_data add_data;
+ struct emit_d_bus_names_changed_foreach_data emit_data;
+ WockyStanzaType stanza_type;
+ WockyStanzaSubType sub_type;
+ GPtrArray *result = g_ptr_array_new ();
+
+ contact = tp_handle_lookup (contact_repo, sender, NULL, NULL);
+ g_assert (contact != 0);
+
+ if (contact == priv->self_handle)
+ /* We don't need to inspect our own tubes */
+ return result;
+
+ wocky_stanza_get_type_info (stanza, &stanza_type, &sub_type);
+ if (stanza_type != WOCKY_STANZA_TYPE_MESSAGE
+ || sub_type != WOCKY_STANZA_SUB_TYPE_GROUPCHAT)
+ return result;
+
+ tubes_node = wocky_node_get_child_ns (top_node, "tubes",
+ WOCKY_TELEPATHY_NS_TUBES);
+ g_assert (tubes_node != NULL);
+
+ /* Fill old_dbus_tubes with D-BUS tubes previoulsy announced by
+ * the contact */
+ old_dbus_tubes = g_hash_table_new (g_direct_hash, g_direct_equal);
+ add_data.old_dbus_tubes = old_dbus_tubes;
+ add_data.contact = contact;
+ g_hash_table_foreach (priv->tubes, add_in_old_dbus_tubes, &add_data);
+
+ for (l = tubes_node->children; l != NULL; l = l->next)
+ {
+ WockyNode *tube_node = (WockyNode *) l->data;
+ const gchar *stream_id;
+ SalutTubeIface *tube;
+ guint tube_id;
+ TpTubeType type;
+
+ stream_id = wocky_node_get_attribute (tube_node, "stream-id");
+
+ extract_tube_information (self, tube_node, NULL,
+ NULL, NULL, NULL, &tube_id);
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (tube_id));
+
+ if (tube == NULL)
+ {
+ /* We don't know yet this tube */
+ const gchar *service;
+ TpHandle initiator_handle;
+ GHashTable *parameters;
+ guint id;
+
+ if (extract_tube_information (self, tube_node, &type,
+ &initiator_handle, &service, &parameters, &id))
+ {
+ switch (type)
+ {
+ case TP_TUBE_TYPE_DBUS:
+ {
+ if (initiator_handle == 0)
+ {
+ DEBUG ("D-Bus tube initiator missing");
+ /* skip to the next child of <tubes> */
+ continue;
+ }
+ }
+ break;
+ case TP_TUBE_TYPE_STREAM:
+ {
+ if (initiator_handle != 0)
+ /* ignore it */
+ tp_handle_unref (contact_repo, initiator_handle);
+
+ initiator_handle = contact;
+ tp_handle_ref (contact_repo, initiator_handle);
+ }
+ break;
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ tube = create_new_tube (self, type, initiator_handle, FALSE,
+ service, parameters, id, 0, NULL);
+ g_ptr_array_add (result, tube);
+
+ /* the tube has reffed its initiator, no need to keep a ref */
+ tp_handle_unref (contact_repo, initiator_handle);
+ g_hash_table_unref (parameters);
+ }
+ }
+ else
+ {
+ /* The contact is in the tube.
+ * Remove it from old_dbus_tubes if needed */
+ g_hash_table_remove (old_dbus_tubes, GUINT_TO_POINTER (tube_id));
+ }
+
+ if (tube == NULL)
+ continue;
+
+ g_object_get (tube, "type", &type, NULL);
+
+ if (type == TP_TUBE_TYPE_DBUS)
+ {
+ /* Update mapping of handle -> D-Bus name. */
+ if (!salut_tube_dbus_handle_in_names (SALUT_TUBE_DBUS (tube),
+ contact))
+ {
+ /* Contact just joined the tube */
+ const gchar *new_name;
+
+ new_name = wocky_node_get_attribute (tube_node,
+ "dbus-name");
+
+ if (!new_name)
+ {
+ DEBUG ("Contact %u isn't announcing their D-Bus name",
+ contact);
+ continue;
+ }
+
+ add_name_in_dbus_names (self, tube_id, contact, new_name);
+
+ /* associate the contact with his stream id */
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM)
+ {
+ GibberBytestreamIface *bytestream;
+
+ g_object_get (tube, "bytestream", &bytestream, NULL);
+ g_assert (bytestream != NULL);
+
+ if (GIBBER_IS_BYTESTREAM_MUC (bytestream))
+ {
+ guint16 tmp = (guint16) atoi (stream_id);
+
+ gibber_bytestream_muc_add_sender (
+ GIBBER_BYTESTREAM_MUC (bytestream), sender, tmp);
+ }
+
+ g_object_unref (bytestream);
+ }
+ }
+ }
+ }
+
+ /* Tubes remaining in old_dbus_tubes was left by the contact */
+ emit_data.contact = contact;
+ emit_data.self = self;
+ g_hash_table_foreach (old_dbus_tubes, emit_d_bus_names_changed_foreach,
+ &emit_data);
+
+ g_hash_table_unref (old_dbus_tubes);
+
+ return result;
+}
+
+/* 1-1 message */
+
+/* Return a newly created SalutTubeIface channel if it has been created
+ * due to this message. This channel has not been announced yet
+ * so it's the responsability of the caller to announce it. */
+SalutTubeIface *
+salut_tubes_channel_message_received (SalutTubesChannel *self,
+ const gchar *service,
+ TpTubeType tube_type,
+ TpHandle initiator_handle,
+ GHashTable *parameters,
+ guint tube_id,
+ guint portnum,
+ WockyStanza *iq_req)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ SalutTubeIface *tube;
+
+ /* do we already know this tube? */
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (tube_id));
+ if (tube == NULL)
+ {
+ tube = create_new_tube (self, tube_type, initiator_handle, FALSE,
+ service, parameters, tube_id, portnum, iq_req);
+ return tube;
+ }
+
+ return NULL;
+}
+
+void
+salut_tubes_channel_message_close_received (SalutTubesChannel *self,
+ TpHandle initiator_handle,
+ guint tube_id)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ SalutTubeIface *tube;
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (tube_id));
+
+ if (tube)
+ {
+ DEBUG ("received a tube close message");
+ salut_tube_iface_close (tube, TRUE);
+ }
+ else
+ {
+ DEBUG ("received a tube close message on a non existent tube");
+ }
+}
+
+static gint
+generate_tube_id (void)
+{
+ return g_random_int_range (0, G_MAXINT);
+}
+
+SalutTubeIface *
+salut_tubes_channel_tube_request (SalutTubesChannel *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ SalutTubeIface *tube;
+ const gchar *channel_type;
+ const gchar *service;
+ guint tube_id;
+ TpTubeType type;
+ GHashTable *parameters;
+
+ tube_id = generate_tube_id ();
+
+ channel_type = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType");
+
+ if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
+ {
+ type = TP_TUBE_TYPE_STREAM;
+ service = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service");
+
+ }
+ else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE))
+ {
+ type = TP_TUBE_TYPE_DBUS;
+ service = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName");
+ }
+ else
+ g_assert_not_reached ();
+
+ /* if the service property is missing, the requestotron rejects the request
+ */
+ g_assert (service != NULL);
+
+ DEBUG ("Request a tube channel with type='%s' and service='%s'",
+ channel_type, service);
+
+ /* requested tubes have an empty parameters dict */
+ parameters = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ tube = create_new_tube (self, type, priv->self_handle, FALSE, service,
+ parameters, tube_id, 0, NULL);
+
+ g_hash_table_unref (parameters);
+ return tube;
+}
+
+static void
+muc_connection_new_senders_cb (GibberMucConnection *conn,
+ GArray *senders,
+ gpointer user_data)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (user_data);
+
+ update_tubes_info (self);
+}
+
+static void
+muc_connection_lost_senders_cb (GibberMucConnection *conn,
+ GArray *senders,
+ gpointer user_data)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (user_data);
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+ guint i;
+
+ for (i = 0; i < senders->len; i++)
+ {
+ gchar *sender;
+ TpHandle contact;
+ GHashTable *old_dbus_tubes;
+ struct _add_in_old_dbus_tubes_data add_data;
+ struct emit_d_bus_names_changed_foreach_data emit_data;
+
+ sender = g_array_index (senders, gchar *, i);
+
+ contact = tp_handle_lookup (contact_repo, sender, NULL, NULL);
+ if (contact == 0)
+ {
+ DEBUG ("unknown sender: %s", sender);
+ return;
+ }
+
+ old_dbus_tubes = g_hash_table_new (g_direct_hash, g_direct_equal);
+ add_data.old_dbus_tubes = old_dbus_tubes;
+ add_data.contact = contact;
+ g_hash_table_foreach (priv->tubes, add_in_old_dbus_tubes, &add_data);
+
+ /* contact left the muc so he left all its tubes */
+ emit_data.contact = contact;
+ emit_data.self = self;
+ g_hash_table_foreach (old_dbus_tubes, emit_d_bus_names_changed_foreach,
+ &emit_data);
+
+ g_hash_table_unref (old_dbus_tubes);
+ }
+}
+
+static void
+copy_tube_in_ptr_array (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ SalutTubeIface *tube = (SalutTubeIface *) value;
+ guint tube_id = GPOINTER_TO_UINT(key);
+ TpHandle initiator;
+ gchar *service;
+ GHashTable *parameters;
+ TpTubeChannelState state;
+ TpTubeType type;
+ GPtrArray *array = (GPtrArray *) user_data;
+ GValue entry = {0,};
+
+ g_object_get (tube,
+ "type", &type,
+ "initiator-handle", &initiator,
+ "service", &service,
+ "parameters", &parameters,
+ "state", &state,
+ NULL);
+
+ g_value_init (&entry, SALUT_CHANNEL_TUBE_TYPE);
+ g_value_take_boxed (&entry,
+ dbus_g_type_specialized_construct (SALUT_CHANNEL_TUBE_TYPE));
+ dbus_g_type_struct_set (&entry,
+ 0, tube_id,
+ 1, initiator,
+ 2, type,
+ 3, service,
+ 4, parameters,
+ 5, state,
+ G_MAXUINT);
+
+ g_ptr_array_add (array, g_value_get_boxed (&entry));
+ g_free (service);
+ g_hash_table_unref (parameters);
+}
+
+static GPtrArray *
+make_tubes_ptr_array (SalutTubesChannel *self,
+ GHashTable *tubes)
+{
+ GPtrArray *ret;
+
+ ret = g_ptr_array_sized_new (g_hash_table_size (tubes));
+
+ g_hash_table_foreach (tubes, copy_tube_in_ptr_array, ret);
+
+ return ret;
+}
+
+/**
+ * salut_tubes_channel_list_tubes
+ *
+ * Implements D-Bus method ListTubes
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_list_tubes (TpSvcChannelTypeTubes *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv;
+ GPtrArray *ret;
+ guint i;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ ret = make_tubes_ptr_array (self, priv->tubes);
+ tp_svc_channel_type_tubes_return_from_list_tubes (context, ret);
+
+ for (i = 0; i < ret->len; i++)
+ g_boxed_free (SALUT_CHANNEL_TUBE_TYPE, ret->pdata[i]);
+
+ g_ptr_array_unref (ret);
+}
+
+static void
+tube_closed_cb (SalutTubeIface *tube,
+ gpointer user_data)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (user_data);
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ guint tube_id;
+ TpChannelManager *mgr;
+
+ if (priv->closed)
+ return;
+
+ g_object_get (tube, "id", &tube_id, NULL);
+ if (!g_hash_table_remove (priv->tubes, GUINT_TO_POINTER (tube_id)))
+ {
+ DEBUG ("Can't find tube having this id: %d", tube_id);
+ }
+
+ DEBUG ("tube %d removed", tube_id);
+
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM && SALUT_IS_TUBE_DBUS (tube))
+ {
+ /* Emit the DBusNamesChanged signal */
+ d_bus_names_changed_removed (self, tube_id, priv->self_handle);
+ }
+
+ update_tubes_info (self);
+
+ tp_svc_channel_type_tubes_emit_tube_closed (self, tube_id);
+ tp_svc_channel_emit_closed (tube);
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ g_object_get (priv->conn, "tubes-manager", &mgr, NULL);
+ }
+ else
+ {
+ g_object_get (priv->conn, "muc-manager", &mgr, NULL);
+ }
+
+ tp_channel_manager_emit_channel_closed_for_object (mgr,
+ TP_EXPORTABLE_CHANNEL (tube));
+
+ g_object_unref (mgr);
+}
+
+static void
+tube_opened_cb (SalutTubeIface *tube,
+ gpointer user_data)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (user_data);
+ guint tube_id;
+ TpTubeType type;
+
+ g_object_get (tube,
+ "id", &tube_id,
+ "type", &type,
+ NULL);
+
+ if (type == TP_TUBE_TYPE_DBUS)
+ {
+ add_yourself_in_dbus_names (self, tube_id);
+ }
+
+ update_tubes_info (self);
+
+ tp_svc_channel_type_tubes_emit_tube_state_changed (self, tube_id,
+ TP_TUBE_STATE_OPEN);
+}
+
+static void
+tube_offered_cb (SalutTubeIface *tube,
+ gpointer user_data)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (user_data);
+ guint tube_id;
+ TpHandle initiator;
+ TpTubeType type;
+ gchar *service;
+ GHashTable *parameters;
+ TpTubeState state;
+
+ g_object_get (tube,
+ "id", &tube_id,
+ "initiator-handle", &initiator,
+ "type", &type,
+ "service", &service,
+ "parameters", &parameters,
+ "state", &state,
+ NULL);
+
+ /* tube has been offered and so can be announced using the old API */
+ tp_svc_channel_type_tubes_emit_new_tube (self,
+ tube_id,
+ initiator,
+ type,
+ service,
+ parameters,
+ state);
+
+ update_tubes_info (self);
+
+ g_free (service);
+ g_hash_table_unref (parameters);
+}
+
+static SalutTubeIface *
+create_new_tube (SalutTubesChannel *self,
+ TpTubeType type,
+ TpHandle initiator,
+ gboolean offered,
+ const gchar *service,
+ GHashTable *parameters,
+ guint tube_id,
+ guint portnum,
+ WockyStanza *iq_req)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ SalutTubeIface *tube;
+ TpTubeChannelState state;
+ GibberMucConnection *muc_connection = NULL;
+
+ if (self->muc != NULL)
+ g_object_get (self->muc, "muc-connection", &muc_connection, NULL);
+
+ switch (type)
+ {
+ case TP_TUBE_TYPE_DBUS:
+ tube = SALUT_TUBE_IFACE (salut_tube_dbus_new (priv->conn, self,
+ priv->handle, priv->handle_type, priv->self_handle, muc_connection,
+ initiator, service, parameters, tube_id));
+ break;
+ case TP_TUBE_TYPE_STREAM:
+ tube = SALUT_TUBE_IFACE (salut_tube_stream_new (priv->conn, self,
+ priv->handle, priv->handle_type,
+ priv->self_handle, initiator, offered, service, parameters,
+ tube_id, portnum, iq_req));
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ DEBUG ("create tube %u", tube_id);
+ g_hash_table_insert (priv->tubes, GUINT_TO_POINTER (tube_id), tube);
+
+ g_object_get (tube, "state", &state, NULL);
+
+ /* The old API doesn't know the "not offered" state, so we have to wait that
+ * the tube is offered before announcing it. */
+ if (state != TP_TUBE_CHANNEL_STATE_NOT_OFFERED)
+ {
+ tp_svc_channel_type_tubes_emit_new_tube (self,
+ tube_id,
+ initiator,
+ type,
+ service,
+ parameters,
+ state);
+ }
+
+ g_signal_connect (tube, "tube-opened", G_CALLBACK (tube_opened_cb), self);
+ g_signal_connect (tube, "tube-closed", G_CALLBACK (tube_closed_cb), self);
+ g_signal_connect (tube, "tube-offered", G_CALLBACK (tube_offered_cb), self);
+
+ if (muc_connection != NULL)
+ g_object_unref (muc_connection);
+
+ return tube;
+}
+
+/* tube_node is a MUC <message> */
+static gboolean
+extract_tube_information (SalutTubesChannel *self,
+ WockyNode *tube_node,
+ TpTubeType *type,
+ TpHandle *initiator_handle,
+ const gchar **service,
+ GHashTable **parameters,
+ guint *tube_id)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ if (type != NULL)
+ {
+ const gchar *_type;
+
+ _type = wocky_node_get_attribute (tube_node, "type");
+
+
+ if (!tp_strdiff (_type, "stream"))
+ {
+ *type = TP_TUBE_TYPE_STREAM;
+ }
+ else if (!tp_strdiff (_type, "dbus"))
+ {
+ *type = TP_TUBE_TYPE_DBUS;
+ }
+ else
+ {
+ DEBUG ("Unknown tube type: %s", _type);
+ return FALSE;
+ }
+ }
+
+ if (initiator_handle != NULL)
+ {
+ const gchar *initiator;
+
+ initiator = wocky_node_get_attribute (tube_node, "initiator");
+
+ if (initiator != NULL)
+ {
+ *initiator_handle = tp_handle_ensure (contact_repo, initiator, NULL,
+ NULL);
+
+ if (*initiator_handle == 0)
+ {
+ DEBUG ("invalid initiator ID %s", initiator);
+ return FALSE;
+ }
+ }
+ else
+ {
+ *initiator_handle = 0;
+ }
+ }
+
+ if (service != NULL)
+ {
+ *service = wocky_node_get_attribute (tube_node, "service");
+ }
+
+ if (parameters != NULL)
+ {
+ WockyNode *node;
+
+ node = wocky_node_get_child (tube_node, "parameters");
+ *parameters = salut_wocky_node_extract_properties (node,
+ "parameter");
+ }
+
+ if (tube_id != NULL)
+ {
+ const gchar *str;
+ gchar *endptr;
+ long int tmp;
+
+ str = wocky_node_get_attribute (tube_node, "id");
+ if (str == NULL)
+ {
+ DEBUG ("no tube id in SI request");
+ return FALSE;
+ }
+
+ tmp = strtol (str, &endptr, 10);
+ if (!endptr || *endptr)
+ {
+ DEBUG ("tube id is not numeric: %s", str);
+ return FALSE;
+ }
+ *tube_id = (int) tmp;
+ }
+
+ return TRUE;
+}
+
+static void
+publish_tube_in_node (SalutTubesChannel *self,
+ WockyNode *node,
+ SalutTubeIface *tube)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ WockyNode *parameters_node;
+ GHashTable *parameters;
+ TpTubeType type;
+ gchar *service, *id_str;
+ guint tube_id;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle initiator_handle;
+
+ g_object_get (G_OBJECT (tube),
+ "type", &type,
+ "initiator-handle", &initiator_handle,
+ "service", &service,
+ "parameters", &parameters,
+ "id", &tube_id,
+ NULL);
+
+ id_str = g_strdup_printf ("%u", tube_id);
+
+ wocky_node_set_attribute (node, "service", service);
+ wocky_node_set_attribute (node, "id", id_str);
+
+ g_free (id_str);
+
+ switch (type)
+ {
+ case TP_TUBE_TYPE_DBUS:
+ {
+ gchar *name, *stream_id;
+
+ g_object_get (G_OBJECT (tube),
+ "dbus-name", &name,
+ "stream-id", &stream_id,
+ NULL);
+
+ wocky_node_set_attribute (node, "type", "dbus");
+ wocky_node_set_attribute (node, "stream-id", stream_id);
+ wocky_node_set_attribute (node, "initiator",
+ tp_handle_inspect (contact_repo, initiator_handle));
+
+ if (name != NULL)
+ wocky_node_set_attribute (node, "dbus-name", name);
+
+ g_free (name);
+ g_free (stream_id);
+
+ }
+ break;
+ case TP_TUBE_TYPE_STREAM:
+ wocky_node_set_attribute (node, "type", "stream");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ parameters_node = wocky_node_add_child (node, "parameters");
+ salut_wocky_node_add_children_from_properties (parameters_node,
+ parameters, "parameter");
+
+ g_free (service);
+ g_hash_table_unref (parameters);
+}
+
+struct _i_hate_g_hash_table_foreach
+{
+ SalutTubesChannel *self;
+ WockyNode *tubes_node;
+};
+
+static void
+publish_tubes_in_node (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ SalutTubeIface *tube = (SalutTubeIface *) value;
+ struct _i_hate_g_hash_table_foreach *data =
+ (struct _i_hate_g_hash_table_foreach *) user_data;
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (
+ data->self);
+ TpTubeChannelState state;
+ WockyNode *tube_node;
+ TpTubeType type;
+ TpHandle initiator;
+
+ if (tube == NULL)
+ return;
+
+ g_object_get (tube,
+ "state", &state,
+ "type", &type,
+ "initiator-handle", &initiator,
+ NULL);
+
+ if (state != TP_TUBE_CHANNEL_STATE_OPEN)
+ return;
+
+ if (type == TP_TUBE_TYPE_STREAM && initiator != priv->self_handle)
+ /* We only announce stream tubes we initiated */
+ return;
+
+ tube_node = wocky_node_add_child (data->tubes_node, "tube");
+ publish_tube_in_node (data->self, tube_node, tube);
+}
+
+static gboolean
+update_tubes_info (SalutTubesChannel *self)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ conn, TP_HANDLE_TYPE_ROOM);
+ WockyStanza *msg;
+ WockyNode *msg_node;
+ WockyNode *node;
+ const gchar *jid;
+ struct _i_hate_g_hash_table_foreach data;
+ GError *error = NULL;
+
+ if (priv->handle_type != TP_HANDLE_TYPE_ROOM)
+ return FALSE;
+
+ /* build the message */
+ jid = tp_handle_inspect (room_repo, priv->handle);
+
+ msg = wocky_stanza_build (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_GROUPCHAT,
+ priv->conn->name, jid,
+ WOCKY_NODE_START, "tubes",
+ WOCKY_NODE_XMLNS, WOCKY_TELEPATHY_NS_TUBES,
+ WOCKY_NODE_END, NULL);
+ msg_node = wocky_stanza_get_top_node (msg);
+
+ node = wocky_node_get_child_ns (msg_node, "tubes",
+ WOCKY_TELEPATHY_NS_TUBES);
+
+ data.self = self;
+ data.tubes_node = node;
+
+ g_hash_table_foreach (priv->tubes, publish_tubes_in_node, &data);
+
+ /* Send it */
+ if (!gibber_muc_connection_send (priv->muc_connection, msg, &error))
+ {
+ g_warning ("%s: sending tubes info failed: %s", G_STRFUNC,
+ error->message);
+ g_error_free (error);
+ g_object_unref (msg);
+ return FALSE;
+ }
+
+ g_object_unref (msg);
+ return TRUE;
+}
+
+/**
+ * salut_tubes_channel_offer_d_bus_tube
+ *
+ * Implements D-Bus method OfferDBusTube
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_offer_d_bus_tube (TpSvcChannelTypeTubes *iface,
+ const gchar *service,
+ GHashTable *parameters,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv;
+ guint tube_id;
+ SalutTubeIface *tube;
+ GError *err = NULL;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM
+ && !tp_handle_set_is_member (TP_GROUP_MIXIN (self->muc)->members,
+ priv->self_handle))
+ {
+ GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Tube channel isn't connected" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ tube_id = generate_tube_id ();
+
+ tube = create_new_tube (self, TP_TUBE_TYPE_DBUS, priv->self_handle,
+ TRUE, service, parameters, tube_id, 0, NULL);
+
+ if (!salut_tube_dbus_offer (SALUT_TUBE_DBUS (tube), &err))
+ {
+ salut_tube_iface_close (tube, TRUE);
+ dbus_g_method_return_error (context, err);
+
+ g_error_free (err);
+ return;
+ }
+
+ tp_svc_channel_type_tubes_return_from_offer_d_bus_tube (context, tube_id);
+}
+
+/**
+ * salut_tubes_channel_accept_d_bus_tube
+ *
+ * Implements D-Bus method AcceptDBusTube
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_accept_d_bus_tube (TpSvcChannelTypeTubes *iface,
+ guint id,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv;
+ SalutTubeIface *tube;
+ TpTubeChannelState state;
+ TpTubeType type;
+ gchar *addr;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (id));
+ if (tube == NULL)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Unknown tube" };
+
+ dbus_g_method_return_error (context, &error);
+
+ return;
+ }
+
+ g_object_get (tube,
+ "type", &type,
+ "state", &state,
+ NULL);
+
+ if (type != TP_TUBE_TYPE_DBUS)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not a D-Bus tube" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ if (state != TP_TUBE_CHANNEL_STATE_LOCAL_PENDING)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not in the local pending state" };
+
+ dbus_g_method_return_error (context, &error);
+
+ return;
+ }
+
+ salut_tube_iface_accept (tube, NULL);
+
+ g_object_get (tube,
+ "dbus-address", &addr,
+ NULL);
+
+ add_yourself_in_dbus_names (self, id);
+
+ tp_svc_channel_type_tubes_return_from_accept_d_bus_tube (context, addr);
+ g_free (addr);
+}
+
+/**
+ * salut_tubes_channel_close_tube
+ *
+ * Implements D-Bus method CloseTube
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_close_tube (TpSvcChannelTypeTubes *iface,
+ guint id,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv;
+ SalutTubeIface *tube;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (id));
+ if (tube == NULL)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Unknown tube" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ salut_tube_iface_close (tube, FALSE);
+
+ tp_svc_channel_type_tubes_return_from_close_tube (context);
+}
+
+/**
+ * salut_tubes_channel_get_d_bus_tube_address
+ *
+ * Implements D-Bus method GetDBusTubeAddress
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_get_d_bus_tube_address (TpSvcChannelTypeTubes *iface,
+ guint id,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv;
+ SalutTubeIface *tube;
+ gchar *addr;
+ TpTubeType type;
+ TpTubeChannelState state;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (id));
+
+ if (tube == NULL)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Unknown tube" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ g_object_get (tube,
+ "type", &type,
+ "state", &state,
+ NULL);
+
+ if (type != TP_TUBE_TYPE_DBUS)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not a D-Bus tube" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ if (state != TP_TUBE_CHANNEL_STATE_OPEN)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Tube is not open" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ g_object_get (tube, "dbus-address", &addr, NULL);
+ tp_svc_channel_type_tubes_return_from_get_d_bus_tube_address (context,
+ addr);
+ g_free (addr);
+}
+
+static void
+get_d_bus_names_foreach (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GPtrArray *ret = user_data;
+ GValue tmp = {0,};
+
+ g_value_init (&tmp, DBUS_NAME_PAIR_TYPE);
+ g_value_take_boxed (&tmp,
+ dbus_g_type_specialized_construct (DBUS_NAME_PAIR_TYPE));
+ dbus_g_type_struct_set (&tmp,
+ 0, key,
+ 1, value,
+ G_MAXUINT);
+ g_ptr_array_add (ret, g_value_get_boxed (&tmp));
+}
+
+/**
+ * salut_tubes_channel_get_d_bus_names
+ *
+ * Implements D-Bus method GetDBusNames
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_get_d_bus_names (TpSvcChannelTypeTubes *iface,
+ guint id,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ SalutTubeIface *tube;
+ GHashTable *names;
+ GPtrArray *ret;
+ TpTubeType type;
+ TpTubeChannelState state;
+ guint i;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (id));
+
+ if (tube == NULL)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Unknown tube" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ g_object_get (tube,
+ "type", &type,
+ "state", &state,
+ NULL);
+
+ if (type != TP_TUBE_TYPE_DBUS)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not a D-Bus tube" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ if (state != TP_TUBE_CHANNEL_STATE_OPEN)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Tube is not open" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ g_object_get (tube, "dbus-names", &names, NULL);
+ g_assert (names);
+
+ ret = g_ptr_array_sized_new (g_hash_table_size (names));
+ g_hash_table_foreach (names, get_d_bus_names_foreach, ret);
+
+ tp_svc_channel_type_tubes_return_from_get_d_bus_names (context, ret);
+
+ for (i = 0; i < ret->len; i++)
+ g_boxed_free (DBUS_NAME_PAIR_TYPE, ret->pdata[i]);
+ g_hash_table_unref (names);
+ g_ptr_array_unref (ret);
+}
+
+static void
+stream_tube_new_connection_cb (SalutTubeIface *tube,
+ guint contact,
+ gpointer user_data)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (user_data);
+ guint tube_id;
+ TpTubeType type;
+
+ g_object_get (tube,
+ "id", &tube_id,
+ "type", &type,
+ NULL);
+
+ g_assert (type == TP_TUBE_TYPE_STREAM);
+
+ tp_svc_channel_type_tubes_emit_stream_tube_new_connection (self,
+ tube_id, contact);
+}
+
+static void
+iq_reply_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ SalutTubeIface *tube = (SalutTubeIface *) user_data;
+ WockyStanzaSubType sub_type;
+ WockyStanza *reply_stanza;
+ GError *error = NULL;
+
+ reply_stanza = wocky_porter_send_iq_finish (porter, result, &error);
+
+ if (reply_stanza == NULL)
+ {
+ DEBUG ("Failed to send IQ: %s", error->message);
+ salut_tube_iface_close (tube, TRUE);
+ g_clear_error (&error);
+ return;
+ }
+
+ wocky_stanza_get_type_info (reply_stanza, NULL, &sub_type);
+ if (sub_type != WOCKY_STANZA_SUB_TYPE_RESULT)
+ {
+ DEBUG ("The contact has declined our tube offer");
+ salut_tube_iface_close (tube, TRUE);
+ return;
+ }
+
+ salut_tube_iface_accepted (tube);
+
+ DEBUG ("The contact has accepted our tube offer");
+}
+
+static void
+send_channel_iq_tube (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ SalutTubesChannel *self = (SalutTubesChannel *) user_data;
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ SalutTubeIface *tube = (SalutTubeIface *) value;
+ guint tube_id = GPOINTER_TO_UINT (key);
+ TpHandle initiator;
+ gchar *service;
+ GHashTable *parameters;
+ TpTubeChannelState state;
+ TpTubeType type;
+
+ g_object_get (tube,
+ "type", &type,
+ "initiator-handle", &initiator,
+ "service", &service,
+ "parameters", &parameters,
+ "state", &state,
+ NULL);
+
+ if (state != TP_TUBE_CHANNEL_STATE_NOT_OFFERED &&
+ salut_tube_iface_offer_needed (tube))
+ {
+ WockyNode *parameters_node;
+ const char *tube_type_str;
+ WockyStanza *stanza;
+ WockyNode *top_node;
+ const gchar *jid_from;
+ TpHandleRepoIface *contact_repo;
+ gchar *tube_id_str;
+ int port;
+ gchar *port_str;
+
+ DEBUG ("Listening for connections from the remote contact "
+ "and sending the tube offer stanza");
+
+ /* listen for future connections from the remote CM before sending the
+ * iq */
+ port = salut_tube_iface_listen (tube);
+ g_assert (port > 0);
+
+ contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ jid_from = tp_handle_inspect (contact_repo, priv->self_handle);
+
+ switch (type)
+ {
+ case TP_TUBE_TYPE_DBUS:
+ tube_type_str = "dbus";
+ break;
+
+ case TP_TUBE_TYPE_STREAM:
+ tube_type_str = "stream";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ port_str = g_strdup_printf ("%d", port);
+ tube_id_str = g_strdup_printf ("%d", tube_id);
+
+ stanza = wocky_stanza_build_to_contact (WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_SET,
+ jid_from, WOCKY_CONTACT (priv->contact),
+ WOCKY_NODE_START, "tube",
+ WOCKY_NODE_XMLNS, WOCKY_TELEPATHY_NS_TUBES,
+ WOCKY_NODE_ATTRIBUTE, "type", tube_type_str,
+ WOCKY_NODE_ATTRIBUTE, "service", service,
+ WOCKY_NODE_ATTRIBUTE, "id", tube_id_str,
+ WOCKY_NODE_START, "transport",
+ WOCKY_NODE_ATTRIBUTE, "port", port_str,
+ WOCKY_NODE_END,
+ WOCKY_NODE_END,
+ NULL);
+ top_node = wocky_stanza_get_top_node (stanza);
+
+ parameters_node = wocky_node_add_child (
+ wocky_node_get_child (top_node, "tube"), "parameters");
+ salut_wocky_node_add_children_from_properties (parameters_node,
+ parameters, "parameter");
+
+ wocky_porter_send_iq_async (priv->conn->porter, stanza,
+ NULL, iq_reply_cb, tube);
+
+ g_object_unref (stanza);
+ g_free (tube_id_str);
+ g_free (port_str);
+ }
+
+ g_free (service);
+ g_hash_table_unref (parameters);
+}
+
+/**
+ * Send iq offer(s) for all tubes in this channel to the remote contact in iq
+ * stanza(s). If the XmppConnection is not established, try to establish it,
+ * and the offer will be sent later asynchronously.
+ *
+ * Iq stanzas are sent only when the tube has not been offered yet. It asks
+ * each tube whether it is really needed with salut_tube_iface_offer_needed()
+ *
+ * This is called when the client offer the tube, either via the old
+ * Channel.Type.Tubes interface, or the new Channel.Type.{Stream,DBus}Tube
+ * interface. This is also called when the XmppConnection is established in
+ * case a tube was offered while the XmppConnection was not established.
+ */
+void
+salut_tubes_channel_send_iq_offer (SalutTubesChannel *self)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->state != CHANNEL_CONNECTED)
+ {
+ /* TODO: do not connect if nothing to send... */
+ setup_connection (self);
+ return;
+ }
+
+ g_hash_table_foreach (priv->tubes, send_channel_iq_tube, self);
+}
+
+
+/**
+ * salut_tubes_channel_offer_stream_tube
+ *
+ * Implements D-Bus method OfferStreamTube
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_offer_stream_tube (TpSvcChannelTypeTubes *iface,
+ const gchar *service,
+ GHashTable *parameters,
+ guint address_type,
+ const GValue *address,
+ guint access_control,
+ const GValue *access_control_param,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv;
+ guint tube_id;
+ SalutTubeIface *tube;
+ GError *error = NULL;
+
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->handle_type == TP_HANDLE_TYPE_ROOM
+ && !tp_handle_set_is_member (TP_GROUP_MIXIN (self->muc)->members,
+ priv->self_handle))
+ {
+ GError err = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Tube channel isn't connected" };
+
+ dbus_g_method_return_error (context, &err);
+ return;
+ }
+
+ if (!salut_tube_stream_check_params (address_type, address,
+ access_control, access_control_param, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tube_id = generate_tube_id ();
+
+ tube = create_new_tube (self, TP_TUBE_TYPE_STREAM, priv->self_handle,
+ TRUE, service, parameters, tube_id, 0, NULL);
+
+ g_object_set (tube,
+ "address-type", address_type,
+ "address", address,
+ "access-control", access_control,
+ "access-control-param", access_control_param,
+ NULL);
+
+ if (!salut_tube_stream_offer (SALUT_TUBE_STREAM (tube), &error))
+ {
+ salut_tube_iface_close (tube, TRUE);
+
+ dbus_g_method_return_error (context, error);
+ return;
+ }
+
+ g_signal_connect (tube, "tube-new-connection",
+ G_CALLBACK (stream_tube_new_connection_cb), self);
+
+ tp_svc_channel_type_tubes_return_from_offer_stream_tube (context,
+ tube_id);
+}
+
+/**
+ * salut_tubes_channel_accept_stream_tube
+ *
+ * Implements D-Bus method AcceptStreamTube
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_accept_stream_tube (TpSvcChannelTypeTubes *iface,
+ guint id,
+ guint address_type,
+ guint access_control,
+ const GValue *access_control_param,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv;
+ SalutTubeIface *tube;
+ TpTubeChannelState state;
+ TpTubeType type;
+ GValue *address;
+ GError *error = NULL;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (id));
+ if (tube == NULL)
+ {
+ GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Unknown tube" };
+
+ dbus_g_method_return_error (context, &err);
+ return;
+ }
+
+ if (address_type != TP_SOCKET_ADDRESS_TYPE_UNIX &&
+ address_type != TP_SOCKET_ADDRESS_TYPE_IPV4 &&
+ address_type != TP_SOCKET_ADDRESS_TYPE_IPV6)
+ {
+ GError *err = NULL;
+
+ err = g_error_new (TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Address type %d not implemented", address_type);
+
+ dbus_g_method_return_error (context, err);
+
+ g_error_free (err);
+ return;
+ }
+
+ if (access_control != TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
+ {
+ GError *err = NULL;
+
+ err = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unix sockets only support localhost control access");
+
+ dbus_g_method_return_error (context, err);
+
+ g_error_free (err);
+ return;
+ }
+
+ g_object_get (tube,
+ "type", &type,
+ "state", &state,
+ NULL);
+
+ if (type != TP_TUBE_TYPE_STREAM)
+ {
+ GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not a stream tube" };
+
+ dbus_g_method_return_error (context, &err);
+ return;
+ }
+
+ if (state != TP_TUBE_CHANNEL_STATE_LOCAL_PENDING)
+ {
+ GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not in the local pending state" };
+
+ dbus_g_method_return_error (context, &err);
+ return;
+ }
+
+ g_object_set (tube,
+ "address-type", address_type,
+ "access-control", access_control,
+ "access-control-param", access_control_param,
+ NULL);
+
+ if (!salut_tube_iface_accept (tube, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ return;
+ }
+
+ g_object_get (tube, "address", &address, NULL);
+
+ tp_svc_channel_type_tubes_return_from_accept_stream_tube (context,
+ address);
+}
+
+/**
+ * salut_tubes_channel_get_stream_tube_socket_address
+ *
+ * Implements D-Bus method GetStreamTubeSocketAddress
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_get_stream_tube_socket_address (TpSvcChannelTypeTubes *iface,
+ guint id,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ SalutTubeIface *tube;
+ TpTubeType type;
+ TpTubeChannelState state;
+ GValue *address;
+ TpSocketAddressType address_type;
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (id));
+ if (tube == NULL)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Unknown tube" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ g_object_get (tube,
+ "type", &type,
+ "state", &state,
+ NULL);
+
+ if (type != TP_TUBE_TYPE_STREAM)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not a Stream tube" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ if (state != TP_TUBE_CHANNEL_STATE_OPEN)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Tube is not open" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ g_object_get (tube,
+ "address", &address,
+ "address-type", &address_type,
+ NULL);
+
+ tp_svc_channel_type_tubes_return_from_get_stream_tube_socket_address (
+ context, address_type, address);
+}
+
+/**
+ * salut_tubes_channel_get_available_stream_tube_types
+ *
+ * Implements D-Bus method GetAvailableStreamTubeTypes
+ * on org.freedesktop.Telepathy.Channel.Type.Tubes
+ */
+static void
+salut_tubes_channel_get_available_stream_tube_types (
+ TpSvcChannelTypeTubes *iface,
+ DBusGMethodInvocation *context)
+{
+ GHashTable *ret;
+
+ ret = salut_tube_stream_get_supported_socket_types ();
+
+ tp_svc_channel_type_tubes_return_from_get_available_stream_tube_types (
+ context, ret);
+
+ g_hash_table_unref (ret);
+}
+
+static void salut_tubes_channel_dispose (GObject *object);
+static void salut_tubes_channel_finalize (GObject *object);
+
+static void
+salut_tubes_channel_class_init (
+ SalutTubesChannelClass *salut_tubes_channel_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_tubes_channel_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "TargetID", "target-id", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (salut_tubes_channel_class,
+ sizeof (SalutTubesChannelPrivate));
+
+ object_class->constructor = salut_tubes_channel_constructor;
+
+ object_class->dispose = salut_tubes_channel_dispose;
+ object_class->finalize = salut_tubes_channel_finalize;
+
+ object_class->get_property = salut_tubes_channel_get_property;
+ object_class->set_property = salut_tubes_channel_set_property;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_string ("target-id", "Target JID",
+ "The string obtained by inspecting this channel's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact which caused the Tubes channel to appear",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator JID",
+ "The string obtained by inspecting this channel's initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "Salut Connection that owns the connection for this tubes channel",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_object (
+ "muc",
+ "SalutMucChannel object",
+ "Salut text MUC channel corresponding to this Tubes channel object, "
+ "if the handle type is ROOM.",
+ SALUT_TYPE_MUC_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MUC, param_spec);
+
+ param_spec = g_param_spec_object (
+ "contact",
+ "SalutContact object",
+ "Salut Contact to which this channel is dedicated in case of 1-1 tube",
+ SALUT_TYPE_CONTACT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ salut_tubes_channel_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutTubesChannelClass, dbus_props_class));
+
+ tp_external_group_mixin_init_dbus_properties (object_class);
+}
+
+void
+salut_tubes_channel_dispose (GObject *object)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (object);
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, priv->handle_type);
+
+ if (priv->dispose_has_run)
+ return;
+
+ if (priv->muc_connection != NULL)
+ {
+ g_signal_handlers_disconnect_matched (priv->muc_connection,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+
+ g_object_unref (priv->muc_connection);
+ priv->muc_connection = NULL;
+ }
+
+ if (priv->contact != NULL)
+ {
+ g_object_unref (priv->contact);
+ priv->contact = NULL;
+ }
+
+ priv->dispose_has_run = TRUE;
+
+ tp_handle_unref (handle_repo, priv->handle);
+
+ if (self->muc != NULL)
+ tp_external_group_mixin_finalize (object);
+
+ salut_tubes_channel_close (self);
+
+ if (G_OBJECT_CLASS (salut_tubes_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_tubes_channel_parent_class)->dispose (object);
+}
+
+static void
+salut_tubes_channel_finalize (GObject *object)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (object);
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ g_free (priv->object_path);
+
+ G_OBJECT_CLASS (salut_tubes_channel_parent_class)->finalize (object);
+}
+
+static void
+emit_tube_closed_signal (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ guint id = GPOINTER_TO_UINT (key);
+ SalutTubesChannel *self = (SalutTubesChannel *) user_data;
+
+ tp_svc_channel_type_tubes_emit_tube_closed (self, id);
+}
+
+void
+salut_tubes_channel_foreach (SalutTubesChannel *self,
+ TpExportableChannelFunc foreach,
+ gpointer user_data)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, priv->tubes);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ foreach (TP_EXPORTABLE_CHANNEL (value), user_data);
+ }
+}
+
+void
+salut_tubes_channel_close (SalutTubesChannel *self)
+{
+ SalutTubesChannelPrivate *priv;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->closed)
+ {
+ return;
+ }
+
+ priv->closed = TRUE;
+
+ g_hash_table_foreach (priv->tubes, emit_tube_closed_signal, self);
+ g_hash_table_unref (priv->tubes);
+
+ priv->tubes = NULL;
+
+ tp_svc_channel_emit_closed (self);
+}
+
+/**
+ * salut_tubes_channel_close_async:
+ *
+ * Implements D-Bus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tubes_channel_close_async (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+
+ salut_tubes_channel_close (self);
+ tp_svc_channel_return_from_close (context);
+}
+
+/**
+ * salut_tubes_channel_get_channel_type
+ *
+ * Tubesplements DBus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tubes_channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TUBES);
+}
+
+
+/**
+ * salut_tubes_channel_get_handle
+ *
+ * Tubesplements DBus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tubes_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+ SalutTubesChannelPrivate *priv;
+
+ g_assert (SALUT_IS_TUBES_CHANNEL (self));
+ priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+
+ tp_svc_channel_return_from_get_handle (context, priv->handle_type,
+ priv->handle);
+}
+
+
+/**
+ * salut_tubes_channel_get_interfaces
+ *
+ * Tubesplements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_tubes_channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutTubesChannel *self = SALUT_TUBES_CHANNEL (iface);
+
+ if (self->muc)
+ {
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_tubes_channel_interfaces);
+ }
+ else
+ {
+ /* only show the NULL */
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_tubes_channel_interfaces + 1);
+ }
+}
+
+/* Called when we receive a SI request,
+ * via salut_muc_manager_handle_si_stream_request
+ */
+void
+salut_tubes_channel_bytestream_offered (SalutTubesChannel *self,
+ GibberBytestreamIface *bytestream,
+ WockyStanza *msg)
+{
+ SalutTubesChannelPrivate *priv = SALUT_TUBES_CHANNEL_GET_PRIVATE (self);
+ WockyNode *node = wocky_stanza_get_top_node (msg);
+ const gchar *stream_id, *tmp;
+ gchar *endptr;
+ WockyNode *si_node, *stream_node;
+ guint tube_id;
+ unsigned long tube_id_tmp;
+ SalutTubeIface *tube;
+ WockyStanzaType type;
+ WockyStanzaSubType sub_type;
+
+ /* Caller is expected to have checked that we have a stream or muc-stream
+ * node with a stream ID and the TUBES profile
+ */
+ wocky_stanza_get_type_info (msg, &type, &sub_type);
+ g_return_if_fail (type == WOCKY_STANZA_TYPE_IQ);
+ g_return_if_fail (sub_type == WOCKY_STANZA_SUB_TYPE_SET);
+
+ si_node = wocky_node_get_child_ns (node, "si",
+ WOCKY_XMPP_NS_SI);
+ g_return_if_fail (si_node != NULL);
+
+ if (priv->handle_type == TP_HANDLE_TYPE_CONTACT)
+ stream_node = wocky_node_get_child_ns (si_node,
+ "stream", WOCKY_TELEPATHY_NS_TUBES);
+ else
+ stream_node = wocky_node_get_child_ns (si_node,
+ "muc-stream", WOCKY_TELEPATHY_NS_TUBES);
+ g_return_if_fail (stream_node != NULL);
+
+ stream_id = wocky_node_get_attribute (si_node, "id");
+ g_return_if_fail (stream_id != NULL);
+
+ tmp = wocky_node_get_attribute (stream_node, "tube");
+ if (tmp == NULL)
+ {
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "<stream> or <muc-stream> has no tube attribute" };
+
+ DEBUG ("%s", e.message);
+ gibber_bytestream_iface_close (bytestream, &e);
+ return;
+ }
+ tube_id_tmp = strtoul (tmp, &endptr, 10);
+ if (!endptr || *endptr || tube_id_tmp > G_MAXUINT32)
+ {
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "<stream> or <muc-stream> tube attribute not numeric or > 2**32" };
+
+ DEBUG ("tube id is not numeric or > 2**32: %s", tmp);
+ gibber_bytestream_iface_close (bytestream, &e);
+ return;
+ }
+ tube_id = (guint) tube_id_tmp;
+
+ tube = g_hash_table_lookup (priv->tubes, GUINT_TO_POINTER (tube_id));
+ if (tube == NULL)
+ {
+ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "<stream> or <muc-stream> tube attribute points to a nonexistent "
+ "tube" };
+
+ DEBUG ("tube %u doesn't exist", tube_id);
+ gibber_bytestream_iface_close (bytestream, &e);
+ return;
+ }
+
+ DEBUG ("received new bytestream request for existing tube: %u", tube_id);
+
+ salut_tube_iface_add_bytestream (tube, bytestream);
+}
+
+static void
+tubes_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelTypeTubesClass *klass = (TpSvcChannelTypeTubesClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_type_tubes_implement_##x (\
+ klass, salut_tubes_channel_##x)
+ IMPLEMENT(get_available_tube_types);
+ IMPLEMENT(list_tubes);
+ IMPLEMENT(close_tube);
+ IMPLEMENT(offer_d_bus_tube);
+ IMPLEMENT(accept_d_bus_tube);
+ IMPLEMENT(get_d_bus_tube_address);
+ IMPLEMENT(get_d_bus_names);
+ IMPLEMENT(offer_stream_tube);
+ IMPLEMENT(accept_stream_tube);
+ IMPLEMENT(get_stream_tube_socket_address);
+ IMPLEMENT(get_available_stream_tube_types);
+#undef IMPLEMENT
+}
+
+static void
+channel_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface;
+
+#define IMPLEMENT(x, suffix) tp_svc_channel_implement_##x (\
+ klass, salut_tubes_channel_##x##suffix)
+ IMPLEMENT(close,_async);
+ IMPLEMENT(get_channel_type,);
+ IMPLEMENT(get_handle,);
+ IMPLEMENT(get_interfaces,);
+#undef IMPLEMENT
+}
diff --git a/salut/src/tubes-channel.h b/salut/src/tubes-channel.h
new file mode 100644
index 000000000..e57ac010c
--- /dev/null
+++ b/salut/src/tubes-channel.h
@@ -0,0 +1,94 @@
+/*
+ * tubes-channel.h - Header for SalutTubesChannel
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * 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.1 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 tubesplied 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_TUBES_CHANNEL_H__
+#define __SALUT_TUBES_CHANNEL_H__
+
+#include <glib-object.h>
+#include <wocky/wocky-stanza.h>
+#include <gibber/gibber-bytestream-iface.h>
+
+#include "muc-channel.h"
+#include "tube-iface.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutTubesChannel SalutTubesChannel;
+typedef struct _SalutTubesChannelClass SalutTubesChannelClass;
+
+struct _SalutTubesChannelClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _SalutTubesChannel {
+ GObject parent;
+
+ SalutMucChannel *muc;
+
+ gpointer priv;
+};
+
+GType salut_tubes_channel_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_TUBES_CHANNEL \
+ (salut_tubes_channel_get_type ())
+#define SALUT_TUBES_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_TUBES_CHANNEL, \
+ SalutTubesChannel))
+#define SALUT_TUBES_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_TUBES_CHANNEL, \
+ SalutTubesChannelClass))
+#define SALUT_IS_TUBES_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_TUBES_CHANNEL))
+#define SALUT_IS_TUBES_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_TUBES_CHANNEL))
+#define SALUT_TUBES_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_TUBES_CHANNEL, \
+ SalutTubesChannelClass))
+
+void salut_tubes_channel_foreach (SalutTubesChannel *self,
+ TpExportableChannelFunc foreach, gpointer user_data);
+
+void salut_tubes_channel_close (SalutTubesChannel *channel);
+
+void salut_tubes_channel_bytestream_offered (SalutTubesChannel *chanel,
+ GibberBytestreamIface *bytestream, WockyStanza *msg);
+
+GPtrArray * salut_tubes_channel_muc_message_received (
+ SalutTubesChannel *channel, const gchar *sender, WockyStanza *stanza);
+
+SalutTubeIface * salut_tubes_channel_message_received (SalutTubesChannel *self,
+ const gchar *service, TpTubeType tube_type, TpHandle initiator_handle,
+ GHashTable *parameters, guint tube_id, guint portnum,
+ WockyStanza *iq_req);
+
+void salut_tubes_channel_message_close_received (SalutTubesChannel *self,
+ TpHandle initiator_handle, guint tube_id);
+
+SalutTubeIface *salut_tubes_channel_tube_request (SalutTubesChannel *self,
+ gpointer request_token, GHashTable *request_properties,
+ gboolean require_new);
+
+void salut_tubes_channel_send_iq_offer (SalutTubesChannel *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_TUBES_CHANNEL_H__*/
diff --git a/salut/src/tubes-manager.c b/salut/src/tubes-manager.c
new file mode 100644
index 000000000..64351ece0
--- /dev/null
+++ b/salut/src/tubes-manager.c
@@ -0,0 +1,1275 @@
+/*
+ * tubes-manager.c - Source for SalutTubesManager
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tubes-manager.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <wocky/wocky-namespaces.h>
+
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/util.h>
+
+#define DEBUG_FLAG DEBUG_TUBES
+
+#include <salut/caps-channel-manager.h>
+
+#include "debug.h"
+#include "extensions/extensions.h"
+#include "connection.h"
+#include "capabilities.h"
+#include "tubes-channel.h"
+#include "muc-manager.h"
+#include "muc-channel.h"
+#include "self.h"
+#include "util.h"
+#include "tube-iface.h"
+#include "tube-stream.h"
+
+
+static SalutTubesChannel *new_tubes_channel (SalutTubesManager *fac,
+ TpHandle handle, TpHandle initiator, gpointer request_token,
+ gboolean requested, GError **error);
+
+static void tubes_channel_closed_cb (SalutTubesChannel *chan,
+ gpointer user_data);
+
+static void salut_tubes_manager_iface_init (gpointer g_iface,
+ gpointer iface_data);
+static void gabble_caps_channel_manager_iface_init (
+ GabbleCapsChannelManagerIface *);
+
+G_DEFINE_TYPE_WITH_CODE (SalutTubesManager,
+ salut_tubes_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ salut_tubes_manager_iface_init);
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_CAPS_CHANNEL_MANAGER,
+ gabble_caps_channel_manager_iface_init))
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_CONTACT_MANAGER,
+ LAST_PROPERTY
+};
+
+typedef struct _SalutTubesManagerPrivate \
+ SalutTubesManagerPrivate;
+struct _SalutTubesManagerPrivate
+{
+ SalutConnection *conn;
+ gulong status_changed_id;
+ guint iq_tube_handler_id;
+ SalutContactManager *contact_manager;
+
+ GHashTable *tubes_channels;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_TUBES_MANAGER_GET_PRIVATE(obj) \
+ ((SalutTubesManagerPrivate *) obj->priv)
+
+static void
+salut_tubes_manager_init (SalutTubesManager *self)
+{
+ SalutTubesManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_TUBES_MANAGER, SalutTubesManagerPrivate);
+
+ self->priv = priv;
+
+ priv->tubes_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+
+ priv->conn = NULL;
+ priv->dispose_has_run = FALSE;
+}
+
+/* similar to the same function in tubes-channel.c but extract
+ * information from a 1-1 <iq> message */
+static gboolean
+extract_tube_information (TpHandleRepoIface *contact_repo,
+ WockyStanza *stanza,
+ gboolean *close_out,
+ TpTubeType *type,
+ TpHandle *initiator_handle,
+ const gchar **service,
+ GHashTable **parameters,
+ guint *tube_id,
+ guint *portnum,
+ GError **error)
+{
+ WockyNode *iq;
+ WockyNode *tube_node, *close_node, *node;
+ gboolean _close;
+
+ iq = wocky_stanza_get_top_node (stanza);
+
+ if (initiator_handle != NULL)
+ {
+ const gchar *from;
+ from = wocky_node_get_attribute (iq, "from");
+ if (from == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "got a message without a from field");
+ return FALSE;
+ }
+ *initiator_handle = tp_handle_ensure (contact_repo, from, NULL,
+ NULL);
+
+ if (*initiator_handle == 0)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "invalid initiator ID %s", from);
+ return FALSE;
+ }
+ }
+
+ tube_node = wocky_node_get_child_ns (iq, "tube",
+ WOCKY_TELEPATHY_NS_TUBES);
+ close_node = wocky_node_get_child_ns (iq, "close",
+ WOCKY_TELEPATHY_NS_TUBES);
+
+ if (tube_node == NULL && close_node == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "The <iq> does not have a <tube> nor a <close>");
+ return FALSE;
+ }
+ if (tube_node != NULL && close_node != NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "The <iq> has both a <tube> nor a <close>");
+ return FALSE;
+ }
+ if (tube_node != NULL)
+ {
+ node = tube_node;
+ }
+ else
+ {
+ node = close_node;
+ }
+
+ _close = (close_node != NULL);
+ if (close_out != NULL)
+ {
+ *close_out = _close;
+ }
+
+ if (tube_id != NULL)
+ {
+ const gchar *str;
+ gchar *endptr;
+ long int tmp;
+
+ str = wocky_node_get_attribute (node, "id");
+ if (str == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "no tube id in tube request");
+ return FALSE;
+ }
+
+ tmp = strtol (str, &endptr, 10);
+ if (!endptr || *endptr)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "tube id is not numeric: %s", str);
+ return FALSE;
+ }
+ *tube_id = (int) tmp;
+ }
+
+ /* next fields are not in the close stanza */
+ if (_close)
+ return TRUE;
+
+ if (type != NULL)
+ {
+ const gchar *tube_type;
+
+ tube_type = wocky_node_get_attribute (tube_node, "type");
+ if (!tp_strdiff (tube_type, "stream"))
+ *type = TP_TUBE_TYPE_STREAM;
+ else if (!tp_strdiff (tube_type, "dbus"))
+ *type = TP_TUBE_TYPE_DBUS;
+ else
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "The <iq><tube> does not have a correct type: '%s'", tube_type);
+ return FALSE;
+ }
+ }
+
+ if (service != NULL)
+ {
+ *service = wocky_node_get_attribute (tube_node, "service");
+ }
+
+ if (parameters != NULL)
+ {
+ WockyNode *parameters_node;
+
+ parameters_node = wocky_node_get_child (tube_node, "parameters");
+ *parameters = salut_wocky_node_extract_properties (parameters_node,
+ "parameter");
+ }
+
+ if (portnum != NULL)
+ {
+ WockyNode *transport_node;
+ const gchar *str;
+ gchar *endptr;
+ long int tmp;
+
+ transport_node = wocky_node_get_child (tube_node, "transport");
+ if (transport_node == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "no transport to connect to in the tube request");
+ return FALSE;
+ }
+
+ str = wocky_node_get_attribute (transport_node, "port");
+ if (str == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "no port to connect to in the tube request");
+ return FALSE;
+ }
+
+ tmp = strtol (str, &endptr, 10);
+ if (!endptr || *endptr)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "port is not numeric: %s", str);
+ return FALSE;
+ }
+ *portnum = (int) tmp;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+iq_tube_request_cb (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutTubesManager *self = SALUT_TUBES_MANAGER (user_data);
+ SalutTubesManagerPrivate *priv = SALUT_TUBES_MANAGER_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ /* tube informations */
+ const gchar *service;
+ TpTubeType tube_type;
+ TpHandle initiator_handle;
+ GHashTable *parameters;
+ guint tube_id;
+ guint portnum = 0;
+ gboolean close_;
+ GError *error = NULL;
+
+ SalutTubesChannel *chan;
+
+ /* after this point, the message is for us, so in all cases we either handle
+ * it or send an error reply */
+
+ if (!extract_tube_information (contact_repo, stanza, &close_, &tube_type,
+ &initiator_handle, &service, &parameters, &tube_id, &portnum,
+ &error))
+ {
+ WockyStanza *reply;
+ GError err = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ error->message };
+
+ reply = wocky_stanza_build_iq_error (stanza, NULL);
+ wocky_stanza_error_to_node (&err, wocky_stanza_get_top_node (reply));
+
+ wocky_porter_send (priv->conn->porter, reply);
+
+ g_error_free (error);
+ g_object_unref (reply);
+ return TRUE;
+ }
+
+ DEBUG ("received a tube request, tube id %d", tube_id);
+
+ chan = g_hash_table_lookup (priv->tubes_channels,
+ GUINT_TO_POINTER (initiator_handle));
+ if (close_)
+ {
+ if (chan != NULL)
+ {
+ salut_tubes_channel_message_close_received (chan, initiator_handle,
+ tube_id);
+ }
+ }
+ else
+ {
+ SalutTubeIface *tube;
+ GHashTable *channels;
+ gboolean tubes_channel_created = FALSE;
+
+ if (chan == NULL)
+ {
+ GError *e = NULL;
+
+ chan = new_tubes_channel (self, initiator_handle, initiator_handle,
+ NULL, FALSE, &e);
+
+ if (chan == NULL)
+ {
+ DEBUG ("couldn't make new tubes channel: %s", e->message);
+ g_error_free (e);
+ g_hash_table_unref (parameters);
+ return TRUE;
+ }
+
+ tubes_channel_created = TRUE;
+ }
+
+ tube = salut_tubes_channel_message_received (chan, service, tube_type,
+ initiator_handle, parameters, tube_id, portnum, stanza);
+
+ if (tube == NULL)
+ {
+ if (tubes_channel_created)
+ {
+ /* Destroy the tubes channel we just created as it's now
+ * useless */
+ g_hash_table_remove (priv->tubes_channels, GUINT_TO_POINTER (
+ initiator_handle));
+ }
+
+ g_hash_table_unref (parameters);
+ return TRUE;
+ }
+
+ /* announce tubes and tube channels */
+ channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+
+ if (tubes_channel_created)
+ g_hash_table_insert (channels, chan, NULL);
+
+ g_hash_table_insert (channels, tube, NULL);
+
+ tp_channel_manager_emit_new_channels (self, channels);
+
+ g_hash_table_unref (parameters);
+ g_hash_table_unref (channels);
+ }
+
+ return TRUE;
+}
+
+static void
+salut_tubes_manager_close_all (SalutTubesManager *self)
+{
+ SalutTubesManagerPrivate *priv = SALUT_TUBES_MANAGER_GET_PRIVATE (self);
+
+ DEBUG ("closing 1-1 tubes tubes channels");
+
+ if (priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (priv->conn,
+ priv->status_changed_id);
+ priv->status_changed_id = 0;
+ }
+
+ if (priv->tubes_channels != NULL)
+ {
+ GHashTable *tmp;
+
+ tmp = priv->tubes_channels;
+ priv->tubes_channels = NULL;
+ g_hash_table_unref (tmp);
+ }
+}
+
+static void
+connection_status_changed_cb (SalutConnection *conn,
+ guint status,
+ guint reason,
+ SalutTubesManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ salut_tubes_manager_close_all (self);
+ break;
+ }
+}
+
+static GObject *
+salut_tubes_manager_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutTubesManager *self;
+ SalutTubesManagerPrivate *priv;
+
+ obj = G_OBJECT_CLASS (salut_tubes_manager_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_TUBES_MANAGER (obj);
+ priv = SALUT_TUBES_MANAGER_GET_PRIVATE (self);
+
+ priv->iq_tube_handler_id = wocky_porter_register_handler_from_anyone (
+ priv->conn->porter, WOCKY_STANZA_TYPE_IQ,
+ WOCKY_STANZA_SUB_TYPE_SET, WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ iq_tube_request_cb, self, NULL);
+
+ priv->status_changed_id = g_signal_connect (priv->conn,
+ "status-changed", (GCallback) connection_status_changed_cb, obj);
+
+ return obj;
+}
+
+static void
+salut_tubes_manager_dispose (GObject *object)
+{
+ SalutTubesManager *fac = SALUT_TUBES_MANAGER (object);
+ SalutTubesManagerPrivate *priv =
+ SALUT_TUBES_MANAGER_GET_PRIVATE (fac);
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("dispose called");
+ priv->dispose_has_run = TRUE;
+
+ salut_tubes_manager_close_all (fac);
+
+ wocky_porter_unregister_handler (priv->conn->porter,
+ priv->iq_tube_handler_id);
+ priv->iq_tube_handler_id = 0;
+
+ if (priv->contact_manager != NULL)
+ {
+ g_object_unref (priv->contact_manager);
+ priv->contact_manager = NULL;
+ }
+
+ if (G_OBJECT_CLASS (salut_tubes_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_tubes_manager_parent_class)->dispose (
+ object);
+}
+
+static void
+salut_tubes_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutTubesManager *fac = SALUT_TUBES_MANAGER (object);
+ SalutTubesManagerPrivate *priv =
+ SALUT_TUBES_MANAGER_GET_PRIVATE (fac);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ case PROP_CONTACT_MANAGER:
+ g_value_set_object (value, priv->contact_manager);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_tubes_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutTubesManager *fac = SALUT_TUBES_MANAGER (object);
+ SalutTubesManagerPrivate *priv =
+ SALUT_TUBES_MANAGER_GET_PRIVATE (fac);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ case PROP_CONTACT_MANAGER:
+ priv->contact_manager = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_tubes_manager_class_init (
+ SalutTubesManagerClass *salut_tubes_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (
+ salut_tubes_manager_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_tubes_manager_class,
+ sizeof (SalutTubesManagerPrivate));
+
+ object_class->constructor = salut_tubes_manager_constructor;
+ object_class->dispose = salut_tubes_manager_dispose;
+
+ object_class->get_property = salut_tubes_manager_get_property;
+ object_class->set_property = salut_tubes_manager_set_property;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "Salut connection object that owns this Tubes channel factory object.",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_object (
+ "contact-manager",
+ "SalutContactManager object",
+ "Salut Contact Manager associated with the Salut Connection of this "
+ "manager",
+ SALUT_TYPE_CONTACT_MANAGER,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTACT_MANAGER,
+ param_spec);
+}
+
+
+/**
+ * tubes_channel_closed_cb:
+ *
+ * Signal callback for when a Tubes channel is closed. Removes the references
+ * that TubesManager holds to them.
+ */
+static void
+tubes_channel_closed_cb (SalutTubesChannel *chan,
+ gpointer user_data)
+{
+ SalutTubesManager *conn = SALUT_TUBES_MANAGER (user_data);
+ SalutTubesManagerPrivate *priv =
+ SALUT_TUBES_MANAGER_GET_PRIVATE (conn);
+ TpHandle contact_handle;
+
+ if (priv->tubes_channels == NULL)
+ return;
+
+ g_object_get (chan, "handle", &contact_handle, NULL);
+
+ DEBUG ("removing tubes channel with handle %d", contact_handle);
+
+ g_hash_table_remove (priv->tubes_channels, GUINT_TO_POINTER (contact_handle));
+}
+
+/**
+ * new_tubes_channel
+ *
+ * Creates the SalutTubes object associated with the given parameters
+ */
+static SalutTubesChannel *
+new_tubes_channel (SalutTubesManager *fac,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token,
+ gboolean requested,
+ GError **error)
+{
+ SalutTubesManagerPrivate *priv;
+ TpBaseConnection *conn;
+ SalutTubesChannel *chan;
+ char *object_path;
+ SalutContact *contact;
+
+ g_assert (SALUT_IS_TUBES_MANAGER (fac));
+
+ priv = SALUT_TUBES_MANAGER_GET_PRIVATE (fac);
+ conn = (TpBaseConnection *) priv->conn;
+
+ contact = salut_contact_manager_get_contact (priv->contact_manager, handle);
+
+ if (contact == NULL)
+ {
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "%s is not online", tp_handle_inspect (contact_repo, handle));
+ return NULL;
+ }
+
+ object_path = g_strdup_printf ("%s/TubesChannel%u", conn->object_path,
+ handle);
+
+ chan = g_object_new (SALUT_TYPE_TUBES_CHANNEL,
+ "connection", priv->conn,
+ "object-path", object_path,
+ "handle", handle,
+ "handle-type", TP_HANDLE_TYPE_CONTACT,
+ "contact", contact,
+ "initiator-handle", initiator,
+ "requested", requested,
+ NULL);
+
+ DEBUG ("object path %s", object_path);
+
+ g_signal_connect (chan, "closed", G_CALLBACK (tubes_channel_closed_cb), fac);
+
+ g_hash_table_insert (priv->tubes_channels, GUINT_TO_POINTER (handle), chan);
+
+ g_object_unref (contact);
+ g_free (object_path);
+
+ return chan;
+}
+
+static void
+salut_tubes_manager_foreach_channel (TpChannelManager *manager,
+ TpExportableChannelFunc foreach,
+ gpointer user_data)
+{
+ SalutTubesManager *fac = SALUT_TUBES_MANAGER (manager);
+ SalutTubesManagerPrivate *priv =
+ SALUT_TUBES_MANAGER_GET_PRIVATE (fac);
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, priv->tubes_channels);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ TpExportableChannel *chan = TP_EXPORTABLE_CHANNEL (value);
+
+ /* Add channels of type Channel.Type.Tubes */
+ foreach (chan, user_data);
+
+ /* Add channels of type Channel.Type.{Stream|DBus}Tube which live in the
+ * SalutTubesChannel object */
+ salut_tubes_channel_foreach (SALUT_TUBES_CHANNEL (chan), foreach,
+ user_data);
+ }
+}
+
+static const gchar * const tubes_channel_fixed_properties[] = {
+ TP_IFACE_CHANNEL ".ChannelType",
+ TP_IFACE_CHANNEL ".TargetHandleType",
+ NULL
+};
+
+static const gchar * const old_tubes_channel_allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ NULL
+};
+
+static const gchar * const stream_tube_channel_allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ TP_IFACE_CHANNEL ".TargetID",
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service",
+ NULL
+};
+
+/* Temporarily disabled since the implementation is incomplete. */
+#if 0
+static const gchar * const dbus_tube_channel_allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ TP_IFACE_CHANNEL ".TargetID",
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName",
+ NULL
+};
+#endif
+
+static void
+salut_tubes_manager_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table;
+ GValue *value;
+
+ /* 1-1 Channel.Type.Tubes */
+ table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TUBES);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType",
+ value);
+
+ value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
+ value);
+
+ func (type, table, old_tubes_channel_allowed_properties, user_data);
+
+ g_hash_table_unref (table);
+
+ /* 1-1 Channel.Type.StreamTube */
+ table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType",
+ value);
+
+ value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
+ value);
+
+ func (type, table, salut_tube_stream_channel_get_allowed_properties (),
+ user_data);
+
+ g_hash_table_unref (table);
+
+ /* 1-1 Channel.Type.DBusTube */
+ /* Temporarily disabled since the implementation is incomplete. */
+#if 0
+ table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType",
+ value);
+
+ value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
+ value);
+
+ func (type, table, dbus_tube_channel_allowed_properties, user_data);
+
+ g_hash_table_unref (table);
+#endif
+}
+
+static gboolean
+salut_tubes_manager_requestotron (SalutTubesManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ SalutTubesManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SALUT_TYPE_TUBES_MANAGER, SalutTubesManagerPrivate);
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle;
+ GError *error = NULL;
+ const gchar *channel_type;
+ SalutTubesChannel *tubes_channel;
+ const gchar *service = NULL;
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT)
+ return FALSE;
+
+ channel_type = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType");
+
+ if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES) &&
+ /* Temporarily disabled since the implementation is incomplete. */
+ /* tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE) && */
+ tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
+ return FALSE;
+
+ if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES))
+ {
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ tubes_channel_fixed_properties,
+ old_tubes_channel_allowed_properties,
+ &error))
+ goto error;
+ }
+ else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
+ {
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ tubes_channel_fixed_properties,
+ salut_tube_stream_channel_get_allowed_properties (),
+ &error))
+ goto error;
+
+ /* "Service" is a mandatory, not-fixed property */
+ service = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service");
+ if (service == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "StreamTube requests must include '%s'",
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service");
+ goto error;
+ }
+ }
+/* Temporarily disabled since the implementation is incomplete. */
+#if 0
+ else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE))
+ {
+ GError *err = NULL;
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ tubes_channel_fixed_properties,
+ dbus_tube_channel_allowed_properties,
+ &error))
+ goto error;
+
+ /* "ServiceName" is a mandatory, not-fixed property */
+ service = tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName");
+ if (service == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Request missed a mandatory property '%s'",
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName");
+ goto error;
+ }
+
+ if (!tp_dbus_check_valid_bus_name (service, TP_DBUS_NAME_TYPE_WELL_KNOWN,
+ &err))
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid ServiceName: %s", err->message);
+ g_error_free (err);
+ goto error;
+ }
+ }
+#endif
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandle", NULL);
+
+ if (!tp_handle_is_valid (contact_repo, handle, &error))
+ goto error;
+
+ /* Don't support opening a channel to our self handle */
+ if (handle == base_conn->self_handle)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Can't open a channel to your self handle");
+ goto error;
+ }
+
+ tubes_channel = g_hash_table_lookup (priv->tubes_channels,
+ GUINT_TO_POINTER (handle));
+
+ if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES))
+ {
+ if (tubes_channel == NULL)
+ {
+ GSList *tokens = NULL;
+
+ tubes_channel = new_tubes_channel (self, handle,
+ base_conn->self_handle, request_token, TRUE, &error);
+
+ if (tubes_channel == NULL)
+ goto error;
+
+ tokens = g_slist_prepend (tokens, request_token);
+
+ tp_channel_manager_emit_new_channel (self,
+ TP_EXPORTABLE_CHANNEL (tubes_channel), tokens);
+
+ g_slist_free (tokens);
+ return TRUE;
+ }
+
+ if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "A tube channel with contact #%u already exists", handle);
+ DEBUG ("A tube channel with contact #%u already exists", handle);
+ goto error;
+ }
+
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (tubes_channel));
+ return TRUE;
+ }
+ else
+ {
+ SalutTubeIface *new_channel;
+ GSList *tokens = NULL;
+ GHashTable *channels;
+
+ if (tubes_channel == NULL)
+ {
+ tubes_channel = new_tubes_channel (self, handle,
+ base_conn->self_handle, NULL, FALSE, &error);
+ if (tubes_channel == NULL)
+ goto error;
+ }
+
+ new_channel = salut_tubes_channel_tube_request (tubes_channel,
+ request_token, request_properties, require_new);
+ g_assert (new_channel != NULL);
+
+ if (request_token != NULL)
+ tokens = g_slist_prepend (NULL, request_token);
+
+ channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+ g_hash_table_insert (channels, tubes_channel, NULL);
+ g_hash_table_insert (channels, new_channel, tokens);
+
+ tp_channel_manager_emit_new_channels (self, channels);
+
+ g_hash_table_unref (channels);
+ g_slist_free (tokens);
+ return TRUE;
+ }
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+salut_tubes_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutTubesManager *self = SALUT_TUBES_MANAGER (manager);
+
+ return salut_tubes_manager_requestotron (self, request_token,
+ request_properties, TRUE);
+}
+
+static gboolean
+salut_tubes_manager_request_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ SalutTubesManager *self = SALUT_TUBES_MANAGER (manager);
+
+ return salut_tubes_manager_requestotron (self, request_token,
+ request_properties, FALSE);
+}
+
+SalutTubesManager *
+salut_tubes_manager_new (
+ SalutConnection *conn,
+ SalutContactManager *contact_manager)
+{
+ g_return_val_if_fail (SALUT_IS_CONNECTION (conn), NULL);
+ g_return_val_if_fail (SALUT_IS_CONTACT_MANAGER (contact_manager), NULL);
+
+ return g_object_new (
+ SALUT_TYPE_TUBES_MANAGER,
+ "connection", conn,
+ "contact-manager", contact_manager,
+ NULL);
+}
+
+static void
+salut_tubes_manager_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpChannelManagerIface *iface = (TpChannelManagerIface *) g_iface;
+
+ iface->foreach_channel = salut_tubes_manager_foreach_channel;
+ iface->type_foreach_channel_class =
+ salut_tubes_manager_type_foreach_channel_class;
+ iface->create_channel = salut_tubes_manager_create_channel;
+ iface->request_channel = salut_tubes_manager_request_channel;
+}
+
+static void
+add_service_to_array (const gchar *service,
+ GPtrArray *arr,
+ TpTubeType type)
+{
+ GValue monster = {0, };
+ GHashTable *fixed_properties;
+ GValue *channel_type_value;
+ GValue *target_handle_type_value;
+ gchar *tube_allowed_properties[] =
+ {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ NULL
+ };
+
+ g_assert (type == TP_TUBE_TYPE_STREAM || type == TP_TUBE_TYPE_DBUS);
+
+ g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS);
+ g_value_take_boxed (&monster,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS));
+
+ fixed_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ channel_type_value = tp_g_value_slice_new (G_TYPE_STRING);
+ if (type == TP_TUBE_TYPE_STREAM)
+ g_value_set_static_string (channel_type_value,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE);
+ else
+ g_value_set_static_string (channel_type_value,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE);
+ g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType",
+ channel_type_value);
+
+ target_handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (target_handle_type_value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (fixed_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", target_handle_type_value);
+
+ target_handle_type_value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_string (target_handle_type_value, service);
+ if (type == TP_TUBE_TYPE_STREAM)
+ g_hash_table_insert (fixed_properties,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service",
+ target_handle_type_value);
+ else
+ g_hash_table_insert (fixed_properties,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName",
+ target_handle_type_value);
+
+ dbus_g_type_struct_set (&monster,
+ 0, fixed_properties,
+ 1, tube_allowed_properties,
+ G_MAXUINT);
+
+ g_hash_table_unref (fixed_properties);
+
+ g_ptr_array_add (arr, g_value_get_boxed (&monster));
+}
+
+static void
+add_generic_tube_caps (GPtrArray *arr)
+{
+ GValue monster1 = {0,};
+ GHashTable *fixed_properties;
+ GValue *channel_type_value;
+ GValue *target_handle_type_value;
+
+ /* StreamTube */
+ g_value_init (&monster1, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS);
+ g_value_take_boxed (&monster1,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS));
+
+ fixed_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ channel_type_value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (channel_type_value,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE);
+
+ g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType",
+ channel_type_value);
+
+ target_handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (target_handle_type_value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (fixed_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", target_handle_type_value);
+
+ dbus_g_type_struct_set (&monster1,
+ 0, fixed_properties,
+ 1, stream_tube_channel_allowed_properties,
+ G_MAXUINT);
+
+ g_hash_table_unref (fixed_properties);
+ g_ptr_array_add (arr, g_value_get_boxed (&monster1));
+
+ /* FIXME: enable once D-Bus tube new API are implemented */
+#if 0
+ /* DBusTube */
+ g_value_init (&monster2, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS);
+ g_value_take_boxed (&monster2,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS));
+
+ fixed_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ channel_type_value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (channel_type_value,
+ SALUT_IFACE_CHANNEL_TYPE_DBUS_TUBE);
+
+ g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType",
+ channel_type_value);
+
+ target_handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (target_handle_type_value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (fixed_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", target_handle_type_value);
+
+ dbus_g_type_struct_set (&monster2,
+ 0, fixed_properties,
+ 1, gabble_tube_dbus_channel_get_allowed_properties (),
+ G_MAXUINT);
+
+ g_hash_table_unref (fixed_properties);
+ g_ptr_array_add (arr, g_value_get_boxed (&monster2));
+#endif
+}
+
+#define STREAM_CAP_PREFIX (WOCKY_TELEPATHY_NS_TUBES "/stream#")
+#define DBUS_CAP_PREFIX (WOCKY_TELEPATHY_NS_TUBES "/dbus#")
+
+typedef struct {
+ gboolean supports_tubes;
+ GPtrArray *arr;
+ TpHandle handle;
+} GetContactCapsClosure;
+
+static void
+get_contact_caps_foreach (gpointer data,
+ gpointer user_data)
+{
+ const gchar *ns = data;
+ GetContactCapsClosure *closure = user_data;
+
+ if (!g_str_has_prefix (ns, WOCKY_TELEPATHY_NS_TUBES))
+ return;
+
+ closure->supports_tubes = TRUE;
+
+ if (g_str_has_prefix (ns, STREAM_CAP_PREFIX))
+ add_service_to_array (ns + strlen (STREAM_CAP_PREFIX), closure->arr,
+ TP_TUBE_TYPE_STREAM);
+ else if (g_str_has_prefix (ns, DBUS_CAP_PREFIX))
+ add_service_to_array (ns + strlen (DBUS_CAP_PREFIX), closure->arr,
+ TP_TUBE_TYPE_DBUS);
+}
+
+static void
+salut_tubes_manager_get_contact_caps_from_set (
+ GabbleCapsChannelManager *iface,
+ TpHandle handle,
+ const GabbleCapabilitySet *caps,
+ GPtrArray *arr)
+{
+ SalutTubesManager *self = SALUT_TUBES_MANAGER (iface);
+ SalutTubesManagerPrivate *priv = SALUT_TUBES_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *base = TP_BASE_CONNECTION (priv->conn);
+ GetContactCapsClosure closure = { FALSE, arr, handle };
+
+ /* Always claim that we support tubes. */
+ closure.supports_tubes = (handle == base->self_handle);
+
+ gabble_capability_set_foreach (caps, get_contact_caps_foreach, &closure);
+
+ if (closure.supports_tubes)
+ add_generic_tube_caps (arr);
+}
+
+/* stolen directly from Gabble... */
+static void
+gabble_private_tubes_factory_add_cap (GabbleCapsChannelManager *manager,
+ const gchar *client_name,
+ GHashTable *cap,
+ GabbleCapabilitySet *cap_set)
+{
+ const gchar *channel_type, *service;
+ gchar *ns = NULL;
+
+ channel_type = tp_asv_get_string (cap,
+ TP_IFACE_CHANNEL ".ChannelType");
+
+ /* this channel is not for this factory */
+ if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES) &&
+ tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE) &&
+ tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE))
+ return;
+
+ if (tp_asv_get_uint32 (cap,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT)
+ return;
+
+ if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
+ {
+ service = tp_asv_get_string (cap,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service");
+
+ if (service != NULL)
+ ns = g_strconcat (STREAM_CAP_PREFIX, service, NULL);
+ }
+ else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE))
+ {
+ service = tp_asv_get_string (cap,
+ TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName");
+
+ if (service != NULL)
+ ns = g_strconcat (DBUS_CAP_PREFIX, service, NULL);
+ }
+
+ if (ns != NULL)
+ {
+ DEBUG ("%s: adding capability %s", client_name, ns);
+ gabble_capability_set_add (cap_set, ns);
+ g_free (ns);
+ }
+}
+
+static void
+salut_tubes_manager_represent_client (
+ GabbleCapsChannelManager *iface,
+ const gchar *client_name,
+ const GPtrArray *filters,
+ const gchar * const *cap_tokens G_GNUC_UNUSED,
+ GabbleCapabilitySet *cap_set,
+ GPtrArray *data_forms)
+{
+ guint i;
+
+ for (i = 0; i < filters->len; i++)
+ {
+ gabble_private_tubes_factory_add_cap (iface, client_name,
+ g_ptr_array_index (filters, i), cap_set);
+ }
+}
+
+static void
+gabble_caps_channel_manager_iface_init (GabbleCapsChannelManagerIface *iface)
+{
+ iface->get_contact_caps = salut_tubes_manager_get_contact_caps_from_set;
+ iface->represent_client = salut_tubes_manager_represent_client;
+}
diff --git a/salut/src/tubes-manager.h b/salut/src/tubes-manager.h
new file mode 100644
index 000000000..1697d9e03
--- /dev/null
+++ b/salut/src/tubes-manager.h
@@ -0,0 +1,71 @@
+/*
+ * tubes-manager.h - Header for SalutTubesManager
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_TUBES_MANAGER_H__
+#define __SALUT_TUBES_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection.h>
+#include "connection.h"
+#include "contact-manager.h"
+#include "tubes-channel.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SalutTubesManagerClass SalutTubesManagerClass;
+typedef struct _SalutTubesManager SalutTubesManager;
+
+struct _SalutTubesManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _SalutTubesManager {
+ GObject parent;
+
+ gpointer priv;
+};
+
+GType salut_tubes_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_TUBES_MANAGER \
+ (salut_tubes_manager_get_type ())
+#define SALUT_TUBES_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_TUBES_MANAGER,\
+ SalutTubesManager))
+#define SALUT_TUBES_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_TUBES_MANAGER,\
+ SalutTubesManagerClass))
+#define SALUT_IS_TUBES_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_TUBES_MANAGER))
+#define SALUT_IS_TUBES_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_TUBES_MANAGER))
+#define SALUT_TUBES_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_TUBES_MANAGER,\
+ SalutTubesManagerClass))
+
+SalutTubesManager * salut_tubes_manager_new (
+ SalutConnection *conn,
+ SalutContactManager *contact_manager);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_TUBES_MANAGER_H__ */
+
diff --git a/salut/src/util.c b/salut/src/util.c
new file mode 100644
index 000000000..39d0e4521
--- /dev/null
+++ b/salut/src/util.c
@@ -0,0 +1,391 @@
+/*
+ * util.c - Code for Salut utility functions
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * Copyright (C) 2006-2007 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "util.h"
+#include <salut/util.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+#include <telepathy-glib/util.h>
+
+#include "contact.h"
+
+#ifdef HAVE_UUID
+# include <uuid.h>
+#endif
+
+struct _xmpp_node_extract_property_data
+{
+ const gchar *prop;
+ GHashTable *properties;
+};
+
+static gboolean
+xmpp_node_extract_property (WockyNode *node,
+ gpointer user_data)
+{
+ struct _xmpp_node_extract_property_data *data =
+ (struct _xmpp_node_extract_property_data *) user_data;
+ const gchar *name;
+ const gchar *type;
+ const gchar *value;
+ GValue *gvalue;
+
+ if (tp_strdiff (node->name, data->prop))
+ return TRUE;
+
+ name = wocky_node_get_attribute (node, "name");
+
+ if (!name)
+ return TRUE;
+
+ type = wocky_node_get_attribute (node, "type");
+ value = node->content;
+
+ if (type == NULL || value == NULL)
+ return TRUE;
+
+ if (!tp_strdiff (type, "bytes"))
+ {
+ GArray *arr;
+ guchar *decoded;
+ gsize len;
+
+ decoded = g_base64_decode (value, &len);
+ if (!decoded)
+ return TRUE;
+
+ arr = g_array_new (FALSE, FALSE, sizeof (guchar));
+ g_array_append_vals (arr, decoded, len);
+ gvalue = tp_g_value_slice_new (DBUS_TYPE_G_UCHAR_ARRAY);
+ g_value_take_boxed (gvalue, arr);
+ g_hash_table_insert (data->properties, g_strdup (name), gvalue);
+ g_free (decoded);
+ }
+ else if (!tp_strdiff (type, "str"))
+ {
+ gvalue = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_string (gvalue, value);
+ g_hash_table_insert (data->properties, g_strdup (name), gvalue);
+ }
+ else if (!tp_strdiff (type, "int"))
+ {
+ gvalue = tp_g_value_slice_new (G_TYPE_INT);
+ g_value_set_int (gvalue, strtol (value, NULL, 10));
+ g_hash_table_insert (data->properties, g_strdup (name), gvalue);
+ }
+ else if (!tp_strdiff (type, "uint"))
+ {
+ gvalue = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (gvalue, strtoul (value, NULL, 10));
+ g_hash_table_insert (data->properties, g_strdup (name), gvalue);
+ }
+ else if (!tp_strdiff (type, "bool"))
+ {
+ gboolean val;
+
+ if (!tp_strdiff (value, "0"))
+ {
+ val = FALSE;
+ }
+ else if (!tp_strdiff (value, "1"))
+ {
+ val = TRUE;
+ }
+ else
+ {
+ g_debug ("invalid boolean value: %s", value);
+ return TRUE;
+ }
+
+ gvalue = tp_g_value_slice_new (G_TYPE_BOOLEAN);
+ g_value_set_boolean (gvalue, val);
+ g_hash_table_insert (data->properties, g_strdup (name), gvalue);
+ }
+
+ return TRUE;
+}
+
+/**
+ * salut_wocky_node_extract_properties
+ *
+ * Map a XML node to a properties hash table
+ *
+ * Example:
+ *
+ * <node>
+ * <prop name="prop1" type="str">prop1_value</prop>
+ * <prop name="prop2" type="uint">7</prop>
+ * </node>
+ *
+ * salut_wocky_node_extract_properties (node, "prop");
+ *
+ * --> { "prop1" : "prop1_value", "prop2" : 7 }
+ *
+ * Returns a hash table mapping names to GValue of the specified type.
+ * Valid types are: str, int, uint, bytes, b.
+ *
+ */
+GHashTable *
+salut_wocky_node_extract_properties (WockyNode *node,
+ const gchar *prop)
+{
+ GHashTable *properties;
+ struct _xmpp_node_extract_property_data data;
+
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ if (node == NULL)
+ return properties;
+
+ data.prop = prop;
+ data.properties = properties;
+
+ wocky_node_each_child (node, xmpp_node_extract_property, &data);
+
+ return properties;
+}
+
+struct _set_child_from_property_data
+{
+ WockyNode *node;
+ const gchar *prop;
+};
+
+static void
+set_child_from_property (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GValue *gvalue = value;
+ struct _set_child_from_property_data *data =
+ (struct _set_child_from_property_data *) user_data;
+ WockyNode *child;
+ const char *type = NULL;
+
+ if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING)
+ {
+ type = "str";
+ }
+ else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT)
+ {
+ type = "int";
+ }
+ else if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT)
+ {
+ type = "uint";
+ }
+ else if (G_VALUE_TYPE (gvalue) == DBUS_TYPE_G_UCHAR_ARRAY)
+ {
+ type = "bytes";
+ }
+ else if (G_VALUE_TYPE (gvalue) == G_TYPE_BOOLEAN)
+ {
+ type = "bool";
+ }
+ else
+ {
+ /* a type we don't know how to handle: ignore it */
+ g_critical ("property with unknown type \"%s\"",
+ g_type_name (G_VALUE_TYPE (gvalue)));
+ return;
+ }
+
+ child = wocky_node_add_child (data->node, data->prop);
+
+ if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING)
+ {
+ wocky_node_set_content (child,
+ g_value_get_string (gvalue));
+ }
+ else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT)
+ {
+ gchar *str;
+
+ str = g_strdup_printf ("%d", g_value_get_int (gvalue));
+ wocky_node_set_content (child, str);
+
+ g_free (str);
+ }
+ else if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT)
+ {
+ gchar *str;
+
+ str = g_strdup_printf ("%u", g_value_get_uint (gvalue));
+ wocky_node_set_content (child, str);
+
+ g_free (str);
+ }
+ else if (G_VALUE_TYPE (gvalue) == DBUS_TYPE_G_UCHAR_ARRAY)
+ {
+ GArray *arr;
+ gchar *str;
+
+ type = "bytes";
+ arr = g_value_get_boxed (gvalue);
+ str = g_base64_encode ((const guchar *) arr->data, arr->len);
+ wocky_node_set_content (child, str);
+
+ g_free (str);
+ }
+ else if (G_VALUE_TYPE (gvalue) == G_TYPE_BOOLEAN)
+ {
+ gboolean val;
+
+ val = g_value_get_boolean (gvalue);
+ if (val)
+ wocky_node_set_content (child, "1");
+ else
+ wocky_node_set_content (child, "0");
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ wocky_node_set_attribute (child, "name", key);
+ wocky_node_set_attribute (child, "type", type);
+}
+
+/**
+ *
+ * wocky_node_set_children_from_properties
+ *
+ * Map a properties hash table to a XML node.
+ *
+ * Example:
+ *
+ * properties = { "prop1" : "prop1_value", "prop2" : 7 }
+ *
+ * salut_wocky_node_add_children_from_properties (node, properties,
+ * "prop");
+ *
+ * --> <node>
+ * <prop name="prop1" type="str">prop1_value</prop>
+ * <prop name="prop2" type="uint">7</prop>
+ * </node>
+ *
+ */
+void
+salut_wocky_node_add_children_from_properties (WockyNode *node,
+ GHashTable *properties,
+ const gchar *prop)
+{
+ struct _set_child_from_property_data data;
+
+ data.node = node;
+ data.prop = prop;
+
+ g_hash_table_foreach (properties, set_child_from_property, &data);
+}
+
+gchar *
+salut_generate_id (void)
+{
+#ifdef HAVE_UUID
+ /* generate random UUIDs */
+ uuid_t uu;
+ gchar *str;
+
+ str = g_new0 (gchar, 37);
+ uuid_generate_random (uu);
+ uuid_unparse_lower (uu, str);
+ return str;
+#else
+ /* generate from the time, a counter, and a random integer */
+ static gulong last = 0;
+ GTimeVal tv;
+
+ g_get_current_time (&tv);
+ return g_strdup_printf ("%lx.%lx/%lx/%x", tv.tv_sec, tv.tv_usec,
+ last++, g_random_int ());
+#endif
+}
+
+static void
+send_stanza_to_contact (WockyPorter *porter,
+ WockyContact *contact,
+ WockyStanza *stanza)
+{
+ WockyStanza *to_send = wocky_stanza_copy (stanza);
+
+ wocky_stanza_set_to_contact (to_send, contact);
+ wocky_porter_send (porter, to_send);
+ g_object_unref (to_send);
+}
+
+void
+salut_send_ll_pep_event (WockySession *session,
+ WockyStanza *stanza)
+{
+ WockyContactFactory *contact_factory;
+ WockyPorter *porter;
+ WockyLLContact *self_contact;
+ GList *contacts, *l;
+ WockyNode *message, *event, *items;
+ const gchar *pep_node;
+ gchar *node;
+
+ g_return_if_fail (WOCKY_IS_SESSION (session));
+ g_return_if_fail (WOCKY_IS_STANZA (stanza));
+
+ message = wocky_stanza_get_top_node (stanza);
+ event = wocky_node_get_first_child (message);
+ items = wocky_node_get_first_child (event);
+
+ pep_node = wocky_node_get_attribute (items, "node");
+
+ if (pep_node == NULL)
+ return;
+
+ node = g_strdup_printf ("%s+notify", pep_node);
+
+ contact_factory = wocky_session_get_contact_factory (session);
+ porter = wocky_session_get_porter (session);
+
+ contacts = wocky_contact_factory_get_ll_contacts (contact_factory);
+
+ for (l = contacts; l != NULL; l = l->next)
+ {
+ SalutContact *contact;
+
+ if (!SALUT_IS_CONTACT (l->data))
+ continue;
+
+ contact = l->data;
+
+ if (gabble_capability_set_has (contact->caps, node))
+ send_stanza_to_contact (porter, WOCKY_CONTACT (contact), stanza);
+ }
+
+ /* now send to self */
+ self_contact = wocky_contact_factory_ensure_ll_contact (contact_factory,
+ wocky_porter_get_full_jid (porter));
+
+ send_stanza_to_contact (porter, WOCKY_CONTACT (self_contact), stanza);
+
+ g_object_unref (self_contact);
+ g_list_free (contacts);
+ g_free (node);
+}
diff --git a/salut/src/util.h b/salut/src/util.h
new file mode 100644
index 000000000..3426e9854
--- /dev/null
+++ b/salut/src/util.h
@@ -0,0 +1,35 @@
+/*
+ * util.h - Headers for Salut utility functions
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * Copyright (C) 2006 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SALUT_UTIL_H__
+#define __SALUT_UTIL_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <wocky/wocky-stanza.h>
+
+/* Mapping a XMPP node with a GHashTable */
+GHashTable *salut_wocky_node_extract_properties (WockyNode *node,
+ const gchar *prop);
+void salut_wocky_node_add_children_from_properties (WockyNode *node,
+ GHashTable *properties, const gchar *prop);
+gchar *salut_generate_id (void);
+
+#endif /* __SALUT_UTIL_H__ */
diff --git a/salut/src/write-mgr-file.c b/salut/src/write-mgr-file.c
new file mode 100644
index 000000000..7ef6e6991
--- /dev/null
+++ b/salut/src/write-mgr-file.c
@@ -0,0 +1,359 @@
+/*
+ * write_mgr_file.c - utility to produce a .manager file, derived from Gabble.
+ * Copyright © 2006-2010 Collabora Ltd.
+ * Copyright © 2006-2010 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-protocol.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "protocol.h"
+
+#define WRITE_STR(prop, key) \
+ { \
+ const gchar *val = tp_asv_get_string (props, prop); \
+ g_assert (val != NULL); \
+ g_key_file_set_string (f, section_name, key, val); \
+ }
+
+static void
+write_parameters (GKeyFile *f, gchar *section_name, TpBaseProtocol *protocol)
+{
+ const TpCMParamSpec *parameters =
+ tp_base_protocol_get_parameters (protocol);
+ const TpCMParamSpec *row;
+
+ for (row = parameters; row->name; row++)
+ {
+ gchar *param_name = g_strdup_printf ("param-%s", row->name);
+ gchar *param_value = g_strdup_printf ("%s%s%s%s", row->dtype,
+ (row->flags & TP_CONN_MGR_PARAM_FLAG_REQUIRED ? " required" : ""),
+ (row->flags & TP_CONN_MGR_PARAM_FLAG_REGISTER ? " register" : ""),
+ (row->flags & TP_CONN_MGR_PARAM_FLAG_SECRET ? " secret" : ""));
+ g_key_file_set_string (f, section_name, param_name, param_value);
+ g_free (param_value);
+ g_free (param_name);
+ }
+
+ for (row = parameters; row->name; row++)
+ {
+ if (row->flags & TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT)
+ {
+ gchar *default_name = g_strdup_printf ("default-%s", row->name);
+
+ switch (row->gtype)
+ {
+ case G_TYPE_STRING:
+ g_key_file_set_string (f, section_name, default_name,
+ row->def);
+ break;
+ case G_TYPE_INT:
+ case G_TYPE_UINT:
+ g_key_file_set_integer (f, section_name, default_name,
+ GPOINTER_TO_INT(row->def));
+ break;
+ case G_TYPE_BOOLEAN:
+ g_key_file_set_boolean (f, section_name, default_name,
+ GPOINTER_TO_INT(row->def) ? 1 : 0);
+ break;
+ default:
+ /* can't be in the case because G_TYPE_STRV is actually a
+ * function */
+ if (row->gtype == G_TYPE_STRV)
+ {
+ g_key_file_set_string_list (f, section_name, default_name,
+ (const gchar **) row->def,
+ g_strv_length ((gchar **) row->def));
+ }
+ }
+ g_free (default_name);
+ }
+ }
+}
+
+static void
+write_rcc_property (GKeyFile *keyfile,
+ const gchar *group_name,
+ const gchar *key,
+ GValue *val)
+{
+ switch (G_VALUE_TYPE (val))
+ {
+ case G_TYPE_BOOLEAN:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_BOOLEAN_AS_STRING, NULL);
+ g_key_file_set_boolean (keyfile, group_name, kf_key,
+ g_value_get_boolean (val));
+ g_free (kf_key);
+ break;
+ }
+
+ case G_TYPE_STRING:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_STRING_AS_STRING, NULL);
+ g_key_file_set_string (keyfile, group_name, kf_key,
+ g_value_get_string (val));
+ g_free (kf_key);
+ break;
+ }
+
+ case G_TYPE_UINT:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_UINT32_AS_STRING, NULL);
+ gchar *kf_val = g_strdup_printf ("%u", g_value_get_uint (val));
+ g_key_file_set_value (keyfile, group_name, kf_key, kf_val);
+ g_free (kf_key);
+ g_free (kf_val);
+ break;
+ }
+
+ /* FIXME: when we depend on Glib 2.26, we can use
+ * g_key_file_set_[u]int64 (g.o #614864). */
+ case G_TYPE_UINT64:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_UINT64_AS_STRING, NULL);
+ gchar *kf_val = g_strdup_printf ("%" G_GUINT64_FORMAT,
+ g_value_get_uint64 (val));
+ g_key_file_set_value (keyfile, group_name, kf_key, kf_val);
+ g_free (kf_key);
+ g_free (kf_val);
+ break;
+ }
+
+ case G_TYPE_INT:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_INT32_AS_STRING, NULL);
+ g_key_file_set_integer (keyfile, group_name, kf_key,
+ g_value_get_int (val));
+ g_free (kf_key);
+ break;
+ }
+
+ case G_TYPE_INT64:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_UINT64_AS_STRING, NULL);
+ gchar *kf_val = g_strdup_printf ("%" G_GINT64_FORMAT,
+ g_value_get_int64 (val));
+ g_key_file_set_value (keyfile, group_name, kf_key, kf_val);
+ g_free (kf_key);
+ g_free (kf_val);
+ break;
+ }
+
+ default:
+ {
+ if (G_VALUE_TYPE (val) == G_TYPE_STRV)
+ {
+ gchar **list = g_value_get_boxed (val);
+ gchar *kf_key = g_strconcat (key, " "
+ DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, NULL);
+ g_key_file_set_string_list (keyfile, group_name,
+ kf_key, (const gchar **) list, g_strv_length (list));
+ g_free (kf_key);
+ break;
+ }
+
+ /* we'd rather crash than forget to write required rcc property */
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static gchar *
+generate_group_name (GHashTable *props)
+{
+ static guint counter = 0;
+ gchar *retval;
+ gchar *chan_type = g_ascii_strdown (tp_asv_get_string (props,
+ TP_PROP_CHANNEL_CHANNEL_TYPE), -1);
+ gchar *chan_type_suffix;
+ gchar *handle_type_name;
+ guint handle_type = tp_asv_get_uint32 (props,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
+
+ g_assert (chan_type != NULL);
+ chan_type_suffix = strrchr (chan_type, '.');
+ g_assert (chan_type_suffix != NULL);
+ chan_type_suffix++;
+
+ switch (handle_type)
+ {
+ case TP_HANDLE_TYPE_CONTACT:
+ handle_type_name = "-1on1";
+ break;
+
+ case TP_HANDLE_TYPE_ROOM:
+ handle_type_name = "-multi";
+ break;
+
+ case TP_HANDLE_TYPE_GROUP:
+ handle_type_name = "-group";
+ break;
+
+ case TP_HANDLE_TYPE_LIST:
+ handle_type_name = "-list";
+ break;
+
+ default:
+ handle_type_name = "";
+ }
+
+ retval = g_strdup_printf ("%s%s-%d", chan_type_suffix, handle_type_name,
+ ++counter);
+
+ g_free (chan_type);
+ return retval;
+}
+
+static void
+write_rccs (GKeyFile *f, const gchar *section_name, GHashTable *props)
+{
+ GPtrArray *rcc_list = tp_asv_get_boxed (props,
+ TP_PROP_PROTOCOL_REQUESTABLE_CHANNEL_CLASSES,
+ TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST);
+ guint i;
+ gchar **group_names = g_new0 (gchar *, rcc_list->len + 1);
+
+ for (i = 0; i < rcc_list->len; i++)
+ {
+ gchar **allowed;
+ gchar *group_name;
+ GHashTable *fixed;
+ GHashTableIter iter;
+ gpointer k, v;
+
+ tp_value_array_unpack (g_ptr_array_index (rcc_list, i), 2,
+ &fixed, &allowed);
+
+ group_name = generate_group_name (fixed);
+
+ g_hash_table_iter_init (&iter, fixed);
+ while (g_hash_table_iter_next (&iter, &k, &v))
+ {
+ const gchar *key = k;
+ GValue *val = v;
+
+ write_rcc_property (f, group_name, key, val);
+ }
+
+ /* takes ownership */
+ group_names[i] = group_name;
+
+ g_key_file_set_string_list (f, group_name, "allowed",
+ (const gchar **) allowed, g_strv_length (allowed));
+ }
+
+ g_key_file_set_string_list (f, section_name, "RequestableChannelClasses",
+ (const gchar **) group_names, rcc_list->len);
+
+ g_strfreev (group_names);
+}
+
+static gchar *
+mgr_file_contents (const char *busname,
+ const char *objpath,
+ GSList *protocols,
+ GError **error)
+{
+ GKeyFile *f = g_key_file_new ();
+ gchar *file_data;
+
+ g_key_file_set_string (f, "ConnectionManager", "BusName", busname);
+ g_key_file_set_string (f, "ConnectionManager", "ObjectPath", objpath);
+
+ /* there are no CM interfaces defined yet, so we cheat */
+ g_key_file_set_string (f, "ConnectionManager", "Interfaces", "");
+
+ while (protocols != NULL)
+ {
+ TpBaseProtocol *protocol = protocols->data;
+ GHashTable *props =
+ tp_base_protocol_get_immutable_properties (protocol);
+ gchar *section_name = g_strdup_printf ("Protocol %s",
+ tp_base_protocol_get_name (protocol));
+ const gchar * const *ifaces = tp_asv_get_strv (props,
+ TP_PROP_PROTOCOL_INTERFACES);
+ const gchar * const *c_ifaces = tp_asv_get_strv (props,
+ TP_PROP_PROTOCOL_CONNECTION_INTERFACES);
+
+ write_parameters (f, section_name, protocol);
+ write_rccs (f, section_name, props);
+
+ g_key_file_set_string_list (f, section_name, "Interfaces",
+ ifaces, g_strv_length ((gchar **) ifaces));
+ g_key_file_set_string_list (f, section_name, "ConnectionInterfaces",
+ c_ifaces, g_strv_length ((gchar **) c_ifaces));
+
+ WRITE_STR (TP_PROP_PROTOCOL_VCARD_FIELD, "VCardField");
+ WRITE_STR (TP_PROP_PROTOCOL_ENGLISH_NAME, "EnglishName");
+ WRITE_STR (TP_PROP_PROTOCOL_ICON, "Icon");
+
+ g_free (section_name);
+ g_hash_table_unref (props);
+ protocols = protocols->next;
+ }
+
+ file_data = g_key_file_to_data (f, NULL, error);
+ g_key_file_free (f);
+ return file_data;
+}
+
+int
+main (void)
+{
+ GError *error = NULL;
+ gchar *s;
+ GSList *protocols = NULL;
+
+ g_type_init ();
+ dbus_g_type_specialized_init ();
+
+ protocols = g_slist_prepend (protocols, salut_protocol_new (G_TYPE_INVALID,
+ NULL,
+ SALUT_PROTOCOL_LOCAL_XMPP_NAME,
+ SALUT_PROTOCOL_LOCAL_XMPP_ENGLISH_NAME,
+ SALUT_PROTOCOL_LOCAL_XMPP_ICON_NAME));
+
+ s = mgr_file_contents (TP_CM_BUS_NAME_BASE "salut",
+ TP_CM_OBJECT_PATH_BASE "salut",
+ protocols, &error);
+
+ g_object_unref (protocols->data);
+ g_slist_free (protocols);
+
+ if (!s)
+ {
+ fprintf (stderr, "%s", error->message);
+ g_error_free (error);
+ return 1;
+ }
+ printf ("%s", s);
+ g_free (s);
+ return 0;
+}
diff --git a/salut/tests/Makefile.am b/salut/tests/Makefile.am
new file mode 100644
index 000000000..7878b139f
--- /dev/null
+++ b/salut/tests/Makefile.am
@@ -0,0 +1,66 @@
+CLEANFILES=
+
+include $(top_srcdir)/rules/check.mak
+
+SUPPRESSIONS=valgrind.supp dlopen.supp
+
+SUBDIRS = twisted
+
+.PHONY: always-run test test-report
+
+# ------------------------------------------------------------------------------
+# telepathy-salut-debug
+
+noinst_PROGRAMS = \
+ telepathy-salut-debug
+
+telepathy_salut_debug_SOURCES = \
+ debug.c
+
+telepathy_salut_debug_LDADD = \
+ $(top_builddir)/src/libsalut-convenience.la \
+ $(top_builddir)/lib/gibber/libgibber.la \
+ $(top_builddir)/extensions/libsalut-extensions.la \
+ -ltelepathy-glib
+
+# Teach it how to make libgibber.la
+$(top_builddir)/lib/gibber/libgibber.la:
+ ${MAKE} -C $(top_builddir)/lib/gibber libgibber.la
+
+.PHONY: $(top_builddir)/lib/gibber/libgibber.la
+
+# ------------------------------------------------------------------------------
+# TESTS
+
+check_PROGRAMS = check-node-properties
+
+AM_CFLAGS = $(ERROR_CFLAGS) @GLIB_CFLAGS@ @LIBXML2_CFLAGS@ @WOCKY_CFLAGS@ \
+ @DBUS_CFLAGS@ @TELEPATHY_GLIB_CFLAGS@ \
+ -I $(top_srcdir) -I $(top_builddir) \
+ -I $(top_srcdir)/lib -I $(top_builddir)/lib \
+ -I $(top_srcdir)/src -I $(top_builddir)/src
+
+AM_LDFLAGS = \
+ @GLIB_LIBS@ @TELEPATHY_GLIB_LIBS@ @LIBSOUP_LIBS@ @WOCKY_LIBS@ @UUID_LIBS@
+
+check_node_properties_LDADD = \
+ $(top_builddir)/src/libsalut-convenience.la \
+ $(top_builddir)/lib/gibber/libgibber.la \
+ $(top_builddir)/extensions/libsalut-extensions.la
+
+test: ${TEST_PROGS}
+ gtester -k --verbose $(check_PROGRAMS)
+
+# ------------------------------------------------------------------------------
+# CODING STYLE
+
+# Coding style checks
+check_c_sources = \
+ $(telepathy_salut_debug_SOURCES) \
+ $(test_xmpp_connection_SOURCES) \
+ $(test_r_multicast_transport_io_SOURCES) \
+ $(check_main_SOURCES)
+
+include $(top_srcdir)/tools/check-coding-style.mk
+
+check-local: check-coding-style test
diff --git a/salut/tests/README b/salut/tests/README
new file mode 100644
index 000000000..099ac6bad
--- /dev/null
+++ b/salut/tests/README
@@ -0,0 +1,62 @@
+== C tests ==
+
+To run all C tests (assuming the current directory is $top_srcdir):
+
+ make -C tests check-TESTS
+
+To run tests under Valgrind:
+
+ make -C tests check-valgrind
+
+== Twisted tests ==
+
+To run tests using the system avahi, configure with --enable-avahi-tests
+
+To run Twisted tests:
+
+ make -C tests/twisted check-twisted
+
+To run an individual Twisted test:
+
+ make -C tests/twisted check-twisted TWISTED_TESTS=avahi/aliases.py
+
+or:
+
+ cd tests/twisted
+ sh tools/with-session-bus.sh --config-file=tools/tmp-session-bus.conf \
+ -- python avahi/aliases.py
+
+To run on the real system avahi instead of the mock avahi, set the
+SALUT_TEST_REAL_AVAHI environment variable to a nonzero value. E.g.
+
+ SALUT_TEST_REAL_AVAHI=1 make -C tests/twisted check-twisted \
+ TWISTED_TESTS=avahi/aliases.py
+
+To run with debug information:
+
+ make -C tests/twisted check-twisted TWISTED_TESTS=avahi/aliases.py \
+ CHECK_TWISTED_VERBOSE=1
+
+or:
+
+ cd tests/twisted
+ sh tools/with-session-bus.sh --config-file=tools/tmp-session-bus.conf \
+ -- python avahi/aliases.py -v
+
+To debug an individual test you can set one of the following env variable:
+
+ * SALUT_TEST_VALGRIND : to run Salut inside valgrind. The report is
+ added to tools/gabble-testing.log.
+ export SALUT_TEST_VALGRIND=1
+
+ * SALUT_TEST_REFDBG : to run Salut inside refdbg. The report is written
+ to tools/refdbg.log. You can change SALUT_WRAPPER to use an alternative
+ refdbg and change REFDBG_OPTIONS to set your own parameters. Example:
+ export SALUT_TEST_REFDBG=1
+ export SALUT_WRAPPER="/path/to/refdbg"
+ export REFDBG_OPTIONS="btnum=16"
+
+ * SALUT_WRAPPER="nemiver" : to run Salut inside the graphical debugger
+ nemiver. You'll be able to set up breakpoints; then hit the "continue"
+ button to launch Salut.
+
diff --git a/salut/tests/causalorderingtest.py b/salut/tests/causalorderingtest.py
new file mode 100644
index 000000000..4b2dc053a
--- /dev/null
+++ b/salut/tests/causalorderingtest.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+from twisted.internet import reactor
+from mesh import Mesh
+from mesh import MeshNode
+import sys
+
+nodes = []
+# We're optimists
+success = True
+
+class TestMeshNode(MeshNode):
+ num = 1
+
+ def newNode (self, data):
+ self.num += 1
+ if len (nodes) == self.num:
+ reactor.callLater(1.5, (lambda y: nodes[0].pushInput("0\n")), x)
+
+
+class TestMesh(Mesh):
+ done = 0
+ expected = None
+
+ def gotOutput(self, node, sender, data):
+ global success
+ value = int(data.rstrip())
+ if (node in self.nodes[0:3]):
+ if (self.nodes.index(node) == (value + 1) % 3):
+ reactor.callLater(0.1,
+ (lambda: node.pushInput( str(value + 1) + "\n")))
+ else:
+ print node.name + " - " + sender + " - " + data.rstrip()
+ if self.expected == None:
+ self.expected = value
+ if self.expected > 2 and self.expected != value:
+ print "Expected: " + str(self.expected) + " But got: " + str(value)
+ success = False
+ reactor.crash()
+ self.expected = value + 1
+
+ if self.expected > 50:
+ reactor.crash()
+
+m = TestMesh()
+
+for x in xrange(0, 3):
+ n = MeshNode("node" + str(x), m)
+ nodes.append(n)
+ m.addMeshNode(n)
+
+n = TestMeshNode("node3", m)
+nodes.append(n)
+m.addMeshNode(n)
+
+
+#connect node 0 and 1 together with dropfree links, and 0 <->2 and 1 <-> 2
+# with quite lossy links
+m.connect_duplex(nodes[0], nodes[1], 100, 0, 0)
+m.connect_duplex(nodes[0], nodes[2], 100, 0, 0)
+
+m.connect_duplex(nodes[1], nodes[2], 100, 0, 0)
+
+m.connect_duplex(nodes[0], nodes[3], 100, 0, 0.50)
+m.connect_duplex(nodes[1], nodes[3], 100, 0, 0.50)
+m.connect_duplex(nodes[2], nodes[3], 100, 0, 0.50)
+
+def timeout():
+ global success
+ print "TIMEOUT!"
+ success = False
+ reactor.crash()
+
+reactor.callLater(60, timeout)
+
+reactor.run()
+
+
+if not success:
+ sys.stderr.write("FAILED\n")
+ sys.exit(-1)
+print "SUCCESS"
diff --git a/salut/tests/check-node-properties.c b/salut/tests/check-node-properties.c
new file mode 100644
index 000000000..755872bb9
--- /dev/null
+++ b/salut/tests/check-node-properties.c
@@ -0,0 +1,260 @@
+/*
+ * check-xmpp-node-properties.c - Test for
+ * salut_wocky_node_extract_properties and
+ * salut_wocky_node_add_children_from_properties
+ * Copyright (C) 2007 Collabora Ltd.
+ * @author Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dbus/dbus-glib.h>
+
+#include <wocky/wocky-stanza.h>
+#include "util.h"
+
+static WockyStanza *
+create_sample_stanza (void)
+{
+ WockyStanza *stanza;
+
+ stanza = wocky_stanza_build (
+ WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
+ "alice@collabora.co.uk", "bob@collabora.co.uk",
+ WOCKY_NODE_START, "properties",
+ WOCKY_NODE_START, "prop",
+ WOCKY_NODE_ATTRIBUTE, "name", "prop1",
+ WOCKY_NODE_ATTRIBUTE, "type", "str",
+ WOCKY_NODE_TEXT, "prop1_value",
+ WOCKY_NODE_END,
+ WOCKY_NODE_START, "prop",
+ WOCKY_NODE_ATTRIBUTE, "name", "prop2",
+ WOCKY_NODE_ATTRIBUTE, "type", "int",
+ WOCKY_NODE_TEXT, "-7",
+ WOCKY_NODE_END,
+ WOCKY_NODE_START, "prop",
+ WOCKY_NODE_ATTRIBUTE, "name", "prop3",
+ WOCKY_NODE_ATTRIBUTE, "type", "uint",
+ WOCKY_NODE_TEXT, "10",
+ WOCKY_NODE_END,
+ WOCKY_NODE_START, "prop",
+ WOCKY_NODE_ATTRIBUTE, "name", "prop4",
+ WOCKY_NODE_ATTRIBUTE, "type", "bytes",
+ WOCKY_NODE_TEXT, "YWJjZGU=",
+ WOCKY_NODE_END,
+ WOCKY_NODE_START, "prop",
+ WOCKY_NODE_ATTRIBUTE, "name", "prop5",
+ WOCKY_NODE_ATTRIBUTE, "type", "bool",
+ WOCKY_NODE_TEXT, "1",
+ WOCKY_NODE_END,
+ WOCKY_NODE_END,
+ NULL);
+
+ return stanza;
+}
+
+static void
+test_extract_properties (void)
+{
+ WockyStanza *stanza;
+ WockyNode *node;
+ GHashTable *properties;
+ GValue *value;
+ const gchar *prop1_value;
+ gint prop2_value;
+ guint prop3_value;
+ GArray *prop4_value;
+ gboolean prop5_value;
+
+ stanza = create_sample_stanza ();
+ node = wocky_node_get_child (wocky_stanza_get_top_node (stanza),
+ "properties");
+
+ g_assert (node != NULL);
+ properties = salut_wocky_node_extract_properties (node, "prop");
+
+ g_assert (properties != NULL);
+ g_assert_cmpuint (g_hash_table_size (properties), ==, 5);
+
+ /* prop1 */
+ value = g_hash_table_lookup (properties, "prop1");
+ g_assert (value != NULL);
+ g_assert (G_VALUE_TYPE (value) == G_TYPE_STRING);
+ prop1_value = g_value_get_string (value);
+ g_assert (prop1_value != NULL);
+ g_assert_cmpstr (prop1_value, ==, "prop1_value");
+
+ /* prop2 */
+ value = g_hash_table_lookup (properties, "prop2");
+ g_assert (value != NULL);
+ g_assert (G_VALUE_TYPE (value) == G_TYPE_INT);
+ prop2_value = g_value_get_int (value);
+ g_assert_cmpuint (prop2_value, ==, -7);
+
+ /* prop3 */
+ value = g_hash_table_lookup (properties, "prop3");
+ g_assert (value != NULL);
+ g_assert (G_VALUE_TYPE (value) == G_TYPE_UINT);
+ prop3_value = g_value_get_uint (value);
+ g_assert_cmpuint (prop3_value, ==, 10);
+
+ /* prop4 */
+ value = g_hash_table_lookup (properties, "prop4");
+ g_assert (value != NULL);
+ g_assert (G_VALUE_TYPE (value) == DBUS_TYPE_G_UCHAR_ARRAY);
+ prop4_value = g_value_get_boxed (value);
+ g_assert (g_array_index (prop4_value, gchar, 0) == 'a');
+ g_assert (g_array_index (prop4_value, gchar, 1) == 'b');
+ g_assert (g_array_index (prop4_value, gchar, 2) == 'c');
+ g_assert (g_array_index (prop4_value, gchar, 3) == 'd');
+ g_assert (g_array_index (prop4_value, gchar, 4) == 'e');
+
+ /* prop 5 */
+ value = g_hash_table_lookup (properties, "prop5");
+ g_assert (value != NULL);
+ g_assert (G_VALUE_TYPE (value) == G_TYPE_BOOLEAN);
+ prop5_value = g_value_get_boolean (value);
+ g_assert (prop5_value == TRUE);
+
+ g_object_unref (stanza);
+ g_hash_table_unref (properties);
+}
+
+static void
+test_g_value_slice_free (GValue *value)
+{
+ g_value_unset (value);
+ g_slice_free (GValue, value);
+}
+
+static GHashTable *
+create_sample_properties (void)
+{
+ GHashTable *properties;
+ GValue *prop1, *prop2, *prop3, *prop4, *prop5;
+ GArray *arr;
+
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) test_g_value_slice_free);
+
+ prop1 = g_slice_new0 (GValue);
+ g_value_init (prop1, G_TYPE_STRING);
+ g_value_set_string (prop1, "prop1_value");
+ g_hash_table_insert (properties, "prop1", prop1);
+
+ prop2 = g_slice_new0 (GValue);
+ g_value_init (prop2, G_TYPE_INT);
+ g_value_set_int (prop2, -7);
+ g_hash_table_insert (properties, "prop2", prop2);
+
+ prop3 = g_slice_new0 (GValue);
+ g_value_init (prop3, G_TYPE_UINT);
+ g_value_set_uint (prop3, 10);
+ g_hash_table_insert (properties, "prop3", prop3);
+
+ prop4 = g_slice_new0 (GValue);
+ g_value_init (prop4, DBUS_TYPE_G_UCHAR_ARRAY);
+ arr = g_array_new (FALSE, FALSE, sizeof (guchar));
+ g_array_append_vals (arr, "abcde", 5);
+ g_value_take_boxed (prop4, arr);
+ g_hash_table_insert (properties, "prop4", prop4);
+
+ prop5 = g_slice_new0 (GValue);
+ g_value_init (prop5, G_TYPE_BOOLEAN);
+ g_value_set_boolean (prop5, TRUE);
+ g_hash_table_insert (properties, "prop5", prop5);
+
+ return properties;
+}
+
+static void
+test_add_children_from_properties (void)
+{
+ GHashTable *properties;
+ WockyStanza *stanza;
+ WockyNode *top_node;
+ GSList *l;
+
+ properties = create_sample_properties ();
+ stanza = wocky_stanza_new ("properties",
+ "http://example.com/stoats");
+ top_node = wocky_stanza_get_top_node (stanza);
+
+ salut_wocky_node_add_children_from_properties (top_node,
+ properties, "prop");
+
+ g_assert_cmpuint (g_slist_length (top_node->children), ==, 5);
+ for (l = top_node->children; l != NULL; l = l->next)
+ {
+ WockyNode *node = (WockyNode *) l->data;
+ const gchar *name, *type;
+
+ name = wocky_node_get_attribute (node, "name");
+ type = wocky_node_get_attribute (node, "type");
+
+ if (strcmp (name, "prop1") == 0)
+ {
+ g_assert_cmpstr (type, ==, "str");
+ g_assert_cmpstr (node->content, ==, "prop1_value");
+ }
+ else if (strcmp (name, "prop2") == 0)
+ {
+ g_assert_cmpstr (type, ==, "int");
+ g_assert_cmpstr (node->content, ==, "-7");
+ }
+ else if (strcmp (name, "prop3") == 0)
+ {
+ g_assert_cmpstr (type, ==, "uint");
+ g_assert_cmpstr (node->content, ==, "10");
+ }
+ else if (strcmp (name, "prop4") == 0)
+ {
+ g_assert_cmpstr (type, ==, "bytes");
+ g_assert_cmpstr (node->content, ==, "YWJjZGU=");
+ }
+ else if (strcmp (name, "prop5") == 0)
+ {
+ g_assert_cmpstr (type, ==, "bool");
+ g_assert_cmpstr (node->content, ==, "1");
+ }
+ else
+ g_assert_not_reached ();
+ }
+
+ g_hash_table_unref (properties);
+ g_object_unref (stanza);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_type_init ();
+
+ /* to initiate D-Bus types */
+ dbus_g_bus_get (DBUS_BUS_STARTER, NULL);
+
+ g_test_add_func ("/node-properties/extract-properties",
+ test_extract_properties);
+ g_test_add_func ("/node-properties/add-children-from-properties",
+ test_add_children_from_properties);
+
+ return g_test_run ();
+}
diff --git a/salut/tests/continous-failure.py b/salut/tests/continous-failure.py
new file mode 100644
index 000000000..2810c4048
--- /dev/null
+++ b/salut/tests/continous-failure.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+
+# Both have a very small chance of occuring
+
+from twisted.internet import reactor
+from mesh import Mesh, MeshNode, packet_type, BYE, DATA
+import sys
+
+NUMNODES = 2
+DELAY = 0.1
+
+m = Mesh()
+
+class TestMeshNode(MeshNode):
+ def __init__(self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+ self.count = 0
+
+ def node_connected (self):
+ MeshNode.node_connected(self)
+ reactor.callLater (0.5, self.push)
+
+ def newNode (self, data):
+ MeshNode.newNode (self, data)
+ print data + " joined"
+
+ def leftNode (self, data):
+ MeshNode.leftNode (self, data)
+ print data + " left"
+
+ def push (self):
+ reactor.callLater (2, self.push)
+ self.pushInput (self.name + " " + str(self.count))
+ self.count += 1
+
+class FailMeshNode(TestMeshNode):
+ def newNode (self, data):
+ TestMeshNode.newNode (self, data)
+ reactor.callLater (5, self.fail, "node")
+
+n = TestMeshNode("node", m)
+m.addMeshNode(n)
+
+failnode = FailMeshNode("failnode", m)
+m.addMeshNode(failnode)
+
+# Connect all nodes to all others. 1024 bytes/s bandwidth, 50ms delay and 0%
+# packet loss.. (bandwidth and delay aren't implemented just yet)
+m.connect_full(1024, 50, 0.00)
+
+reactor.run()
diff --git a/salut/tests/debug.c b/salut/tests/debug.c
new file mode 100644
index 000000000..9a03bcd8c
--- /dev/null
+++ b/salut/tests/debug.c
@@ -0,0 +1,40 @@
+#include "config.h"
+
+#include <glib.h>
+
+#include <telepathy-glib/run.h>
+#include <telepathy-glib/debug.h>
+
+#include "connection-manager.h"
+#include "dummy-discovery-client.h"
+#include "debug.h"
+
+static TpBaseConnectionManager *
+salut_create_connection_manager (void)
+{
+ return TP_BASE_CONNECTION_MANAGER (
+ g_object_new (SALUT_TYPE_CONNECTION_MANAGER,
+ "backend-type", SALUT_TYPE_DUMMY_DISCOVERY_CLIENT,
+ NULL));
+}
+
+int
+main (int argc, char **argv)
+{
+ g_type_init ();
+ g_set_prgname ("telepathy-salut");
+
+#ifdef ENABLE_DEBUG
+ tp_debug_divert_messages (g_getenv ("SALUT_LOGFILE"));
+ debug_set_flags_from_env ();
+
+ if (g_getenv ("SALUT_PERSIST"))
+ tp_debug_set_persistent (TRUE);
+#endif
+
+ return tp_run_connection_manager ("telepathy-salut", VERSION,
+ salut_create_connection_manager,
+ argc, argv);
+}
+
+
diff --git a/salut/tests/died-node.py b/salut/tests/died-node.py
new file mode 100644
index 000000000..a3ad6e53a
--- /dev/null
+++ b/salut/tests/died-node.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+# Both have a very small chance of occuring
+
+from twisted.internet import reactor
+from mesh import Mesh, MeshNode, packet_type, BYE, DATA
+import sys
+
+NUMNODES = 2
+DELAY = 0.1
+
+m = Mesh()
+success = False
+
+class TestMeshNode(MeshNode):
+
+ def newNode (self, data):
+ MeshNode.newNode (self, data)
+ print data + " joined"
+ if (data == failnode.name):
+ m.removeMeshNode(failnode)
+ n = MeshNode("joinnode", m)
+ m.addMeshNode(n)
+ m.connect_duplex (self, n, 1024, 50, 0.30)
+ if (data == "joinnode"):
+ global success
+ success = True
+ reactor.crash()
+
+
+ def lostNode (self, data):
+ MeshNode.lostNode (self, data)
+ print data + " lost"
+
+n = TestMeshNode("node", m)
+m.addMeshNode(n)
+
+failnode = MeshNode("failnode", m)
+m.addMeshNode(failnode)
+
+# Connect all nodes to all others. 1024 bytes/s bandwidth, 50ms delay and 0%
+# packet loss.. (bandwidth and delay aren't implemented just yet)
+m.connect_full(1024, 50, 0.30)
+
+reactor.run()
+
+if not success:
+ print "FAILED"
+ exit(1)
+
+print "SUCCESS"
+exit(0)
+
diff --git a/salut/tests/dlopen.supp b/salut/tests/dlopen.supp
new file mode 100644
index 000000000..f6300a3a7
--- /dev/null
+++ b/salut/tests/dlopen.supp
@@ -0,0 +1,127 @@
+{
+ <dlopen>
+ Addrcheck,Memcheck:Cond
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ fun:dlopen
+}
+{
+ <dlopen>
+ Addrcheck,Memcheck:Addr4
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ fun:dlopen
+}
+{
+ <dlopen>
+ Addrcheck,Memcheck:Cond
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ fun:dlopen
+}
+{
+ <dlsym>
+ Addrcheck,Memcheck:Addr4
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libc-2.5.so
+ fun:_dl_sym
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ fun:dlsym
+}
+{
+ <dlsym>
+ Addrcheck,Memcheck:Cond
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libc-2.5.so
+ fun:_dl_sym
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ fun:dlsym
+}
+{
+ <dlopen>
+ Addrcheck,Memcheck:Addr1
+ fun:malloc
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ fun:dlopen
+}
+{
+ <dlopen>
+ Addrcheck,Memcheck:Addr1
+ fun:malloc
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ fun:dlopen
+}
+{
+ <dlopen>
+ Addrcheck,Memcheck:Addr1
+ fun:malloc
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ fun:dlopen
+}
+{
+ <libdl>
+ Addrcheck,Memcheck:Addr4
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+ obj:/lib/ld-2.5.so
+ obj:/lib/i686/cmov/libdl-2.5.so
+}
diff --git a/salut/tests/failmeshtest.py b/salut/tests/failmeshtest.py
new file mode 100644
index 000000000..e1cb25456
--- /dev/null
+++ b/salut/tests/failmeshtest.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+
+from twisted.internet import reactor
+from mesh import Mesh, MeshNode, packet_type, ATTEMPT_JOIN
+import sys
+
+NUMNODES = 5
+NUMPACKETS = 10
+DELAY = 0.1
+
+
+nodes = []
+# We're optimists
+success = True
+
+class TestMeshNode(MeshNode):
+ nodes = 1
+
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def node_connected(self):
+ MeshNode.node_connected(self)
+ print "Connected"
+
+ def newNode (self, data):
+ MeshNode.newNode (self, data)
+ print "node0 - Added " + data
+ self.nodes += 1
+ if self.nodes == NUMNODES - 1:
+ print "Everybody who could joined"
+ for x in xrange(0, NUMPACKETS):
+ reactor.callLater(0.1 * x, (lambda y: self.pushInput(str(y) + "\n")), x)
+
+ def leftNode (self, data):
+ MeshNode.leftNode (self, data)
+ print data.rstrip() + " left"
+ reactor.stop()
+
+class FailMeshNode (MeshNode):
+
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def sendPacket (self, data):
+ if packet_type(data) != ATTEMPT_JOIN:
+ MeshNode.sendPacket(self, data)
+
+
+
+class TestMesh(Mesh):
+ expected = {}
+ done = 0
+
+ def gotOutput(self, node, sender, data):
+ global success
+
+ if self.expected.get(node) == None:
+ self.expected[node] = 0
+
+ if (self.expected.get(node, int(data)) != int(data)):
+ print "Got " + data.rstrip() + " instead of " + \
+ str(self.expected[node]) + " from " + node.name
+
+ success = False
+ reactor.crash()
+
+ if not sender in node.peers:
+ print "Sender " + sender + " not in node peers"
+ success = False
+ reactor.crash()
+
+ self.expected[node] = int(data) + 1
+
+ if self.expected[node] == 10:
+ self.done += 1
+
+ if self.done == NUMNODES - 2:
+ for x in self.nodes:
+ x.stats()
+ self.nodes[-2].disconnect()
+
+m = TestMesh()
+
+
+n = TestMeshNode("node0", m)
+nodes.append(n)
+m.addMeshNode(n)
+
+for x in xrange(1, NUMNODES - 1):
+ nodes.append(m.addNode("node" + str(x)))
+
+x = NUMNODES - 1
+n = FailMeshNode("node" + str(x), m)
+nodes.append(n)
+m.addMeshNode(n)
+
+
+# Connect all nodes to all others. 1024 bytes/s bandwidth, 50ms delay and 0%
+# packet loss.. (bandwidth and delay aren't implemented just yet)
+m.connect_full(1024, 50, 0.30)
+
+def timeout():
+ global success
+ print "TIMEOUT!"
+ success = False
+ reactor.crash()
+
+reactor.callLater(60, timeout)
+
+reactor.run()
+
+
+if not success:
+ print "FAILED"
+ sys.exit(-1)
+
+print "SUCCESS"
diff --git a/salut/tests/failnamemeshtest.py b/salut/tests/failnamemeshtest.py
new file mode 100644
index 000000000..e6213a510
--- /dev/null
+++ b/salut/tests/failnamemeshtest.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+
+from twisted.internet import reactor
+from mesh import Mesh, MeshNode, packet_type, WHOIS_REPLY
+import sys
+
+NUMNODES = 5
+NUMPACKETS = 10
+DELAY = 0.1
+
+
+nodes = []
+# We're optimists
+success = True
+
+class TestMeshNode(MeshNode):
+ nodes = 1
+
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def node_connected(self):
+ MeshNode.node_connected(self)
+ print "Connected"
+
+ def newNode (self, data):
+ MeshNode.newNode (self, data)
+ print "node0 - Added " + data
+ self.nodes += 1
+ if self.nodes == NUMNODES - 1:
+ print "Everybody who could joined"
+ for x in xrange(0, NUMPACKETS):
+ reactor.callLater(0.1 * x, (lambda y: self.pushInput(str(y) + "\n")), x)
+
+ def leftNode (self, data):
+ MeshNode.leftNode (self, data)
+ print data.rstrip() + " left"
+ reactor.stop()
+
+class FailMeshNode (MeshNode):
+
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def sendPacket (self, data):
+ if packet_type(data) != WHOIS_REPLY:
+ MeshNode.sendPacket(self, data)
+
+
+
+class TestMesh(Mesh):
+ expected = {}
+ done = 0
+
+ def gotOutput(self, node, sender, data):
+ global success
+
+ if self.expected.get(node) == None:
+ self.expected[node] = 0
+
+ if (self.expected.get(node, int(data)) != int(data)):
+ print "Got " + data.rstrip() + " instead of " + \
+ str(self.expected[node]) + " from " + node.name
+
+ success = False
+ reactor.crash()
+
+ if not sender in node.peers:
+ print "Sender " + sender + " not in node peers"
+ success = False
+ reactor.crash()
+
+ self.expected[node] = int(data) + 1
+
+ if self.expected[node] == 10:
+ self.done += 1
+
+ if self.done == NUMNODES - 2:
+ for x in self.nodes:
+ x.stats()
+ self.nodes[-2].disconnect()
+
+m = TestMesh()
+
+
+n = TestMeshNode("node0", m)
+nodes.append(n)
+m.addMeshNode(n)
+
+for x in xrange(1, NUMNODES - 1):
+ nodes.append(m.addNode("node" + str(x)))
+
+x = NUMNODES - 1
+n = FailMeshNode("node" + str(x), m)
+nodes.append(n)
+m.addMeshNode(n)
+
+
+# Connect all nodes to all others. 1024 bytes/s bandwidth, 50ms delay and 0%
+# packet loss.. (bandwidth and delay aren't implemented just yet)
+m.connect_full(1024, 50, 0.30)
+
+def timeout():
+ global success
+ print "TIMEOUT!"
+ success = False
+ reactor.crash()
+
+reactor.callLater(60, timeout)
+
+reactor.run()
+
+
+if not success:
+ print "FAILED"
+ sys.exit(-1)
+
+print "SUCCESS"
diff --git a/salut/tests/repair-after-fail-test.py b/salut/tests/repair-after-fail-test.py
new file mode 100644
index 000000000..0bfb9516f
--- /dev/null
+++ b/salut/tests/repair-after-fail-test.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+
+# Simulate a node saying bye before any of the other nodes have received all
+# it's data
+#
+# - One sender node + a number of observers is created
+# - The sender node waits untill all observers joined it's group
+# - It then sends out a number of data packets (0,1,2...)
+# Of which the last two data packets are dropped before hitting the mesh
+# - The sender node disconnects (sends bye), causing the observers to notice
+# that they didn't receive all previous packets. And send out repair
+# requests for them, which need to be answered by the leaving node.
+#
+# This can yield false negatives in two cases:
+# * None of the first two bye packets are ever received by any of the other
+# nodes.
+# * While the leaving node did send out (some) repairs, they never reached
+# by any of the other nodes in time.
+#
+# Note that with 4 observers and 30% change of packet drop:
+# - each packet has a 0.8% change of being missed by every observer.
+# - All observers dropping the first two bye packets has a 0.006% chance
+
+from twisted.internet import reactor
+from mesh import Mesh, MeshNode, packet_type, BYE, DATA
+import sys
+
+NUMOBSERVERS = 4
+NUMPACKETS = 10
+DELAY = 0.1
+
+nodes = []
+# We're optimists
+success = True
+observers_done = 0
+
+class TestMeshNode(MeshNode):
+ nodes = 1
+ send_data = True
+
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def node_connected(self):
+ MeshNode.node_connected(self)
+ print "Connected"
+
+ def push_packet(self, num):
+ self.pushInput (str (num) + "\n")
+ if (num >= NUMPACKETS - 3):
+ self.send_data = False
+
+ if num == NUMPACKETS -1:
+ self.disconnect()
+
+ def sendPacket (self, data):
+ if (not self.send_data and packet_type(data) == DATA):
+ return
+ if packet_type(data) == BYE:
+ self.send_data = True
+ MeshNode.sendPacket (self, data)
+
+
+ def newNode (self, data):
+ MeshNode.newNode (self, data)
+ print "node0 - Added " + data
+ self.nodes += 1
+ if self.nodes == NUMOBSERVERS + 1:
+ print "Everybody who could joined"
+ for x in xrange(0, NUMPACKETS):
+ reactor.callLater(0.1 * x, (lambda y: self.push_packet(y)), x)
+
+class ObserverMeshNode(MeshNode):
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def leftNode (self, data):
+ global observers_done
+
+ MeshNode.leftNode (self, data)
+ print data + " left"
+
+ if (data != "node0"):
+ print "Wrong node left!"
+ success = False
+ reactor.stop()
+ if (self.mesh.done < observers_done):
+ print "Observer done before getting all info"
+ success = False
+ reactor.stop()
+
+ observers_done += 1
+
+ if (observers_done == NUMOBSERVERS):
+ reactor.stop()
+
+class TestMesh(Mesh):
+ expected = {}
+ done = 0
+
+ def gotOutput(self, node, sender, data):
+ global success
+
+ if self.expected.get(node) == None:
+ self.expected[node] = 0
+
+ if (self.expected.get(node, int(data)) != int(data)):
+ print "Got " + data.rstrip() + " instead of " + \
+ str(self.expected[node]) + " from " + node.name
+
+ success = False
+ reactor.crash()
+
+ if not sender in node.peers:
+ print "Sender " + sender + " not in node peers"
+ success = False
+ reactor.crash()
+
+ self.expected[node] = int(data) + 1
+
+ if self.expected[node] == 10:
+ self.done += 1
+
+m = TestMesh()
+
+n = TestMeshNode("node0", m)
+nodes.append(n)
+m.addMeshNode(n)
+
+for x in xrange(0, NUMOBSERVERS):
+ n = ObserverMeshNode("observer" + str(x), m)
+ nodes.append(n)
+ m.addMeshNode(n)
+
+# Connect all nodes to all others. 1024 bytes/s bandwidth, 50ms delay and 0%
+# packet loss.. (bandwidth and delay aren't implemented just yet)
+m.connect_full(1024, 50, 0.30)
+
+def timeout():
+ global success
+ print "TIMEOUT!"
+ success = False
+ reactor.crash()
+
+reactor.callLater(60, timeout)
+
+reactor.run()
+
+
+if not success:
+ print "FAILED"
+ sys.exit(-1)
+
+print "SUCCESS"
diff --git a/salut/tests/repair-after-node-disconnected-test.py b/salut/tests/repair-after-node-disconnected-test.py
new file mode 100644
index 000000000..727b32045
--- /dev/null
+++ b/salut/tests/repair-after-node-disconnected-test.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python
+
+# Simulate a node leaving and one of the remaining nodes not having retrieved
+# all info untill after the failure process is running.. Thus needing to get
+# repairs from peers that already have failed the left node
+#
+# - One sender node, a number of observers and a retriever are created
+# - The sender node waits untill all observers joined it's group
+# - It then sends out a number of data packets (0,1,2...)
+# Of which the last packets are dropped before being received by the
+# receiver.
+# - The sender node disconnects (sends bye). Triggering the other nodes to
+# start the failure process, depend on all packets the sender has sent. At
+# the same time the retriever is asked to fail the sender. Which means
+# everyone will complete the failure process except for the retriever, who
+# misses some of the senders packets. Only after the sender node is fully
+# gone, the retrivier is allowed to retrieve it's data packet.. Thus it needs
+# to be able to retrieve them from its remaining peers
+# - After successfull finishing an extra packet is send by the retriever, which
+# has the side-effect of acking all packets of node0. The debug output should
+# reveal that node0 is disposed by everyone shortly afterwards
+#
+# This can yield false negatives in two cases:
+# * The bye packets aren't received by any of the observers/retriever.
+# * The complete set of packets send out isn't ``in the network'' after the
+# sender leaves
+#
+# Both have a very small chance of occuring
+
+from twisted.internet import reactor
+from mesh import Mesh, MeshNode, packet_type, BYE, DATA
+import sys
+
+NUMOBSERVERS = 4
+NUMPACKETS = 10
+DELAY = 0.1
+
+nodes = []
+# We're optimists
+success = True
+
+observers_done = 0
+retriever_receiving = True
+
+class TestMeshNode(MeshNode):
+ nodes = 1
+
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def node_connected(self):
+ MeshNode.node_connected(self)
+ print "Connected"
+
+ def node_disconnected(self):
+ global retriever
+
+ # Let the retriever receive packets again and let it fail the sender
+ retriever.fail(self.name)
+ MeshNode.node_disconnected(self)
+
+ def push_packet(self, num):
+ global retriever_receiving
+ self.pushInput (str (num) + "\n")
+
+ if num >= NUMPACKETS - 3:
+ retriever_receiving = False
+
+ if num == NUMPACKETS - 1:
+ self.disconnect()
+
+ def newNode (self, data):
+ MeshNode.newNode (self, data)
+ print "node0 - Added " + data
+ self.nodes += 1
+ if self.nodes == NUMOBSERVERS + 1:
+ print "Everybody who could joined"
+ for x in xrange(0, NUMPACKETS):
+ reactor.callLater(0.1 * x, (lambda y: self.push_packet(y)), x)
+
+class ObserverMeshNode(MeshNode):
+ def __init__ (self, name, mesh):
+ MeshNode.__init__(self, name, mesh)
+
+ def leftNode (self, data):
+ global observers_done
+ global retriever_receiving
+ global success
+
+ MeshNode.leftNode (self, data)
+ print self.name + " => " + data + " left"
+
+ if (data != "node0"):
+ print "Wrong node left!"
+ success = False
+ reactor.crash()
+ return
+
+ if (self.mesh.done < observers_done):
+ print "Observer done before getting all info"
+ success = False
+ reactor.crash()
+ return
+
+ observers_done += 1
+
+ if (observers_done == NUMOBSERVERS -1):
+ retriever_receiving = True
+
+ if (observers_done == NUMOBSERVERS):
+ reactor.crash()
+ retriever.pushInput("blaat\n");
+
+class RetrieverMeshNode(ObserverMeshNode):
+ def __init__ (self, name, mesh):
+ ObserverMeshNode.__init__(self, name, mesh)
+
+ def recvPacket (self, data):
+ if retriever_receiving:
+ ObserverMeshNode.recvPacket(self, data)
+
+class TestMesh(Mesh):
+ expected = {}
+ done = 0
+
+ def gotOutput(self, node, sender, data):
+ global success
+
+ if sender == "retriever":
+ return
+
+ if self.expected.get(node) == None:
+ self.expected[node] = 0
+
+ if (self.expected.get(node, int(data)) != int(data)):
+ print "Got " + data.rstrip() + " instead of " + \
+ str(self.expected[node]) + " from " + node.name
+
+ success = False
+ reactor.crash()
+
+ if not sender in node.peers:
+ print "Sender " + sender + " not in node peers"
+ success = False
+ reactor.crash()
+
+ self.expected[node] = int(data) + 1
+
+ if self.expected[node] == 10:
+ self.done += 1
+
+m = TestMesh()
+
+n = TestMeshNode("node0", m)
+nodes.append(n)
+m.addMeshNode(n)
+
+for x in xrange(0, NUMOBSERVERS - 1):
+ n = ObserverMeshNode("observer" + str(x), m)
+ nodes.append(n)
+ m.addMeshNode(n)
+
+retriever = RetrieverMeshNode("retriever", m)
+nodes.append(retriever)
+m.addMeshNode(retriever)
+
+# Connect all nodes to all others. 1024 bytes/s bandwidth, 50ms delay and 0%
+# packet loss.. (bandwidth and delay aren't implemented just yet)
+m.connect_full(1024, 50, 0.30)
+
+def timeout():
+ global success
+ print "TIMEOUT!"
+ success = False
+ reactor.crash()
+
+id = reactor.callLater(60, timeout)
+reactor.run()
+
+id.cancel()
+
+if not success:
+ print "FAILED"
+ sys.exit(-1)
+
+print "SUCCES!!.. Waiting 30 before exiting"
+
+reactor.callLater(30, reactor.stop)
+reactor.run()
diff --git a/salut/tests/twisted/Makefile.am b/salut/tests/twisted/Makefile.am
new file mode 100644
index 000000000..00fb50e17
--- /dev/null
+++ b/salut/tests/twisted/Makefile.am
@@ -0,0 +1,132 @@
+TWISTED_TESTS =
+
+TWISTED_BASIC_TESTS = \
+ cm/protocol.py \
+ sidecars.py \
+ $(NULL)
+
+TWISTED_AVAHI_TESTS = \
+ caps_helper.py \
+ avahi/register.py \
+ avahi/aliases.py \
+ avahi/request-im.py \
+ avahi/muc-invite.py \
+ avahi/caps-file-transfer.py \
+ avahi/close-local-pending-room.py \
+ avahi/tubes/disabled-1-1-tubes.py \
+ avahi/file-transfer/send-file-and-cancel-immediately.py \
+ avahi/file-transfer/send-file-and-disconnect.py \
+ avahi/file-transfer/send-file-declined.py \
+ avahi/file-transfer/send-file-item-not-found.py \
+ avahi/file-transfer/send-file-ipv6.py \
+ avahi/file-transfer/send-file-provide-immediately.py \
+ avahi/file-transfer/send-file-to-unknown-contact.py \
+ avahi/file-transfer/send-file-wait-to-provide.py \
+ avahi/file-transfer/receive-and-send-file.py \
+ avahi/file-transfer/receive-file.py \
+ avahi/file-transfer/receive-file-and-disconnect.py \
+ avahi/file-transfer/receive-file-and-sender-disconnect-while-pending.py \
+ avahi/file-transfer/receive-file-and-sender-disconnect-while-transfering.py \
+ avahi/file-transfer/receive-file-and-xmpp-disconnect.py \
+ avahi/file-transfer/receive-file-cancelled-immediately.py \
+ avahi/file-transfer/receive-file-decline.py \
+ avahi/file-transfer/receive-file-ipv6.py \
+ avahi/file-transfer/receive-file-not-found.py \
+ avahi/file-transfer/metadata.py \
+ avahi/file-transfer/ft-client-caps.py \
+ avahi/caps-self.py \
+ avahi/caps-tubes.py \
+ avahi/text-channel.py \
+ avahi/ichat-composing.py \
+ avahi/ichat-incoming-msg.py \
+ avahi/file-transfer/ichat-receive-directory.py \
+ avahi/file-transfer/ichat-receive-file.py \
+ avahi/file-transfer/ichat-send-file.py \
+ avahi/file-transfer/ichat-send-file-declined.py \
+ avahi/tubes/request-invalid-dbus-tube.py \
+ avahi/request-muc.py \
+ avahi/tubes/request-muc-tubes.py \
+ avahi/roomlist.py \
+ avahi/set-presence.py \
+ avahi/tubes/offer-private-stream-tube.py \
+ avahi/tubes/two-muc-stream-tubes.py \
+ avahi/tubes/two-private-stream-tubes.py \
+ avahi/tubes/tube-close.py \
+ avahi/tubes/tubes-to-nonexistant-ids.py \
+ avahi/tubes/two-muc-dbus-tubes.py
+
+TWISTED_AVAHI_OLPC_TESTS = \
+ avahi/olpc-activity-announcements.py
+
+TESTS =
+
+TESTS_ENVIRONMENT = \
+ PYTHONPATH=@abs_top_srcdir@/tests/twisted:@abs_top_builddir@/tests/twisted:$(PYTHONPATH)
+
+if WANT_TWISTED_TESTS
+ TWISTED_TESTS += $(TWISTED_BASIC_TESTS)
+endif
+
+if ENABLE_OLPC
+ TWISTED_AVAHI_TESTS += $(TWISTED_AVAHI_OLPC_TESTS)
+endif
+
+if WANT_TWISTED_AVAHI_TESTS
+ TWISTED_TESTS += $(TWISTED_AVAHI_TESTS)
+endif
+
+check-local: check-coding-style check-twisted
+
+check-twisted:
+ $(MAKE) -C tools
+ rm -f tools/core
+ rm -f tools/salut-testing.log
+ if ! test -n "$$SALUT_TEST_REAL_AVAHI"; then \
+ also_for_system="--also-for-system"; \
+ fi; \
+ sh $(srcdir)/tools/with-session-bus.sh $$also_for_system --config-file=tools/tmp-session-bus.conf -- $(MAKE) check-TESTS \
+ TESTS="$(TWISTED_TESTS)" \
+ TESTS_ENVIRONMENT="$(TESTS_ENVIRONMENT) $(TEST_PYTHON)"
+ @if test -e tools/core; then\
+ echo "Core dump exists: tools/core";\
+ exit 1;\
+ fi
+
+if ENABLE_PLUGINS
+PLUGINS_ENABLED_PYBOOL = True
+else
+PLUGINS_ENABLED_PYBOOL = False
+endif
+
+config.py: Makefile
+ $(AM_V_GEN) { \
+ echo "PACKAGE_STRING = \"$(PACKAGE_STRING)\""; \
+ echo "PLUGINS_ENABLED = $(PLUGINS_ENABLED_PYBOOL)"; \
+ } > $@
+
+BUILT_SOURCES = config.py
+
+EXTRA_DIST = \
+ $(TWISTED_AVAHI_TESTS) \
+ $(TWISTED_AVAHI_OLPC_TESTS) \
+ $(TWISTED_BASIC_TESTS) \
+ constants.py \
+ saluttest.py \
+ servicetest.py \
+ trivialstream.py \
+ avahitest.py \
+ avahimock.py \
+ config.py \
+ ns.py \
+ avahi/file-transfer/file_transfer_helper.py \
+ avahi/tubes/tubetestutil.py \
+ xmppstream.py \
+ ipv6.py
+
+CLEANFILES = salut-[1-9]*.log *.pyc */*.pyc config.py
+
+check_misc_sources = $(TESTS)
+
+include $(top_srcdir)/tools/check-coding-style.mk
+
+SUBDIRS = tools
diff --git a/salut/tests/twisted/avahi/aliases.py b/salut/tests/twisted/avahi/aliases.py
new file mode 100644
index 000000000..02887803d
--- /dev/null
+++ b/salut/tests/twisted/avahi/aliases.py
@@ -0,0 +1,170 @@
+"""
+Test that aliases are built as expected from contacts' TXT records, and that
+the details show up correctly in ContactInfo.
+"""
+
+from servicetest import assertContains, assertEquals, assertLength, call_async
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer
+from avahitest import get_host_name
+import constants as cs
+
+import time
+
+def wait_for_aliases_changed(q, handle):
+ e = q.expect('dbus-signal', signal='AliasesChanged',
+ predicate=lambda e: e.args[0][0][0] == handle)
+ _, alias = e.args[0][0]
+ return alias
+
+def wait_for_contact_info_changed(q, handle):
+ e = q.expect('dbus-signal', signal='ContactInfoChanged',
+ predicate=lambda e: e.args[0] == handle)
+ _, info = e.args
+ return info
+
+def assertOmitsField(field_name, fields):
+ def matches(field):
+ return field[0] == field_name
+
+ assertLength(0, filter(matches, fields))
+
+def check_contact_info(info, txt):
+ first = txt.get('1st', '')
+ last = txt.get('last', '')
+
+ if first != '' or last != '':
+ values = [last, first, '', '', '']
+ assertContains(('n', [], values), info)
+
+ fn = ' '.join([ x for x in [first, last] if x != ''])
+ assertContains(('fn', [], [fn]), info)
+ else:
+ assertOmitsField('n', info)
+ assertOmitsField('fn', info)
+
+ email = txt.get('email', '')
+ if email != '':
+ assertContains(('email', ['type=internet'], [email]), info)
+ else:
+ assertOmitsField('email', info)
+
+ jid = txt.get('jid', '')
+ if jid != '':
+ assertContains(('x-jabber', [], [jid]), info)
+ else:
+ assertOmitsField('x-jabber', info)
+
+def check_all_contact_info_methods(conn, handle, keys):
+ attrs = conn.Contacts.GetContactAttributes([handle],
+ [cs.CONN_IFACE_CONTACT_INFO], True)[handle]
+ info = attrs[cs.CONN_IFACE_CONTACT_INFO + "/info"]
+ check_contact_info(info, keys)
+
+ info = conn.ContactInfo.GetContactInfo([handle])[handle]
+ check_contact_info(info, keys)
+
+ info = conn.ContactInfo.RequestContactInfo(handle)
+ check_contact_info(info, keys)
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_CONNECTED, cs.CSR_NONE_SPECIFIED])
+
+ assertContains(cs.CONN_IFACE_CONTACT_INFO,
+ conn.Properties.Get(cs.CONN, "Interfaces"))
+ ci_props = conn.Properties.GetAll(cs.CONN_IFACE_CONTACT_INFO)
+ assertEquals(cs.CONTACT_INFO_FLAG_PUSH, ci_props['ContactInfoFlags'])
+ assertEquals(
+ [ ('n', [], cs.CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1),
+ ('fn', [], cs.CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1),
+ ('email', ['type=internet'],
+ cs.CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1),
+ ('x-jabber', [], cs.CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1),
+
+ ],
+ ci_props['SupportedFields'])
+
+ # Just to verify that RCI does approximately nothing and doesn't crash.
+ conn.ContactInfo.RefreshContactInfo([21,42,88])
+
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ contact_name = "aliastest@" + get_host_name()
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", 1234, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+ alias = wait_for_aliases_changed(q, handle)
+ assertEquals(contact_name, alias)
+
+ for (alias, dict, expect_contact_info_changed) in [
+ # Contact publishes just one of 1st and last
+ ("last", { "last": "last" }, True),
+ ("1st", { "1st": "1st"}, True),
+ # Contact publishes a meaningful value for one of 1st and last, and an
+ # empty value for the other one and for "nick". Empty values should be
+ # treated as if missing.
+ ("last", { "last": "last", "1st": "", "nick": "" }, True),
+ ("1st", { "1st": "1st", "last": "", "nick": "" }, True),
+ # When a contact publishes both 1st and last, we have to join them
+ # together in a stupid anglo-centric way, like iChat does.
+ ("1st last", { "1st": "1st", "last": "last" }, True),
+ # Nickname should be preferred as the alias to 1st or last. Since we
+ # don't report nicknames in ContactInfo, and nothing else has changed
+ # from the last update, no ContactInfo changes should be announced.
+ ("nickname", { "1st": "1st", "last": "last", "nick": "nickname" }, False),
+ # If the contact stops publishing any of this stuff, we should fall back
+ # to their JID as their alias.
+ (contact_name, {}, True) ]:
+ txt = basic_txt.copy()
+ txt.update(dict)
+
+ announcer.set(txt)
+
+ a = wait_for_aliases_changed (q, handle)
+ assert a == alias, (a, alias, txt)
+
+ if expect_contact_info_changed:
+ info = wait_for_contact_info_changed(q, handle)
+ check_contact_info(info, dict)
+
+ attrs = conn.Contacts.GetContactAttributes([handle],
+ [cs.CONN_IFACE_ALIASING], True)[handle]
+ assertEquals(alias, attrs[cs.CONN_IFACE_ALIASING + "/alias"])
+
+ check_all_contact_info_methods(conn, handle, dict)
+
+ for keys in [ # Check a few neat transitions, with no empty fields
+ { "email": "foo@bar.com" },
+ { "jid": "nyan@gmail.com", "email": "foo@bar.com" },
+ { "jid": "orly@example.com" },
+ # Check that empty fields are treated as if omitted
+ { "email": "foo@bar.com", "jid": "" },
+ { "jid": "orly@example.com", "email": "" },
+ ]:
+ txt = basic_txt.copy()
+ txt.update(keys)
+
+ announcer.set(txt)
+ info = wait_for_contact_info_changed(q, handle)
+ check_contact_info(info, keys)
+
+ check_all_contact_info_methods(conn, handle, keys)
+
+ # Try an invalid handle. Both Get and Request should return InvalidHandle.
+ # (Technically so should RefreshContactInfo but I am lazy.)
+ call_async(q, conn.ContactInfo, 'GetContactInfo', [42])
+ q.expect('dbus-error', method='GetContactInfo', name=cs.INVALID_HANDLE)
+ call_async(q, conn.ContactInfo, 'RequestContactInfo', 42)
+ q.expect('dbus-error', method='RequestContactInfo', name=cs.INVALID_HANDLE)
+
+ # Try a valid handle for whom we have no data from the network. Get should
+ # just omit them; Request should fail.
+ h = conn.RequestHandles(cs.HT_CONTACT, ['rthrtha@octopus'])[0]
+ assertEquals({}, conn.ContactInfo.GetContactInfo([h]))
+ call_async(q, conn.ContactInfo, 'RequestContactInfo', h)
+ q.expect('dbus-error', method='RequestContactInfo', name=cs.NOT_AVAILABLE)
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/caps-file-transfer.py b/salut/tests/twisted/avahi/caps-file-transfer.py
new file mode 100644
index 000000000..4a630d160
--- /dev/null
+++ b/salut/tests/twisted/avahi/caps-file-transfer.py
@@ -0,0 +1,243 @@
+
+"""
+Test tubes capabilities with Connection.Interface.ContactCapabilities.DRAFT
+
+1. Check if Salut advertise the OOB caps
+
+2. Receive presence and caps from contacts and check that
+GetContactCapabilities works correctly and that ContactCapabilitiesChanged is
+correctly received. Also check that GetContactAttributes gives the same
+results.
+
+- capa announced with FT
+- capa announced without FT
+- no capabilites announced (assume FT is supported)
+"""
+
+import dbus
+
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+from avahitest import txt_get_key
+import avahi
+
+from twisted.words.xish import xpath
+
+from servicetest import EventPattern, assertContains, assertDoesNotContain
+from saluttest import exec_test, make_result_iq
+from xmppstream import setup_stream_listener
+import ns
+from constants import *
+
+from caps_helper import compute_caps_hash, ft_fixed_properties, \
+ ft_allowed_properties, ft_allowed_properties_with_metadata
+from config import PACKAGE_STRING
+
+ft_caps = (ft_fixed_properties, ft_allowed_properties)
+ft_metadata_caps = (ft_fixed_properties, ft_allowed_properties_with_metadata)
+
+# last value of the "ver" key we resolved. We use it to be sure that the
+# modified caps has already be announced.
+old_ver = ''
+
+def receive_presence_and_ask_caps(q, stream, service, contact_name):
+ global old_ver
+
+ event_avahi = q.expect('service-resolved', service=service)
+
+ ver = txt_get_key(event_avahi.txt, "ver")
+ while ver == old_ver:
+ # be sure that the announced caps actually changes
+ event_avahi = q.expect('service-resolved', service=service)
+ ver = txt_get_key(event_avahi.txt, "ver")
+ old_ver = ver
+
+ hash = txt_get_key(event_avahi.txt, "hash")
+ node = txt_get_key(event_avahi.txt, "node")
+ assert hash == 'sha-1'
+
+ # ask caps
+ # FIXME: this only works because Salut ignores @to
+ request = """
+<iq from='""" + contact_name + """'
+ id='receive-presence-and-ask-caps'
+ to='salut@jabber.org/resource'
+ type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info'
+ node='""" + node + '#' + ver + """'/>
+</iq>
+"""
+ stream.send(request)
+
+ # receive caps
+ event = q.expect('stream-iq', iq_type='result',
+ to=contact_name, iq_id='receive-presence-and-ask-caps',
+ query_ns='http://jabber.org/protocol/disco#info')
+ caps_str = str(xpath.queryForNodes('/iq/query/feature', event.stanza))
+
+ features = []
+ for feature in xpath.queryForNodes('/iq/query/feature', event.stanza):
+ features.append(feature['var'])
+
+ # Check if the hash matches the announced capabilities
+ assert ver == compute_caps_hash(['client/pc//%s' % PACKAGE_STRING], features, {})
+ assert ns.X_OOB in features
+ assert ns.IQ_OOB in features
+
+def caps_contain(event, cap):
+ node = xpath.queryForNodes('/iq/query/feature[@var="%s"]'
+ % cap,
+ event.stanza)
+ if node is None:
+ return False
+ if len(node) != 1:
+ return False
+ var = node[0].attributes['var']
+ if var is None:
+ return False
+ return var == cap
+
+def test_ft_caps_from_contact(q, bus, conn, client):
+
+ conn_caps_iface = dbus.Interface(conn, CONN_IFACE_CONTACT_CAPS)
+ conn_contacts_iface = dbus.Interface(conn, CONN_IFACE_CONTACTS)
+
+ # send presence with FT capa
+ ver = compute_caps_hash([], [ns.IQ_OOB], {})
+ txt_record = { "txtvers": "1", "status": "avail",
+ "node": client, "ver": ver, "hash": "sha-1"}
+ contact_name = "test-caps-ft@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port,
+ txt_record)
+
+ # this is the first presence, Salut connects to the contact
+ e = q.expect('incoming-connection', listener = listener)
+ incoming = e.connection
+
+ # Salut looks up our capabilities
+ event = q.expect('stream-iq', connection = incoming,
+ query_ns='http://jabber.org/protocol/disco#info')
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + ver, (query_node.attributes['node'], client, ver)
+
+ contact_handle = conn.RequestHandles(HT_CONTACT, [contact_name])[0]
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + ver
+
+ feature = query.addElement('feature')
+ feature['var'] = ns.IQ_OOB
+ incoming.send(result)
+
+ # FT capa is announced
+ e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged',
+ predicate=lambda e: contact_handle in e.args[0])
+ caps = e.args[0][contact_handle]
+ assertContains(ft_caps, caps)
+
+ caps_get = conn_caps_iface.GetContactCapabilities([contact_handle])[contact_handle]
+ assert caps == caps_get
+
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [contact_handle], [CONN_IFACE_CONTACT_CAPS], False) \
+ [contact_handle][CONN_IFACE_CONTACT_CAPS + '/capabilities']
+ assert caps_via_contacts_iface == caps, caps_via_contacts_iface
+
+ # check if Salut announces the OOB capa
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+ receive_presence_and_ask_caps(q, incoming, service, contact_name)
+
+ # capa announced without FT
+ ver = compute_caps_hash([], ["http://telepathy.freedesktop.org/xmpp/pony"], {})
+ txt_record = { "txtvers": "1", "status": "avail",
+ "node": client, "ver": ver, "hash": "sha-1"}
+ contact_name = "test-caps-ft2@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port,
+ txt_record)
+
+ # this is the first presence, Salut connects to the contact
+ e = q.expect('incoming-connection', listener = listener)
+ incoming = e.connection
+
+ # Salut looks up our capabilities
+ event = q.expect('stream-iq', connection = incoming,
+ query_ns='http://jabber.org/protocol/disco#info')
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + ver, (query_node.attributes['node'], client, ver)
+
+ contact_handle = conn.RequestHandles(HT_CONTACT, [contact_name])[0]
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + ver
+
+ feature = query.addElement('feature')
+ feature['var'] = "http://telepathy.freedesktop.org/xmpp/pony"
+ incoming.send(result)
+
+ # the FT capability is not announced
+ e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged')
+ caps = e.args[0][contact_handle]
+ assertDoesNotContain(ft_caps, caps)
+
+ caps_get = conn_caps_iface.GetContactCapabilities([contact_handle])[contact_handle]
+ assert caps == caps_get
+
+
+ # no capabilites announced (assume FT is supported to insure interop)
+ txt_record = { "txtvers": "1", "status": "avail"}
+ contact_name = "test-caps-ft-no-capa2@" + get_host_name()
+ contact_handle = conn.RequestHandles(HT_CONTACT, [contact_name])[0]
+ listener, port = setup_stream_listener(q, contact_name)
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port,
+ txt_record)
+
+ # FT capa is announced
+ e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged',
+ predicate=lambda e: contact_handle in e.args[0].keys())
+
+ caps = e.args[0][contact_handle]
+ assertContains(ft_caps, caps)
+
+ caps_get = conn_caps_iface.GetContactCapabilities([contact_handle])[contact_handle]
+ assert caps == caps_get
+
+def test(q, bus, conn):
+ # last value of the "ver" key we resolved. We use it to be sure that the
+ # modified caps has already be announced.
+ old_ver = None
+
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0, 0])
+
+ # check our own capabilities
+ self_handle = conn.GetSelfHandle()
+ conn_caps_iface = dbus.Interface(conn, CONN_IFACE_CONTACT_CAPS)
+ caps = conn_caps_iface.GetContactCapabilities([self_handle])[self_handle]
+ assertContains(ft_metadata_caps, caps)
+
+ client = 'http://telepathy.freedesktop.org/fake-client'
+ test_ft_caps_from_contact(q, bus, conn, client)
+
+ conn.Disconnect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/caps-self.py b/salut/tests/twisted/avahi/caps-self.py
new file mode 100644
index 000000000..e73c1fa34
--- /dev/null
+++ b/salut/tests/twisted/avahi/caps-self.py
@@ -0,0 +1,73 @@
+"""
+Basic test of SetSelfCapabilities on interface
+org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities
+"""
+
+from saluttest import exec_test
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+from avahitest import txt_get_key
+import avahi
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy
+from saluttest import fixed_features
+
+from twisted.words.xish import xpath, domish
+from caps_helper import compute_caps_hash, check_caps_txt
+from config import PACKAGE_STRING
+import ns
+import constants as cs
+
+import time
+import dbus
+
+HT_CONTACT = 1
+
+def test(q, bus, conn):
+ # last value of the "ver" key we resolved. We use it to be sure that the
+ # modified caps has already be announced.
+ old_ver = None
+
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+ e = q.expect('service-resolved', service = service)
+
+ ver = txt_get_key(e.txt, "ver")
+ while ver == old_ver:
+ # be sure that the announced caps actually changes
+ e = q.expect('service-resolved', service=service)
+ ver = txt_get_key(e.txt, "ver")
+ old_ver = ver
+
+ # We support OOB file transfer
+ caps = compute_caps_hash(['client/pc//%s' % PACKAGE_STRING],
+ fixed_features, {})
+ check_caps_txt(e.txt, caps)
+
+ conn_caps_iface = dbus.Interface(conn, cs.CONN_IFACE_CONTACT_CAPS)
+
+ # Advertise nothing
+ conn_caps_iface.UpdateCapabilities([])
+
+ service.resolve()
+ e = q.expect('service-resolved', service = service)
+
+ # Announced capa didn't change
+ caps = compute_caps_hash(['client/pc//%s' % PACKAGE_STRING],
+ fixed_features, {})
+
+ check_caps_txt(e.txt, caps)
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/caps-tubes.py b/salut/tests/twisted/avahi/caps-tubes.py
new file mode 100644
index 000000000..dde30457c
--- /dev/null
+++ b/salut/tests/twisted/avahi/caps-tubes.py
@@ -0,0 +1,791 @@
+
+"""
+Test tubes capabilities with Connection.Interface.ContactCapabilities.DRAFT
+
+1. Receive presence and caps from contacts and check that
+GetContactCapabilities works correctly and that ContactCapabilitiesChanged is
+correctly received. Also check that GetContactAttributes gives the same
+results.
+
+- no tube cap at all
+- 1 stream tube cap
+- 1 D-Bus tube cap
+- 1 stream tube + 1 D-Bus tube caps
+- 2 stream tube + 2 D-Bus tube caps
+- 1 stream tube + 1 D-Bus tube caps, again, to test whether the caps cache
+ works with tubes
+
+2. Test UpdateCapabilities and test that the avahi txt record is updated test
+that the D-Bus signal ContactCapabilitiesChanged is fired for the self handle,
+ask Salut for its caps with an iq request, check the reply is correct, and ask
+Salut for its caps using D-Bus method GetContactCapabilities. Also check that
+GetContactAttributes gives the same results.
+
+- no tube cap at all
+- 1 stream tube cap
+- 1 D-Bus tube cap
+- 1 stream tube + 1 D-Bus tube caps
+- 2 stream tube + 2 D-Bus tube caps
+- 1 stream tube + 1 D-Bus tube caps, again, just for the fun
+
+"""
+
+import dbus
+import sys
+
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+from avahitest import txt_get_key
+import avahi
+
+from twisted.words.xish import domish, xpath
+
+from servicetest import EventPattern
+from saluttest import exec_test, make_result_iq, sync_stream, fixed_features
+from xmppstream import setup_stream_listener, connect_to_stream
+import ns
+from constants import *
+
+from caps_helper import compute_caps_hash, check_caps
+from config import PACKAGE_STRING
+
+print "FIXME: disabled because 1-1 tubes are disabled for now"
+# exiting 77 causes automake to consider the test to have been skipped
+raise SystemExit(77)
+
+text_fixed_properties = dbus.Dictionary({
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType': 1L,
+ 'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.Text'
+ })
+text_allowed_properties = dbus.Array([
+ 'org.freedesktop.Telepathy.Channel.TargetHandle',
+ ])
+
+ft_fixed_properties = dbus.Dictionary({
+ CHANNEL_TYPE: CHANNEL_TYPE_FILE_TRANSFER,
+ TARGET_HANDLE_TYPE: HT_CONTACT
+ })
+ft_allowed_properties = dbus.Array([
+ FT_CONTENT_HASH_TYPE, TARGET_HANDLE, TARGET_ID, FT_CONTENT_TYPE, FT_FILENAME,
+ FT_SIZE, FT_CONTENT_HASH, FT_DESCRIPTION,
+ FT_DATE, FT_INITIAL_OFFSET, FT_URI
+ ])
+
+stream_tube_fixed_properties = dbus.Dictionary({
+ TARGET_HANDLE_TYPE: HT_CONTACT,
+ CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE
+ })
+stream_tube_allowed_properties = dbus.Array([TARGET_HANDLE,
+ TARGET_ID, STREAM_TUBE_SERVICE])
+
+dbus_tube_fixed_properties = dbus.Dictionary({
+ TARGET_HANDLE_TYPE: HT_CONTACT,
+ CHANNEL_TYPE: CHANNEL_TYPE_DBUS_TUBE
+ })
+dbus_tube_allowed_properties = dbus.Array([TARGET_HANDLE,
+ TARGET_ID, DBUS_TUBE_SERVICE_NAME])
+
+daap_fixed_properties = dbus.Dictionary({
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType': 1L,
+ 'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube',
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube.Service':
+ 'daap'
+ })
+daap_allowed_properties = dbus.Array([
+ 'org.freedesktop.Telepathy.Channel.TargetHandle',
+ ])
+
+http_fixed_properties = dbus.Dictionary({
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType': 1L,
+ 'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube',
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube.Service':
+ 'http'
+ })
+http_allowed_properties = dbus.Array([
+ 'org.freedesktop.Telepathy.Channel.TargetHandle',
+ ])
+
+xiangqi_fixed_properties = dbus.Dictionary({
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType': 1L,
+ 'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.DBusTube',
+ 'org.freedesktop.Telepathy.Channel.Type.DBusTube.ServiceName':
+ 'com.example.Xiangqi'
+ })
+xiangqi_allowed_properties = dbus.Array([
+ 'org.freedesktop.Telepathy.Channel.TargetHandle',
+ ])
+
+go_fixed_properties = dbus.Dictionary({
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType': 1L,
+ 'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.DBusTube',
+ 'org.freedesktop.Telepathy.Channel.Type.DBusTube.ServiceName':
+ 'com.example.Go'
+ })
+go_allowed_properties = dbus.Array([
+ 'org.freedesktop.Telepathy.Channel.TargetHandle',
+ ])
+
+def make_presence(from_jid, type, status):
+ presence = domish.Element((None, 'presence'))
+
+ if from_jid is not None:
+ presence['from'] = from_jid
+
+ if type is not None:
+ presence['type'] = type
+
+ if status is not None:
+ presence.addElement('status', content=status)
+
+ return presence
+
+def presence_add_caps(presence, ver, client, hash=None):
+ c = presence.addElement(('http://jabber.org/protocol/caps', 'c'))
+ c['node'] = client
+ c['ver'] = ver
+ if hash is not None:
+ c['hash'] = hash
+ return presence
+
+# last value of the "ver" key we resolved. We use it to be sure that the
+# modified caps has already be announced.
+old_ver = ''
+
+def receive_presence_and_ask_caps(q, stream, service):
+ global old_ver
+
+ event_avahi, event_dbus = q.expect_many(
+ EventPattern('service-resolved', service=service),
+ EventPattern('dbus-signal', signal='ContactCapabilitiesChanged')
+ )
+ signaled_caps = event_dbus.args[0][1]
+
+ ver = txt_get_key(event_avahi.txt, "ver")
+ while ver == old_ver:
+ # be sure that the announced caps actually changes
+ event_avahi = q.expect('service-resolved', service=service)
+ ver = txt_get_key(event_avahi.txt, "ver")
+ old_ver = ver
+
+ hash = txt_get_key(event_avahi.txt, "hash")
+ node = txt_get_key(event_avahi.txt, "node")
+ assert hash == 'sha-1'
+
+ # ask caps
+ request = """
+<iq from='fake_contact@jabber.org/resource'
+ id='disco1'
+ to='salut@jabber.org/resource'
+ type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info'
+ node='""" + node + '#' + ver + """'/>
+</iq>
+"""
+ stream.send(request)
+
+ # receive caps
+ event = q.expect('stream-iq',
+ query_ns='http://jabber.org/protocol/disco#info')
+ caps_str = str(xpath.queryForNodes('/iq/query/feature', event.stanza))
+
+ features = []
+ for feature in xpath.queryForNodes('/iq/query/feature', event.stanza):
+ features.append(feature['var'])
+
+ # Check if the hash matches the announced capabilities
+ assert ver == compute_caps_hash(['client/pc//%s' % PACKAGE_STRING], features, {})
+
+ return (event, caps_str, signaled_caps)
+
+def caps_contain(event, cap):
+ node = xpath.queryForNodes('/iq/query/feature[@var="%s"]'
+ % cap,
+ event.stanza)
+ if node is None:
+ return False
+ if len(node) != 1:
+ return False
+ var = node[0].attributes['var']
+ if var is None:
+ return False
+ return var == cap
+
+def test_tube_caps_from_contact(q, bus, conn, service,
+ client):
+
+ conn_caps_iface = dbus.Interface(conn, CONN_IFACE_CONTACT_CAPS)
+ conn_contacts_iface = dbus.Interface(conn, CONN_IFACE_CONTACTS)
+
+ # send presence with no tube cap
+ ver = compute_caps_hash([], [], {})
+ txt_record = { "txtvers": "1", "status": "avail",
+ "node": client, "ver": ver, "hash": "sha-1"}
+ contact_name = "test-caps-tube@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port,
+ txt_record)
+
+ # this is the first presence, Salut connects to the contact
+ e = q.expect('incoming-connection', listener = listener)
+ incoming = e.connection
+
+ # Salut looks up our capabilities
+ event = q.expect('stream-iq', connection = incoming, iq_type='set',
+ query_ns='http://jabber.org/protocol/disco#info')
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + ver, (query_node.attributes['node'], client, ver)
+
+ contact_handle = conn.RequestHandles(HT_CONTACT, [contact_name])[0]
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + ver
+
+ feature = query.addElement('feature')
+ feature['var'] = 'http://jabber.org/protocol/jingle'
+ feature = query.addElement('feature')
+ feature['var'] = 'http://jabber.org/protocol/jingle/description/audio'
+ feature = query.addElement('feature')
+ feature['var'] = 'http://www.google.com/transport/p2p'
+ incoming.send(result)
+
+ # no change in ContactCapabilities, so no signal ContactCapabilitiesChanged
+ sync_stream(q, incoming)
+
+ # no special capabilities
+ basic_caps = dbus.Dictionary({contact_handle:
+ [(text_fixed_properties, text_allowed_properties)]})
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == basic_caps, caps
+ # test again, to check GetContactCapabilities does not have side effect
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == basic_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [contact_handle], [CONN_IFACE_CONTACT_CAPS], False) \
+ [contact_handle][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[contact_handle], \
+ caps_via_contacts_iface
+
+ # send presence with generic tube capability
+ txt_record['ver'] = compute_caps_hash([], [ns.TUBES], {})
+ announcer.update(txt_record)
+
+ # Salut looks up our capabilities
+ event = q.expect('stream-iq', connection = incoming,
+ query_ns='http://jabber.org/protocol/disco#info')
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + txt_record['ver']
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + txt_record['ver']
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES
+ incoming.send(result)
+
+ # generic tubes capabilities
+ generic_tubes_caps = dbus.Dictionary({contact_handle:
+ [(text_fixed_properties, text_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties)]})
+ # FIXME: add D-Bus tube once implemented
+ # (dbus_tube_fixed_properties, dbus_tube_allowed_properties)]})
+ event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged')
+ assert len(event.args) == 1
+ assert event.args[0] == generic_tubes_caps, generic_tubes_caps
+
+ # send presence with 1 stream tube cap
+ txt_record['ver'] = compute_caps_hash([], [ns.TUBES + '/stream#daap'], {})
+ announcer.update(txt_record)
+
+ # Salut looks up our capabilities
+ event = q.expect('stream-iq', connection = incoming, iq_type='set',
+ query_ns='http://jabber.org/protocol/disco#info')
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + txt_record['ver']
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + txt_record['ver']
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES + '/stream#daap'
+ incoming.send(result)
+
+ event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged')
+ signaled_caps = event.args[0][contact_handle]
+ assert len(signaled_caps) == 3, signaled_caps # basic caps + daap
+ assert (daap_fixed_properties, daap_allowed_properties) in signaled_caps
+
+ # daap capabilities
+ daap_caps = dbus.Dictionary({contact_handle:
+ [(text_fixed_properties, text_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (daap_fixed_properties, daap_allowed_properties)]})
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == daap_caps, caps
+ # test again, to check GetContactCapabilities does not have side effect
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == daap_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [contact_handle], [CONN_IFACE_CONTACT_CAPS], False) \
+ [contact_handle][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[contact_handle], \
+ caps_via_contacts_iface
+
+ # send presence with 1 D-Bus tube cap
+ txt_record['ver'] = compute_caps_hash([], [ns.TUBES + '/dbus#com.example.Xiangqi'], {})
+ announcer.update(txt_record)
+
+ # Salut looks up our capabilities
+ event = q.expect('stream-iq', connection = incoming, iq_type='set',
+ query_ns='http://jabber.org/protocol/disco#info')
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + txt_record['ver']
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + txt_record['ver']
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES + '/dbus#com.example.Xiangqi'
+ incoming.send(result)
+
+ event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged')
+ signaled_caps = event.args[0][contact_handle]
+ assert len(signaled_caps) == 3, signaled_caps # basic caps + Xiangqi
+ assert (xiangqi_fixed_properties, xiangqi_allowed_properties) in signaled_caps
+
+ # xiangqi capabilities
+ xiangqi_caps = dbus.Dictionary({contact_handle:
+ [(text_fixed_properties, text_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (xiangqi_fixed_properties, xiangqi_allowed_properties)]})
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == xiangqi_caps, caps
+ # test again, to check GetContactCapabilities does not have side effect
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == xiangqi_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [contact_handle], [CONN_IFACE_CONTACT_CAPS], False) \
+ [contact_handle][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[contact_handle], \
+ caps_via_contacts_iface
+
+ # send presence with both D-Bus and stream tube caps
+ txt_record['ver'] = compute_caps_hash([], [ns.TUBES + '/dbus#com.example.Xiangqi',
+ ns.TUBES + '/stream#daap'], {})
+ announcer.update(txt_record)
+
+ # Salut looks up our capabilities
+ event = q.expect('stream-iq', connection = incoming, iq_type='set',
+ query_ns='http://jabber.org/protocol/disco#info')
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + txt_record['ver']
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + txt_record['ver']
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES + '/dbus#com.example.Xiangqi'
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES + '/stream#daap'
+ incoming.send(result)
+
+ event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged')
+ signaled_caps = event.args[0][contact_handle]
+ assert len(signaled_caps) == 4, signaled_caps # basic caps + daap+xiangqi
+ assert (daap_fixed_properties, daap_allowed_properties) in signaled_caps
+ assert (xiangqi_fixed_properties, xiangqi_allowed_properties) in signaled_caps
+
+ # daap + xiangqi capabilities
+ daap_xiangqi_caps = dbus.Dictionary({contact_handle:
+ [(text_fixed_properties, text_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (daap_fixed_properties, daap_allowed_properties),
+ (xiangqi_fixed_properties, xiangqi_allowed_properties)]})
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == daap_xiangqi_caps, caps
+ # test again, to check GetContactCapabilities does not have side effect
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == daap_xiangqi_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [contact_handle], [CONN_IFACE_CONTACT_CAPS], False) \
+ [contact_handle][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[contact_handle], \
+ caps_via_contacts_iface
+
+ # send presence with 4 tube caps
+ txt_record['ver'] = compute_caps_hash([], [ns.TUBES + '/dbus#com.example.Xiangqi',
+ ns.TUBES + '/dbus#com.example.Go', ns.TUBES + '/stream#daap', ns.TUBES + '/stream#http'], {})
+ announcer.update(txt_record)
+
+ # Salut looks up our capabilities
+ event = q.expect('stream-iq', connection = incoming, iq_type='set',
+ query_ns='http://jabber.org/protocol/disco#info')
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + txt_record['ver']
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + txt_record['ver']
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES + '/dbus#com.example.Xiangqi'
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES + '/dbus#com.example.Go'
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES + '/stream#daap'
+ feature = query.addElement('feature')
+ feature['var'] = ns.TUBES + '/stream#http'
+ incoming.send(result)
+
+ event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged')
+ signaled_caps = event.args[0][contact_handle]
+ assert len(signaled_caps) == 6, signaled_caps # basic caps + 4 tubes
+ assert (daap_fixed_properties, daap_allowed_properties) in signaled_caps
+ assert (http_fixed_properties, http_allowed_properties) in signaled_caps
+ assert (xiangqi_fixed_properties, xiangqi_allowed_properties) in signaled_caps
+ assert (go_fixed_properties, go_allowed_properties) in signaled_caps
+
+ # http + daap + xiangqi + go capabilities
+ all_tubes_caps = dbus.Dictionary({contact_handle:
+ [(text_fixed_properties, text_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (daap_fixed_properties, daap_allowed_properties),
+ (http_fixed_properties, http_allowed_properties),
+ (xiangqi_fixed_properties,
+ xiangqi_allowed_properties),
+ (go_fixed_properties, go_allowed_properties)]})
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == all_tubes_caps, caps
+ # test again, to check GetContactCapabilities does not have side effect
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == all_tubes_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [contact_handle], [CONN_IFACE_CONTACT_CAPS], False) \
+ [contact_handle][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[contact_handle], \
+ caps_via_contacts_iface
+
+ # send presence with both D-Bus and stream tube caps
+ txt_record['ver'] = compute_caps_hash([], [ns.TUBES + '/dbus#com.example.Xiangqi',
+ ns.TUBES + '/stream#daap'], {})
+ announcer.update(txt_record)
+
+ # Salut does not look up our capabilities because of the cache
+
+ event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged')
+ signaled_caps = event.args[0][contact_handle]
+ assert len(signaled_caps) == 4, signaled_caps # basic caps + daap+xiangqi
+ assert (daap_fixed_properties, daap_allowed_properties) in signaled_caps
+ assert (xiangqi_fixed_properties, xiangqi_allowed_properties) in signaled_caps
+
+ # daap + xiangqi capabilities
+ daap_xiangqi_caps = dbus.Dictionary({contact_handle:
+ [(text_fixed_properties, text_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (daap_fixed_properties, daap_allowed_properties),
+ (xiangqi_fixed_properties, xiangqi_allowed_properties)]})
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == daap_xiangqi_caps, caps
+ # test again, to check GetContactCapabilities does not have side effect
+ caps = conn_caps_iface.GetContactCapabilities([contact_handle])
+ assert caps == daap_xiangqi_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [contact_handle], [CONN_IFACE_CONTACT_CAPS], False) \
+ [contact_handle][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[contact_handle], \
+ caps_via_contacts_iface
+
+def test_tube_caps_to_contact(q, bus, conn, service):
+ basic_caps = dbus.Dictionary({1:
+ [(text_fixed_properties, text_allowed_properties),
+ (ft_fixed_properties, ft_allowed_properties)]})
+ daap_caps = dbus.Dictionary({1:
+ [(text_fixed_properties, text_allowed_properties),
+ (ft_fixed_properties, ft_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (daap_fixed_properties, daap_allowed_properties)]})
+ xiangqi_caps = dbus.Dictionary({1:
+ [(text_fixed_properties, text_allowed_properties),
+ (ft_fixed_properties, ft_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (xiangqi_fixed_properties, xiangqi_allowed_properties)]})
+ daap_xiangqi_caps = dbus.Dictionary({1:
+ [(text_fixed_properties, text_allowed_properties),
+ (ft_fixed_properties, ft_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (daap_fixed_properties, daap_allowed_properties),
+ (xiangqi_fixed_properties, xiangqi_allowed_properties)]})
+ all_tubes_caps = dbus.Dictionary({1:
+ [(text_fixed_properties, text_allowed_properties),
+ (ft_fixed_properties, ft_allowed_properties),
+ (stream_tube_fixed_properties, stream_tube_allowed_properties),
+ (daap_fixed_properties, daap_allowed_properties),
+ (http_fixed_properties, http_allowed_properties),
+ (xiangqi_fixed_properties, xiangqi_allowed_properties),
+ (go_fixed_properties, go_allowed_properties)]})
+
+ # send presence with no cap info
+ txt_record = { "txtvers": "1", "status": "avail"}
+ contact_name = "test-caps-tube2@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port,
+ txt_record)
+
+ # Before opening a connection to Salut, wait Salut receives our presence
+ # via Avahi. Otherwise, Salut will not allow our connection. We may
+ # consider it is a bug in Salut, and we may want Salut to wait a few
+ # seconds in case Avahi was slow.
+ # See incoming_pending_connection_got_from(): if the SalutContact is not
+ # found in the table, we close the connection.
+ q.expect('dbus-signal', signal='PresencesChanged')
+
+ # initialise a connection (Salut does not do it because there is no caps
+ # here)
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+ service.resolve()
+ e = q.expect('service-resolved', service = service)
+ outbound = connect_to_stream(q, contact_name,
+ self_handle_name, str(e.pt), e.port)
+ e = q.expect('connection-result')
+ assert e.succeeded, e.reason
+ e = q.expect('stream-opened', connection = outbound)
+
+ conn_caps_iface = dbus.Interface(conn, CONN_IFACE_CONTACT_CAPS)
+ conn_contacts_iface = dbus.Interface(conn, CONN_IFACE_CONTACTS)
+
+ # Check our own caps
+ caps = conn_caps_iface.GetContactCapabilities([1])
+ assert caps == basic_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [1], [CONN_IFACE_CONTACT_CAPS], False) \
+ [1][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[1], caps_via_contacts_iface
+
+ # Advertise nothing
+ conn_caps_iface.UpdateCapabilities([])
+
+ # Check our own caps
+ caps = conn_caps_iface.GetContactCapabilities([1])
+ assert caps == basic_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [1], [CONN_IFACE_CONTACT_CAPS], False) \
+ [1][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[1], caps_via_contacts_iface
+
+ sync_stream(q, outbound)
+
+ # Advertise daap
+ ret_caps = conn_caps_iface.UpdateCapabilities(
+ [('bigclient', [daap_fixed_properties], [])])
+
+ # Expect Salut to reply with the correct caps
+ event, caps_str, signaled_caps = receive_presence_and_ask_caps(q, outbound,
+ service)
+ assert caps_contain(event, ns.TUBES) == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#daap') == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#http') == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Go') \
+ == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Xiangqi') \
+ == False, caps_str
+ assert len(signaled_caps) == 4, signaled_caps # basic caps + daap
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ STREAM_TUBE_SERVICE: 'daap'}, [TARGET_HANDLE]) in signaled_caps
+
+ # Check our own caps
+ caps = conn_caps_iface.GetContactCapabilities([1])
+ assert caps == daap_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [1], [CONN_IFACE_CONTACT_CAPS], False) \
+ [1][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[1], caps_via_contacts_iface
+
+ # Advertise xiangqi
+ ret_caps = conn_caps_iface.SetSelfCapabilities(
+ [('bigclient', [xiangqi_fixed_properties], [])])
+
+ # Expect Salut to reply with the correct caps
+ event, caps_str, signaled_caps = receive_presence_and_ask_caps(q, outbound,
+ service)
+ assert caps_contain(event, ns.TUBES) == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#daap') == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#http') == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Go') \
+ == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Xiangqi') \
+ == True, caps_str
+ assert len(signaled_caps) == 4, signaled_caps # basic caps + daap
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_DBUS_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ DBUS_TUBE_SERVICE_NAME: 'com.example.Xiangqi'}, [TARGET_HANDLE]) in signaled_caps
+
+ # Check our own caps
+ caps = conn_caps_iface.GetContactCapabilities([1])
+ assert caps == xiangqi_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [1], [CONN_IFACE_CONTACT_CAPS], False) \
+ [1][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[1], caps_via_contacts_iface
+
+ # Advertise daap + xiangqi
+ ret_caps = conn_caps_iface.SetSelfCapabilities(
+ [('bigclient', [daap_fixed_properties + xiangqi_fixed_properties], [])])
+
+ # Expect Salut to reply with the correct caps
+ event, caps_str, signaled_caps = receive_presence_and_ask_caps(q, outbound,
+ service)
+ assert caps_contain(event, ns.TUBES) == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#daap') == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#http') == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Go') \
+ == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Xiangqi') \
+ == True, caps_str
+ assert len(signaled_caps) == 5, signaled_caps # basic caps + daap+xiangqi
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ STREAM_TUBE_SERVICE: 'daap'}, [TARGET_HANDLE]) in signaled_caps
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_DBUS_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ DBUS_TUBE_SERVICE_NAME: 'com.example.Xiangqi'}, [TARGET_HANDLE]) in signaled_caps
+
+ # Check our own caps
+ caps = conn_caps_iface.GetContactCapabilities([1])
+ assert caps == daap_xiangqi_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [1], [CONN_IFACE_CONTACT_CAPS], False) \
+ [1][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[1], caps_via_contacts_iface
+
+ # Advertise 4 tubes
+ ret_caps = conn_caps_iface.SetSelfCapabilities(
+ [('bigclient', [daap_fixed_properties, http_fixed_properties,
+ go_fixed_properties, xiangqi_fixed_properties], [])])
+
+ # Expect Salut to reply with the correct caps
+ event, caps_str, signaled_caps = receive_presence_and_ask_caps(q, outbound,
+ service)
+ assert caps_contain(event, ns.TUBES) == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#daap') == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#http') == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Go') \
+ == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Xiangqi') \
+ == True, caps_str
+ assert len(signaled_caps) == 7, signaled_caps # basic caps + 4 tubes
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ STREAM_TUBE_SERVICE: 'daap'}, [TARGET_HANDLE]) in signaled_caps
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_DBUS_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ DBUS_TUBE_SERVICE_NAME: 'com.example.Xiangqi'}, [TARGET_HANDLE]) in signaled_caps
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ STREAM_TUBE_SERVICE: 'http'}, [TARGET_HANDLE]) in signaled_caps
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_DBUS_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ DBUS_TUBE_SERVICE_NAME: 'com.example.Go'}, [TARGET_HANDLE]) in signaled_caps
+
+ # Check our own caps
+ caps = conn_caps_iface.GetContactCapabilities([1])
+ assert caps == all_tubes_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [1], [CONN_IFACE_CONTACT_CAPS], False) \
+ [1][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[1], caps_via_contacts_iface
+
+ # Advertise daap + xiangqi
+ ret_caps = conn_caps_iface.SetSelfCapabilities(
+ [('bigclient', [daap_fixed_properties + xiangqi_fixed_properties], [])])
+
+ # Expect Salut to reply with the correct caps
+ event, caps_str, signaled_caps = receive_presence_and_ask_caps(q, outbound,
+service)
+ assert caps_contain(event, ns.TUBES) == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#daap') == True, caps_str
+ assert caps_contain(event, ns.TUBES + '/stream#http') == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Go') \
+ == False, caps_str
+ assert caps_contain(event, ns.TUBES + '/dbus#com.example.Xiangqi') \
+ == True, caps_str
+ assert len(signaled_caps) == 5, signaled_caps # basic caps + daap+xiangqi
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ STREAM_TUBE_SERVICE: 'daap'}, [TARGET_HANDLE]) in signaled_caps
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_DBUS_TUBE, TARGET_HANDLE_TYPE: HT_CONTACT,
+ DBUS_TUBE_SERVICE_NAME: 'com.example.Xiangqi'}, [TARGET_HANDLE]) in signaled_caps
+
+ # Check our own caps
+ caps = conn_caps_iface.GetContactCapabilities([1])
+ assert caps == daap_xiangqi_caps, caps
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes(
+ [1], [CONN_IFACE_CONTACT_CAPS], False) \
+ [1][CONN_IFACE_CONTACT_CAPS + '/caps']
+ assert caps_via_contacts_iface == caps[1], caps_via_contacts_iface
+
+
+def test(q, bus, conn):
+ # last value of the "ver" key we resolved. We use it to be sure that the
+ # modified caps has already be announced.
+ old_ver = None
+
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0, 0])
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(1, [self_handle])[0]
+
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+ e = q.expect('service-resolved', service = service)
+ ver = txt_get_key(e.txt, "ver")
+ while ver == old_ver:
+ # be sure that the announced caps actually changes
+ e = q.expect('service-resolved', service=service)
+ ver = txt_get_key(e.txt, "ver")
+ old_ver = ver
+
+ caps = compute_caps_hash(['client/pc//%s' % PACKAGE_STRING],
+ fixed_features, {})
+ check_caps(e.txt, caps)
+
+ client = 'http://telepathy.freedesktop.org/fake-client'
+
+ test_tube_caps_from_contact(q, bus, conn, service,
+ client)
+
+ test_tube_caps_to_contact(q, bus, conn, service)
+
+ conn.Disconnect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+
+
+if __name__ == '__main__':
+ exec_test(test)
+
diff --git a/salut/tests/twisted/avahi/close-local-pending-room.py b/salut/tests/twisted/avahi/close-local-pending-room.py
new file mode 100644
index 000000000..b6b2e081d
--- /dev/null
+++ b/salut/tests/twisted/avahi/close-local-pending-room.py
@@ -0,0 +1,91 @@
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+import avahi
+import constants as cs
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy
+
+from twisted.words.xish import domish
+
+import dbus
+
+NS_CLIQUE = "http://telepathy.freedesktop.org/xmpp/clique"
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(cs.HT_CONTACT, [self_handle])[0]
+
+ contact_name = "test-text-channel@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ # create a clique room
+ basic_txt = { "txtvers": "0"}
+ AvahiAnnouncer("myroom", "_clique._udp", 41377, basic_txt)
+
+ # connect a stream
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+ e = q.expect('service-resolved', service = service)
+
+ xmpp_connection = connect_to_stream(q, contact_name,
+ self_handle_name, str(e.pt), e.port)
+
+ e = q.expect('connection-result')
+ assert e.succeeded, e.reason
+
+ e = q.expect('stream-opened', connection = xmpp_connection)
+
+ # send an invitation
+ message = domish.Element(('', 'message'))
+ message['type'] = 'normal'
+ message.addElement('body', content='You got a Clique chatroom invitation')
+ invite = message.addElement((NS_CLIQUE, 'invite'))
+ invite.addElement('roomname', content='myroom')
+ invite.addElement('reason', content='Inviting to this room')
+ invite.addElement('address', content='127.0.0.1')
+ invite.addElement('port', content='41377')
+ xmpp_connection.send(message)
+
+ # group channel is created
+ e = q.expect('dbus-signal', signal='NewChannel',
+ predicate=lambda e:
+ e.args[1] == cs.CHANNEL_TYPE_TEXT and
+ e.args[2] == cs.HT_ROOM)
+ path = e.args[0]
+ channel = make_channel_proxy(conn, path, 'Channel')
+ props_iface = dbus.Interface(bus.get_object(conn.object.bus_name, path),
+ dbus.PROPERTIES_IFACE)
+
+ q.expect('dbus-signal', signal='MembersChanged', path=path)
+
+ lp_members = props_iface.Get('org.freedesktop.Telepathy.Channel.Interface.Group',
+ 'LocalPendingMembers')
+
+ assert len(lp_members) == 1
+ added, actor, reason, msg = lp_members[0]
+
+ assert added == self_handle
+ assert actor == handle
+ assert reason == 4 #invited
+ assert msg == 'Inviting to this room'
+
+ # decline invitation
+ channel.Close()
+ q.expect('dbus-signal', signal='Closed')
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/file-transfer/file_transfer_helper.py b/salut/tests/twisted/avahi/file-transfer/file_transfer_helper.py
new file mode 100644
index 000000000..d25db76b9
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/file_transfer_helper.py
@@ -0,0 +1,507 @@
+import dbus
+import socket
+import hashlib
+import avahi
+import BaseHTTPServer
+import urllib
+import httplib
+import urlparse
+import sys
+import os
+
+from avahitest import AvahiAnnouncer, AvahiListener, get_host_name
+from saluttest import wait_for_contact_in_publish
+
+from caps_helper import extract_data_forms, add_dataforms, compute_caps_hash, \
+ send_disco_reply
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy, EventPattern, assertEquals, call_async, sync_dbus
+import constants as cs
+import ns
+
+from twisted.words.xish import domish, xpath
+
+from dbus import PROPERTIES_IFACE
+
+class File(object):
+ DEFAULT_DATA = "What a nice file"
+ DEFAULT_NAME = "The foo.txt"
+ DEFAULT_CONTENT_TYPE = 'text/plain'
+ DEFAULT_DESCRIPTION = "A nice file to test"
+
+ def __init__(self, data=DEFAULT_DATA, name=DEFAULT_NAME,
+ content_type=DEFAULT_CONTENT_TYPE, description=DEFAULT_DESCRIPTION,
+ hash_type=cs.FILE_HASH_TYPE_MD5):
+ self.data = data
+ self.size = len(self.data)
+ self.name = name
+
+ self.content_type = content_type
+ self.description = description
+ self.date = 0
+
+ self.compute_hash(hash_type)
+
+ self.uri = 'file:///tmp/%s' % self.name
+
+ def compute_hash(self, hash_type):
+ assert hash_type == cs.FILE_HASH_TYPE_MD5
+
+ self.hash_type = hash_type
+ self.hash = hashlib.md5(self.data).hexdigest()
+
+class FileTransferTest(object):
+ CONTACT_NAME = 'test-ft'
+
+ service_name = 'wacky.service.name'
+ metadata = {'loads': ['of', 'blahblah', 'stuff'],
+ 'mental': ['data', 'sidf']}
+
+ def __init__(self):
+ self.file = File()
+ self.contact_service = None
+
+ def connect(self):
+ self.conn.Connect()
+ self.q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+ self.self_handle = self.conn.GetSelfHandle()
+ self.self_handle_name = self.conn.InspectHandles(cs.HT_CONTACT, [self.self_handle])[0]
+
+ def announce_contact(self, name=CONTACT_NAME, metadata=True):
+ client = 'http://telepathy.freedesktop.org/fake-client'
+ features = [ns.IQ_OOB]
+
+ if metadata:
+ features += [ns.TP_FT_METADATA]
+
+ ver = compute_caps_hash([], features, {})
+ txt_record = { "txtvers": "1", "status": "avail",
+ "node": client, "ver": ver, "hash": "sha-1"}
+
+ suffix = '@%s' % get_host_name()
+ name += ('-' + os.path.splitext(os.path.basename(sys.argv[0]))[0])
+
+ self.contact_name = name + suffix
+ if len(self.contact_name) > 63:
+ allowed = 63 - len(suffix)
+ self.contact_name = name[:allowed] + suffix
+
+ self.listener, port = setup_stream_listener(self.q, self.contact_name)
+
+ self.contact_service = AvahiAnnouncer(self.contact_name, "_presence._tcp",
+ port, txt_record)
+
+ self.handle = wait_for_contact_in_publish(self.q, self.bus, self.conn,
+ self.contact_name)
+
+ # expect salut to disco our caps
+ e = self.q.expect('incoming-connection', listener=self.listener)
+ stream = e.connection
+
+ e = self.q.expect('stream-iq', to=self.contact_name, query_ns=ns.DISCO_INFO,
+ connection=stream)
+ assertEquals(client + '#' + ver, e.query['node'])
+ send_disco_reply(stream, e.stanza, [], features)
+
+ # lose the connection here to ensure connections are created
+ # where necessary; I just wanted salut to know my caps.
+ stream.send('</stream:stream>')
+ # spend a bit of time in the main loop to ensure the last two
+ # stanzas are actually received by salut before closing the
+ # connection.
+ sync_dbus(self.bus, self.q, self.conn)
+ stream.transport.loseConnection()
+
+ def wait_for_contact(self):
+ if not hasattr(self, 'handle'):
+ self.handle = wait_for_contact_in_publish(self.q, self.bus, self.conn,
+ self.contact_name)
+
+ def create_ft_channel(self):
+ self.channel = make_channel_proxy(self.conn, self.ft_path, 'Channel')
+ self.ft_channel = make_channel_proxy(self.conn, self.ft_path, 'Channel.Type.FileTransfer')
+ self.ft_props = dbus.Interface(self.bus.get_object(
+ self.conn.object.bus_name, self.ft_path), PROPERTIES_IFACE)
+
+ def close_channel(self):
+ self.channel.Close()
+ self.q.expect('dbus-signal', signal='Closed')
+
+ def test(self, q, bus, conn):
+ self.q = q
+ self.bus = bus
+ self.conn = conn
+
+ for fct in self._actions:
+ # stop if a function returns True
+ if fct():
+ break
+
+ # if we announced the service, let's be sure to get rid of it
+ if self.contact_service:
+ self.contact_service.stop()
+
+class ReceiveFileTest(FileTransferTest):
+ def __init__(self):
+ FileTransferTest.__init__(self)
+
+ self._actions = [self.connect, self.announce_contact, self.wait_for_contact,
+ self.connect_to_salut, self.setup_http_server, self.send_ft_offer_iq,
+ self.check_new_channel, self.create_ft_channel, self.set_uri,
+ self.accept_file, self.receive_file, self.close_channel]
+
+ def _resolve_salut_presence(self):
+ AvahiListener(self.q).listen_for_service("_presence._tcp")
+ e = self.q.expect('service-added', name = self.self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+ e = self.q.expect('service-resolved', service = service)
+ return str(e.pt), e.port
+
+ def connect_to_salut(self):
+ host, port = self._resolve_salut_presence()
+
+ self.outbound = connect_to_stream(self.q, self.contact_name,
+ self.self_handle_name, host, port)
+
+ e = self.q.expect('connection-result')
+ assert e.succeeded, e.reason
+ self.q.expect('stream-opened', connection = self.outbound)
+
+ def setup_http_server(self):
+ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ def do_GET(self_):
+ # is that the right file ?
+ filename = self_.path.rsplit('/', 2)[-1]
+ assert filename == urllib.quote(self.file.name)
+
+ self_.send_response(200)
+ self_.send_header('Content-type', self.file.content_type)
+ self_.end_headers()
+ self_.wfile.write(self.file.data)
+
+ def log_message(self, format, *args):
+ if 'CHECK_TWISTED_VERBOSE' in os.environ:
+ BaseHTTPServer.BaseHTTPRequestHandler.log_message(self, format, *args)
+
+ self.httpd = self._get_http_server_class()(('', 0), HTTPHandler)
+
+ def _get_http_server_class(self):
+ return BaseHTTPServer.HTTPServer
+
+ def send_ft_offer_iq(self):
+ iq = domish.Element((None, 'iq'))
+ iq['to'] = self.self_handle_name
+ iq['from'] = self.contact_name
+ iq['type'] = 'set'
+ iq['id'] = 'gibber-file-transfer-0'
+ query = iq.addElement(('jabber:iq:oob', 'query'))
+ url = 'http://127.0.0.1:%u/gibber-file-transfer-0/%s' % \
+ (self.httpd.server_port, urllib.quote(self.file.name))
+ url_node = query.addElement('url', content=url)
+ url_node['type'] = 'file'
+ url_node['size'] = str(self.file.size)
+ url_node['mimeType'] = self.file.content_type
+ query.addElement('desc', content=self.file.description)
+
+ # Metadata
+ if self.service_name:
+ service_form = {ns.TP_FT_METADATA_SERVICE: {'ServiceName': [self.service_name]}}
+ add_dataforms(query, service_form)
+
+ if self.metadata:
+ metadata_form = {ns.TP_FT_METADATA: self.metadata}
+ add_dataforms(query, metadata_form)
+
+ self.outbound.send(iq)
+
+ def check_new_channel(self):
+ e = self.q.expect('dbus-signal', signal='NewChannels',
+ predicate=lambda e:
+ e.args[0][0][1][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_FILE_TRANSFER)
+
+ channels = e.args[0]
+ assert len(channels) == 1
+ path, props = channels[0]
+
+ # check channel properties
+ # org.freedesktop.Telepathy.Channel D-Bus properties
+ assert props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_FILE_TRANSFER
+ assert props[cs.INTERFACES] == []
+ assert props[cs.TARGET_HANDLE] == self.handle
+ assert props[cs.TARGET_ID] == self.contact_name
+ assert props[cs.TARGET_HANDLE_TYPE] == cs.HT_CONTACT
+ assert props[cs.REQUESTED] == False
+ assert props[cs.INITIATOR_HANDLE] == self.handle
+ assert props[cs.INITIATOR_ID] == self.contact_name
+
+ # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties
+ assert props[cs.FT_STATE] == cs.FT_STATE_PENDING
+ assert props[cs.FT_CONTENT_TYPE] == self.file.content_type
+ assert props[cs.FT_FILENAME] == self.file.name
+ assert props[cs.FT_SIZE] == self.file.size
+ # FT's protocol doesn't allow us the send the hash info
+ assert props[cs.FT_CONTENT_HASH_TYPE] == cs.FILE_HASH_TYPE_NONE
+ assert props[cs.FT_CONTENT_HASH] == ''
+ assert props[cs.FT_DESCRIPTION] == self.file.description
+ # FT's protocol doesn't allow us the send the date info
+ assert props[cs.FT_DATE] == 0
+ assert props[cs.FT_AVAILABLE_SOCKET_TYPES] == \
+ {cs.SOCKET_ADDRESS_TYPE_UNIX: [cs.SOCKET_ACCESS_CONTROL_LOCALHOST]}
+ assert props[cs.FT_TRANSFERRED_BYTES] == 0
+ assert props[cs.FT_INITIAL_OFFSET] == 0
+
+ assertEquals(self.service_name, props[cs.FT_SERVICE_NAME])
+ assertEquals(self.metadata, props[cs.FT_METADATA])
+
+ self.ft_path = path
+
+ def set_uri(self):
+ ft_props = dbus.Interface(self.ft_channel, cs.PROPERTIES_IFACE)
+
+ # URI is not set yet
+ uri = ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'URI')
+ assertEquals('', uri)
+
+ # Setting URI
+ call_async(self.q, ft_props, 'Set',
+ cs.CHANNEL_TYPE_FILE_TRANSFER, 'URI', self.file.uri)
+
+ self.q.expect('dbus-signal', signal='URIDefined', args=[self.file.uri])
+
+ self.q.expect('dbus-return', method='Set')
+
+ # Check it has the right value now
+ uri = ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'URI')
+ assertEquals(self.file.uri, uri)
+
+ # We can't change it once it has been set
+ call_async(self.q, ft_props, 'Set',
+ cs.CHANNEL_TYPE_FILE_TRANSFER, 'URI', 'badger://snake')
+ self.q.expect('dbus-error', method='Set', name=cs.INVALID_ARGUMENT)
+
+ def accept_file(self):
+ self.address = self.ft_channel.AcceptFile(cs.SOCKET_ADDRESS_TYPE_UNIX,
+ cs.SOCKET_ACCESS_CONTROL_LOCALHOST, "", 5, byte_arrays=True)
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_ACCEPTED
+ assert reason == cs.FT_STATE_CHANGE_REASON_REQUESTED
+
+ e = self.q.expect('dbus-signal', signal='InitialOffsetDefined')
+ offset = e.args[0]
+ # We don't support resume
+ assert offset == 0
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_OPEN
+ assert reason == cs.FT_STATE_CHANGE_REASON_NONE
+
+ def _read_file_from_socket(self, s):
+ # Read the file from Salut's socket
+ data = ''
+ read = 0
+ while read < self.file.size:
+ data += s.recv(self.file.size - read)
+ read = len(data)
+ assert data == self.file.data
+
+ e = self.q.expect('dbus-signal', signal='TransferredBytesChanged')
+ count = e.args[0]
+ while count < self.file.size:
+ # Catch TransferredBytesChanged until we transfered all the data
+ e = self.q.expect('dbus-signal', signal='TransferredBytesChanged')
+ count = e.args[0]
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_COMPLETED
+ assert reason == cs.FT_STATE_CHANGE_REASON_NONE
+
+ def receive_file(self):
+ # Connect to Salut's socket
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.address)
+
+ self.httpd.handle_request()
+
+ # Receiver inform us he finished to download the file
+ self.q.expect('stream-iq', iq_type='result')
+
+ self._read_file_from_socket(s)
+
+class SendFileTest(FileTransferTest):
+ def __init__(self):
+ FileTransferTest.__init__(self)
+
+ self._actions = [self.connect, self.announce_contact, self.wait_for_contact,
+ self.check_ft_available, self.request_ft_channel, self.create_ft_channel,
+ self.got_send_iq, self.provide_file, self.client_request_file, self.send_file,
+ self.close_channel]
+
+ def check_ft_available(self):
+ properties = self.conn.GetAll(cs.CONN_IFACE_REQUESTS,
+ dbus_interface=PROPERTIES_IFACE)
+
+ assert ({cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_FILE_TRANSFER,
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT},
+ [cs.FT_CONTENT_HASH_TYPE,
+ cs.TARGET_HANDLE,
+ cs.TARGET_ID,
+ cs.FT_CONTENT_TYPE,
+ cs.FT_FILENAME,
+ cs.FT_SIZE,
+ cs.FT_CONTENT_HASH,
+ cs.FT_DESCRIPTION,
+ cs.FT_DATE,
+ cs.FT_INITIAL_OFFSET,
+ cs.FT_URI,
+ cs.FT_SERVICE_NAME,
+ cs.FT_METADATA],
+ ) in properties.get('RequestableChannelClasses', []),\
+ properties.get('RequestableChannelClasses')
+
+ def request_ft_channel(self, uri=True):
+ request = { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_FILE_TRANSFER,
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.TARGET_HANDLE: self.handle,
+
+ cs.FT_CONTENT_TYPE: self.file.content_type,
+ cs.FT_FILENAME: self.file.name,
+ cs.FT_SIZE: self.file.size,
+ cs.FT_CONTENT_HASH_TYPE: self.file.hash_type,
+ cs.FT_CONTENT_HASH:self.file.hash,
+ cs.FT_DESCRIPTION: self.file.description,
+ cs.FT_DATE: self.file.date,
+ cs.FT_INITIAL_OFFSET: 0 }
+
+ if self.service_name:
+ request[cs.FT_SERVICE_NAME] = self.service_name
+ if self.metadata:
+ request[cs.FT_METADATA] = dbus.Dictionary(self.metadata, signature='sas')
+
+ if uri:
+ request[cs.FT_URI] = self.file.uri
+
+ self.ft_path, props = self.conn.Requests.CreateChannel(request)
+
+ # org.freedesktop.Telepathy.Channel D-Bus properties
+ assert props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_FILE_TRANSFER
+ assert props[cs.INTERFACES] == []
+ assert props[cs.TARGET_HANDLE] == self.handle
+ assert props[cs.TARGET_ID] == self.contact_name
+ assert props[cs.TARGET_HANDLE_TYPE] == cs.HT_CONTACT
+ assert props[cs.REQUESTED] == True
+ assert props[cs.INITIATOR_HANDLE] == self.self_handle
+ assert props[cs.INITIATOR_ID] == self.self_handle_name
+
+ # org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties
+ assert props[cs.FT_STATE] == cs.FT_STATE_PENDING
+ assert props[cs.FT_CONTENT_TYPE] == self.file.content_type
+ assert props[cs.FT_FILENAME] == self.file.name
+ assert props[cs.FT_SIZE] == self.file.size
+ assert props[cs.FT_CONTENT_HASH_TYPE] == self.file.hash_type
+ assert props[cs.FT_CONTENT_HASH] == self.file.hash
+ assert props[cs.FT_DESCRIPTION] == self.file.description
+ assert props[cs.FT_DATE] == self.file.date
+ assert props[cs.FT_AVAILABLE_SOCKET_TYPES] == \
+ {cs.SOCKET_ADDRESS_TYPE_UNIX: [cs.SOCKET_ACCESS_CONTROL_LOCALHOST]}
+ assert props[cs.FT_TRANSFERRED_BYTES] == 0
+ assert props[cs.FT_INITIAL_OFFSET] == 0
+ if uri:
+ assertEquals(self.file.uri, props[cs.FT_URI])
+ else:
+ assertEquals('', props[cs.FT_URI])
+ assertEquals(self.service_name, props[cs.FT_SERVICE_NAME])
+ assertEquals(self.metadata, props[cs.FT_METADATA])
+
+ def got_send_iq(self):
+ conn_event, iq_event = self.q.expect_many(
+ EventPattern('incoming-connection', listener = self.listener),
+ EventPattern('stream-iq'))
+
+ self.incoming = conn_event.connection
+
+ self._check_oob_iq(iq_event)
+
+ def _check_oob_iq(self, iq_event):
+ assert iq_event.iq_type == 'set'
+ assert iq_event.connection == self.incoming
+ self.iq = iq_event.stanza
+ assert self.iq['to'] == self.contact_name
+ query = self.iq.firstChildElement()
+ assert query.uri == 'jabber:iq:oob'
+ url_node = xpath.queryForNodes("/iq/query/url", self.iq)[0]
+ assert url_node['type'] == 'file'
+ assert url_node['size'] == str(self.file.size)
+ assert url_node['mimeType'] == self.file.content_type
+ self.url = url_node.children[0]
+ _, self.host, self.filename, _, _, _ = urlparse.urlparse(self.url)
+ urllib.unquote(self.filename) == self.file.name
+ desc_node = xpath.queryForNodes("/iq/query/desc", self.iq)[0]
+ self.desc = desc_node.children[0]
+ assert self.desc == self.file.description
+
+ # Metadata forms
+ forms = extract_data_forms(xpath.queryForNodes('/iq/query/x', self.iq))
+
+ if self.service_name:
+ assertEquals({'ServiceName': [self.service_name]},
+ forms[ns.TP_FT_METADATA_SERVICE])
+ else:
+ assert ns.TP_FT_METADATA_SERVICE not in forms
+
+ if self.metadata:
+ assertEquals(self.metadata, forms[ns.TP_FT_METADATA])
+ else:
+ assert ns.TP_FT_METADATA not in forms
+
+ def provide_file(self):
+ self.address = self.ft_channel.ProvideFile(cs.SOCKET_ADDRESS_TYPE_UNIX,
+ cs.SOCKET_ACCESS_CONTROL_LOCALHOST, "", byte_arrays=True)
+
+ def client_request_file(self):
+ # Connect HTTP client to the CM and request the file
+ self.http = httplib.HTTPConnection(self.host)
+ self.http.request('GET', self.filename)
+
+ def _get_http_response(self):
+ response = self.http.getresponse()
+ assert (response.status, response.reason) == (200, 'OK')
+ data = response.read(self.file.size)
+ # Did we received the right file?
+ assert data == self.file.data
+
+ def send_file(self):
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.address)
+ s.send(self.file.data)
+
+ e = self.q.expect('dbus-signal', signal='TransferredBytesChanged')
+
+ count = e.args[0]
+ while count < self.file.size:
+ # Catch TransferredBytesChanged until we transfered all the data
+ e = self.q.expect('dbus-signal', signal='TransferredBytesChanged')
+ count = e.args[0]
+
+ self._get_http_response()
+
+ # Inform sender that we received all the file from the OOB transfer
+ reply = domish.Element(('', 'iq'))
+ reply['to'] = self.iq['from']
+ reply['from'] = self.iq['to']
+ reply['type'] = 'result'
+ reply['id'] = self.iq['id']
+ self.incoming.send(reply)
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_COMPLETED
+ assert reason == cs.FT_STATE_CHANGE_REASON_NONE
diff --git a/salut/tests/twisted/avahi/file-transfer/ft-client-caps.py b/salut/tests/twisted/avahi/file-transfer/ft-client-caps.py
new file mode 100644
index 000000000..40598936c
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/ft-client-caps.py
@@ -0,0 +1,423 @@
+
+"""
+Test FT capabilities with Connection.Interface.ContactCapabilities
+
+1. Receive presence and caps from contacts and check that
+GetContactCapabilities works correctly and that ContactCapabilitiesChanged is
+correctly received. Also check that GetContactAttributes gives the same
+results.
+
+- no FT cap at all
+- FT caps without metadata extension
+- FT caps with metadata extension
+- 1 FT cap with a service name
+- 2 FT caps with service names
+- 1 FT cap again, to test whether the caps cache works with FT services
+
+2. Test UpdateCapabilities and test that a presence stanza is sent to the
+contacts, test that the D-Bus signal ContactCapabilitiesChanged is fired for
+the self handle, ask Salut for its caps with an iq request, check the reply
+is correct, and ask Salut for its caps using D-Bus method
+GetContactCapabilities. Also check that GetContactAttributes gives the same
+results.
+
+Ensure that just a Requested=True channel class in a client filter doesn't
+make a FT service advertised as a cap.
+
+- no FT cap at all
+- 1 FT cap with no service name
+- 1 Requested=True FT cap with service name
+- 1 FT cap with service name
+- 1 FT cap with service name + 1 FT cap with no service name
+- 2 FT caps with service names
+- 1 FT cap with service name again, just for fun
+
+"""
+
+import dbus
+
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+from avahitest import txt_get_key
+import avahi
+
+from xmppstream import connect_to_stream, setup_stream_listener
+
+from twisted.words.xish import xpath
+
+from servicetest import assertEquals, assertLength, assertContains,\
+ assertDoesNotContain, sync_dbus, EventPattern
+from saluttest import exec_test, make_result_iq, sync_stream, make_presence, \
+ fixed_features
+import constants as cs
+
+from caps_helper import check_caps, compute_caps_hash, text_fixed_properties, \
+ text_allowed_properties, caps_contain, disco_caps, \
+ ft_fixed_properties, ft_allowed_properties, ft_allowed_properties_with_metadata
+import ns
+from config import PACKAGE_STRING
+
+def dict_union(a, b):
+ return dbus.Dictionary(a.items() + b.items(), signature='sv')
+
+no_service_fixed_properties = {
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_FILE_TRANSFER,
+ }
+bidir_daap_fixed_properties = dict_union(no_service_fixed_properties, {
+ cs.FT_SERVICE_NAME: 'daap'
+ })
+outgoing_daap_fixed_properties = dict_union(bidir_daap_fixed_properties, {
+ cs.REQUESTED : True,
+ })
+incoming_daap_fixed_properties = dict_union(bidir_daap_fixed_properties, {
+ cs.REQUESTED : False,
+ })
+http_fixed_properties = dict_union(no_service_fixed_properties, {
+ cs.FT_SERVICE_NAME: 'http',
+ })
+xiangqi_fixed_properties = dict_union(no_service_fixed_properties, {
+ cs.FT_SERVICE_NAME: 'com.example.Xiangqi',
+ })
+go_fixed_properties = dict_union(no_service_fixed_properties, {
+ cs.FT_SERVICE_NAME: 'com.example.Go',
+ })
+
+client = 'http://telepathy.freedesktop.org/fake-client'
+
+def assertSameElements(a, b):
+ assertEquals(sorted(a), sorted(b))
+
+def receive_caps(q, bus, conn, service, contact, contact_handle, features,
+ expected_caps, expect_disco=True, expect_ccc=True):
+
+ ver = compute_caps_hash([], features, {})
+ txt_record = { "txtvers": "1", "status": "avail",
+ "node": client, "ver": ver, "hash": "sha-1"}
+
+ listener, port = setup_stream_listener(q, contact)
+ AvahiAnnouncer(contact, "_presence._tcp", port, txt_record)
+
+ if expect_disco:
+ # Salut looks up our capabilities
+ e = q.expect('incoming-connection', listener=listener)
+ stream = e.connection
+
+ event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO,
+ connection=stream)
+ query_node = xpath.queryForNodes('/iq/query', event.stanza)[0]
+ assert query_node.attributes['node'] == \
+ client + '#' + ver
+
+ # send good reply
+ result = make_result_iq(event.stanza)
+ query = result.firstChildElement()
+ query['node'] = client + '#' + ver
+
+ for f in features:
+ feature = query.addElement('feature')
+ feature['var'] = f
+
+ stream.send(result)
+
+ if expect_ccc:
+ event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged')
+ announced_ccs, = event.args
+ assertSameElements(expected_caps, announced_ccs[contact_handle])
+ else:
+ if expect_disco:
+ # Make sure Salut's got the caps
+ sync_stream(q, stream)
+
+ caps = conn.ContactCapabilities.GetContactCapabilities([contact_handle])
+ assertSameElements(expected_caps, caps[contact_handle])
+
+ # test again, to check GetContactCapabilities does not have side effect
+ caps = conn.ContactCapabilities.GetContactCapabilities([contact_handle])
+ assertSameElements(expected_caps, caps[contact_handle])
+
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn.Contacts.GetContactAttributes(
+ [contact_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \
+ [contact_handle][cs.ATTR_CONTACT_CAPABILITIES]
+ assertSameElements(caps[contact_handle], caps_via_contacts_iface)
+
+ # close the connection and expect a new one to be opened by Salut
+ # the next time we need some discoing doing
+ if expect_disco:
+ stream.send('</stream:stream>')
+ stream.transport.loseConnection()
+ # pass some time so Salut knows the connection is lost and
+ # won't try and send stuff down a closed connection on the
+ # next test.
+ sync_dbus(bus, q, conn)
+
+def test_ft_caps_from_contact(q, bus, conn, service, contact):
+ contact_handle = conn.RequestHandles(cs.HT_CONTACT, [contact])[0]
+
+ # Check that we don't crash if we haven't seen any caps/presence for this
+ # contact yet.
+ caps = conn.ContactCapabilities.GetContactCapabilities([contact_handle])
+
+ basic_caps = [(text_fixed_properties, text_allowed_properties)]
+
+ # Since we don't know their caps, they should be omitted from the dict,
+ # rather than present with no caps, but all contacts have text chat caps.
+ assertEquals([], caps[contact_handle])
+
+ # send presence with no FT cap
+ # We don't expect ContactCapabilitiesChanged to be emitted here: we always
+ # assume people can do text channels.
+ receive_caps(q, bus, conn, service, contact, contact_handle, [], basic_caps,
+ expect_ccc=False)
+
+ # send presence with no mention of metadata
+ no_metadata_ft_caps = [
+ (text_fixed_properties, text_allowed_properties),
+ (ft_fixed_properties, ft_allowed_properties)
+ ]
+ receive_caps(q, bus, conn, service, contact, contact_handle,
+ [ns.IQ_OOB], no_metadata_ft_caps)
+
+ # send presence with generic FT caps including metadata from now on
+ generic_ft_caps = [
+ (text_fixed_properties, text_allowed_properties),
+ (ft_fixed_properties, ft_allowed_properties_with_metadata)
+ ]
+ generic_ft_features = [ns.IQ_OOB, ns.TP_FT_METADATA]
+ receive_caps(q, bus, conn, service, contact, contact_handle,
+ generic_ft_features, generic_ft_caps)
+
+ # send presence with 1 FT cap with a service
+ daap_caps = generic_ft_caps + [
+ (bidir_daap_fixed_properties, ft_allowed_properties + [cs.FT_METADATA])]
+ receive_caps(q, bus, conn, service, contact, contact_handle,
+ generic_ft_features + [ns.TP_FT_METADATA + '#daap'], daap_caps)
+
+ # send presence with 2 FT caps
+ daap_xiangqi_caps = daap_caps + [
+ (xiangqi_fixed_properties, ft_allowed_properties + [cs.FT_METADATA])]
+ receive_caps(q, bus, conn, service, contact, contact_handle,
+ generic_ft_features + [ns.TP_FT_METADATA + '#com.example.Xiangqi',
+ ns.TP_FT_METADATA + '#daap',
+ ], daap_xiangqi_caps)
+
+ # send presence with 1 FT cap again
+ # Salut does not look up our capabilities because of the cache
+ receive_caps(q, bus, conn, service, contact, contact_handle,
+ generic_ft_features + [ns.TP_FT_METADATA + '#daap'], daap_caps,
+ expect_disco=False)
+
+def advertise_caps(q, bus, conn, service, filters, expected_features, unexpected_features,
+ expected_caps):
+ # make sure nothing from a previous update is still running
+ sync_dbus(bus, q, conn)
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(1, [self_handle])[0]
+ ret_caps = conn.ContactCapabilities.UpdateCapabilities(
+ [(cs.CLIENT + '.Foo', filters, [])])
+
+ presence, event_dbus = q.expect_many(
+ EventPattern('service-resolved', service=service),
+ EventPattern('dbus-signal', signal='ContactCapabilitiesChanged')
+ )
+ assertLength(1, event_dbus.args)
+ signaled_caps = event_dbus.args[0]
+
+ outbound = connect_to_stream(q, 'test@foobar',
+ self_handle_name, str(presence.pt), presence.port)
+
+ e = q.expect('connection-result')
+ assert e.succeeded, e.reason
+
+ e = q.expect('stream-opened', connection=outbound)
+
+ # Expect Salut to reply with the correct caps
+ event, namespaces = disco_caps(q, outbound, presence.txt)
+
+ assertSameElements(expected_caps, signaled_caps[self_handle])
+
+ assertContains(ns.TP_FT_METADATA, namespaces)
+
+ for var in expected_features:
+ assertContains(var, namespaces)
+
+ for var in unexpected_features:
+ assertDoesNotContain(var, namespaces)
+
+ # Check our own caps
+ caps = conn.ContactCapabilities.GetContactCapabilities([self_handle])
+ assertSameElements(expected_caps, caps[self_handle])
+
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn.Contacts.GetContactAttributes(
+ [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \
+ [self_handle][cs.ATTR_CONTACT_CAPABILITIES]
+ assertSameElements(caps[self_handle], caps_via_contacts_iface)
+
+ # close things...
+ outbound.send('</stream:stream>')
+ sync_dbus(bus, q, conn)
+ outbound.transport.loseConnection()
+
+def test_ft_caps_to_contact(q, bus, conn, service):
+ self_handle = conn.GetSelfHandle()
+
+ basic_caps = [
+ (text_fixed_properties, text_allowed_properties),
+ (ft_fixed_properties, ft_allowed_properties_with_metadata),
+ ]
+ daap_caps = basic_caps + [
+ (bidir_daap_fixed_properties, ft_allowed_properties + [cs.FT_METADATA]),
+ ]
+ xiangqi_caps = basic_caps + [
+ (xiangqi_fixed_properties, ft_allowed_properties + [cs.FT_METADATA]),
+ ]
+ xiangqi_go_caps = xiangqi_caps + [
+ (go_fixed_properties, ft_allowed_properties + [cs.FT_METADATA]),
+ ]
+ go_caps = basic_caps + [
+ (go_fixed_properties, ft_allowed_properties + [cs.FT_METADATA]),
+ ]
+
+ #
+ # Check our own caps
+ #
+ caps = conn.ContactCapabilities.GetContactCapabilities([self_handle])
+ assertEquals(basic_caps, caps[self_handle])
+
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn.Contacts.GetContactAttributes(
+ [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \
+ [self_handle][cs.ATTR_CONTACT_CAPABILITIES]
+ assertEquals(caps[self_handle], caps_via_contacts_iface)
+
+ #
+ # Advertise nothing
+ #
+ conn.ContactCapabilities.UpdateCapabilities(
+ [(cs.CLIENT + '.Foo', {}, [])])
+
+ # Check our own caps
+ caps = conn.ContactCapabilities.GetContactCapabilities([self_handle])
+ assertLength(1, caps)
+ assertEquals(basic_caps, caps[self_handle])
+
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn.Contacts.GetContactAttributes(
+ [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \
+ [self_handle][cs.ATTR_CONTACT_CAPABILITIES]
+ assertEquals(caps[self_handle], caps_via_contacts_iface)
+
+ sync_dbus(bus, q, conn)
+
+ #
+ # Advertise FT but with no service name
+ #
+ conn.ContactCapabilities.UpdateCapabilities(
+ [(cs.CLIENT + '.Foo', [no_service_fixed_properties], [])])
+
+ # Check our own caps
+ caps = conn.ContactCapabilities.GetContactCapabilities([self_handle])
+ assertLength(1, caps)
+ assertEquals(basic_caps, caps[self_handle])
+
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn.Contacts.GetContactAttributes(
+ [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \
+ [self_handle][cs.ATTR_CONTACT_CAPABILITIES]
+ assertEquals(caps[self_handle], caps_via_contacts_iface)
+
+ sync_dbus(bus, q, conn)
+
+ #
+ # Advertise a Requested=True FT cap
+ #
+ conn.ContactCapabilities.UpdateCapabilities(
+ [(cs.CLIENT + '.Foo', [outgoing_daap_fixed_properties], [])])
+
+ # Check our own caps
+ caps = conn.ContactCapabilities.GetContactCapabilities([self_handle])
+ assertLength(1, caps)
+ assertEquals(basic_caps, caps[self_handle])
+
+ # check the Contacts interface give the same caps
+ caps_via_contacts_iface = conn.Contacts.GetContactAttributes(
+ [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \
+ [self_handle][cs.ATTR_CONTACT_CAPABILITIES]
+ assertEquals(caps[self_handle], caps_via_contacts_iface)
+
+ advertise_caps(q, bus, conn, service,
+ [bidir_daap_fixed_properties],
+ [ns.TP_FT_METADATA + '#daap'],
+ [ns.TP_FT_METADATA + '#http',
+ ns.TP_FT_METADATA + '#com.example.Go',
+ ns.TP_FT_METADATA + '#com.example.Xiangqi',
+ ],
+ daap_caps)
+
+ advertise_caps(q, bus, conn, service,
+ [xiangqi_fixed_properties, no_service_fixed_properties],
+ [ns.TP_FT_METADATA + '#com.example.Xiangqi'],
+ [ns.TP_FT_METADATA + '#daap',
+ ns.TP_FT_METADATA + '#http',
+ ns.TP_FT_METADATA + '#com.example.Go',
+ ],
+ xiangqi_caps)
+
+ advertise_caps(q, bus, conn, service,
+ [xiangqi_fixed_properties, go_fixed_properties],
+ [ns.TP_FT_METADATA + '#com.example.Xiangqi',
+ ns.TP_FT_METADATA + '#com.example.Go',
+ ],
+ [ns.TP_FT_METADATA + '#http',
+ ns.TP_FT_METADATA + '#daap',
+ ],
+ xiangqi_go_caps)
+
+ advertise_caps(q, bus, conn, service,
+ [go_fixed_properties],
+ [ns.TP_FT_METADATA + '#com.example.Go',
+ ],
+ [ns.TP_FT_METADATA + '#http',
+ ns.TP_FT_METADATA + '#daap',
+ ns.TP_FT_METADATA + '#com.example.Xiangqi',
+ ],
+ go_caps)
+
+def test(q, bus, conn):
+ # last value of the "ver" key we resolved. We use it to be sure that the
+ # modified caps has already be announced.
+ old_ver = None
+
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0, 0])
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(1, [self_handle])[0]
+
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+ e = q.expect('service-resolved', service = service)
+ ver = txt_get_key(e.txt, "ver")
+ while ver == old_ver:
+ # be sure that the announced caps actually changes
+ e = q.expect('service-resolved', service=service)
+ ver = txt_get_key(e.txt, "ver")
+ old_ver = ver
+
+ caps = compute_caps_hash(['client/pc//%s' % PACKAGE_STRING],
+ fixed_features, {})
+ assertEquals(caps, ver)
+
+ test_ft_caps_from_contact(q, bus, conn, service, 'yo@momma')
+
+ test_ft_caps_to_contact(q, bus, conn, service)
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/file-transfer/ichat-receive-directory.py b/salut/tests/twisted/avahi/file-transfer/ichat-receive-directory.py
new file mode 100644
index 000000000..618c54b75
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/ichat-receive-directory.py
@@ -0,0 +1,43 @@
+from saluttest import exec_test
+from avahitest import skip_if_another_llxmpp
+from xmppstream import connect_to_stream, OutgoingXmppiChatStream
+from twisted.words.xish import domish
+
+from file_transfer_helper import ReceiveFileTest
+
+class IChatReceiveDirectory(ReceiveFileTest):
+ def connect_to_salut(self):
+ host, port = self._resolve_salut_presence()
+
+ self.outbound = connect_to_stream(self.q, self.contact_name,
+ self.self_handle_name, host, port, OutgoingXmppiChatStream)
+
+ e = self.q.expect('connection-result')
+ assert e.succeeded, e.reason
+ self.q.expect('stream-opened', connection = self.outbound)
+
+ def send_ft_offer_iq(self):
+ iq = domish.Element((None, 'iq'))
+ iq['to'] = self.self_handle_name
+ # no 'from' attribute
+ iq['type'] = 'set'
+ iq['id'] = 'iChat_A1FB5D95'
+ query = iq.addElement(('jabber:iq:oob', 'query'))
+ url = 'http://127.0.0.1:%u/gibber-file-transfer-0/my_directory/' % (self.httpd.server_port)
+ url_node = query.addElement('url', content=url)
+ url_node['type'] = 'directory'
+ url_node['size'] = '1000'
+ url_node['nfiles'] = '5'
+ url_node['posixflags'] = '00000180'
+ self.outbound.send(iq)
+
+ # Send an error as we don't support directory transfer for now
+ self.q.expect('stream-iq', iq_type='error')
+
+ # stop the test
+ return True
+
+if __name__ == '__main__':
+ skip_if_another_llxmpp()
+ test = IChatReceiveDirectory()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/ichat-receive-file.py b/salut/tests/twisted/avahi/file-transfer/ichat-receive-file.py
new file mode 100644
index 000000000..1742a0356
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/ichat-receive-file.py
@@ -0,0 +1,44 @@
+import urllib
+from avahitest import skip_if_another_llxmpp
+from saluttest import exec_test
+from xmppstream import connect_to_stream, OutgoingXmppiChatStream
+from twisted.words.xish import domish
+
+from file_transfer_helper import ReceiveFileTest
+
+class IChatReceiveFile(ReceiveFileTest):
+ service_name = ''
+ metadata = {}
+
+ def connect_to_salut(self):
+ host, port = self._resolve_salut_presence()
+
+ self.outbound = connect_to_stream(self.q, self.contact_name,
+ self.self_handle_name, host, port, OutgoingXmppiChatStream)
+
+ e = self.q.expect('connection-result')
+ assert e.succeeded, e.reason
+ self.q.expect('stream-opened', connection = self.outbound)
+
+ def send_ft_offer_iq(self):
+ # connected to Salut, now send the IQ
+ iq = domish.Element((None, 'iq'))
+ iq['to'] = self.self_handle_name
+ # no 'from' attribute
+ iq['type'] = 'set'
+ iq['id'] = 'iChat_A1FB5D95'
+ query = iq.addElement(('jabber:iq:oob', 'query'))
+ url = 'http://127.0.0.1:%u/gibber-file-transfer-0/%s' % (self.httpd.server_port,
+ urllib.quote(self.file.name))
+ url_node = query.addElement('url', content="\n%s" % url) #iChat adds a \n before the URL
+ url_node['type'] = 'file'
+ url_node['size'] = str(self.file.size)
+ url_node['mimeType'] = self.file.content_type
+ url_node['posixflags'] = '00000180'
+ query.addElement('desc', content=self.file.description)
+ self.outbound.send(iq)
+
+if __name__ == '__main__':
+ skip_if_another_llxmpp()
+ test = IChatReceiveFile()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/ichat-send-file-declined.py b/salut/tests/twisted/avahi/file-transfer/ichat-send-file-declined.py
new file mode 100644
index 000000000..039138616
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/ichat-send-file-declined.py
@@ -0,0 +1,55 @@
+from saluttest import exec_test
+from xmppstream import IncomingXmppiChatStream, setup_stream_listener
+from twisted.words.xish import domish
+from avahitest import get_host_name, AvahiAnnouncer
+
+from file_transfer_helper import SendFileTest
+import constants as cs
+
+class IChatSendFileDeclined(SendFileTest):
+ CONTACT_NAME = 'test-ft'
+
+ # we need to unset these so we won't try and send them and then
+ # because we don't have the right caps, salut complains
+ service_name = ''
+ metadata = {}
+
+ def announce_contact(self, name=CONTACT_NAME):
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ self.contact_name = '%s@%s' % (name, get_host_name())
+ self.listener, port = setup_stream_listener(self.q, self.contact_name,
+ protocol=IncomingXmppiChatStream)
+
+ AvahiAnnouncer(self.contact_name, "_presence._tcp", port, basic_txt)
+
+ def got_send_iq(self):
+ SendFileTest.got_send_iq(self)
+
+ # Receiver declines the file offer
+ reply = domish.Element(('', 'iq'))
+ reply['to'] = self.iq['from']
+ reply['type'] = 'error'
+ reply['id'] = self.iq['id']
+ error_node = reply.addElement((None, 'error'), content='User declined to receive the file')
+ error_node['type'] = '406'
+ self.incoming.send(reply)
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_CANCELLED, state
+ assert reason == cs.FT_STATE_CHANGE_REASON_REMOTE_STOPPED
+
+ transferred = self.ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'TransferredBytes')
+ # no byte has been transferred as the file was declined
+ assert transferred == 0
+
+ self.channel.Close()
+ self.q.expect('dbus-signal', signal='Closed')
+
+ # stop test
+ return True
+
+if __name__ == '__main__':
+ test = IChatSendFileDeclined()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/ichat-send-file.py b/salut/tests/twisted/avahi/file-transfer/ichat-send-file.py
new file mode 100644
index 000000000..f2c35ce24
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/ichat-send-file.py
@@ -0,0 +1,60 @@
+import httplib
+import struct
+
+from saluttest import exec_test
+from xmppstream import IncomingXmppiChatStream, setup_stream_listener
+from avahitest import get_host_name, AvahiAnnouncer
+
+from file_transfer_helper import SendFileTest
+
+class IChatSendFileDeclined(SendFileTest):
+ CONTACT_NAME = 'test-ft'
+
+ # we need to unset these so we won't try and send them and then
+ # because we don't have the right caps, salut complains
+ service_name = ''
+ metadata = {}
+
+ def announce_contact(self, name=CONTACT_NAME):
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ self.contact_name = '%s@%s' % (name, get_host_name())
+ self.listener, port = setup_stream_listener(self.q, self.contact_name,
+ protocol=IncomingXmppiChatStream)
+
+ AvahiAnnouncer(self.contact_name, "_presence._tcp", port, basic_txt)
+
+ def client_request_file(self):
+ # Connect HTTP client to the CM and request the file
+ self.http = httplib.HTTPConnection(self.host)
+ headers = {'Accept-Encoding': 'AppleSingle'}
+ self.http.request('GET', self.filename, headers=headers)
+
+ def _get_http_response(self):
+ # Header is 38 bytes
+ APPLE_SINGLE_HEADER_SIZE = 38
+
+ response = self.http.getresponse()
+ assert (response.status, response.reason) == (200, 'OK')
+ data = response.read(self.file.size + APPLE_SINGLE_HEADER_SIZE)
+
+ # File is using the AppleSingle encoding
+ magic_number, version, f1, f2, f3, f4, nb_entry, entry_id, offset, length = struct.unpack(
+ '>II4IhIII', data[:APPLE_SINGLE_HEADER_SIZE])
+
+ assert hex(magic_number) == '0x51600'
+ assert hex(version) == '0x20000'
+ # filler
+ assert f1 == f2 == f3 == f4 == 0
+ assert nb_entry == 1
+ # data fork
+ assert entry_id == 1
+ assert offset == APPLE_SINGLE_HEADER_SIZE
+ assert length == self.file.size
+
+ # Did we received the right file?
+ assert data[APPLE_SINGLE_HEADER_SIZE:] == self.file.data
+
+if __name__ == '__main__':
+ test = IChatSendFileDeclined()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/metadata.py b/salut/tests/twisted/avahi/file-transfer/metadata.py
new file mode 100644
index 000000000..41811ab8e
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/metadata.py
@@ -0,0 +1,83 @@
+# The 'normal' cases are tested with test-receive-file.py and test-send-file-provide-immediately.py
+# This file tests some corner cases
+import dbus
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest, SendFileTest
+from servicetest import call_async
+
+import constants as cs
+
+class SendFileNoMetadata(SendFileTest):
+ # this is basically the equivalent of calling CreateChannel
+ # without these two properties
+ service_name = ''
+ metadata = {}
+
+class ReceiveFileNoMetadata(ReceiveFileTest):
+ service_name = ''
+ metadata = {}
+
+class SendFileBadProps(SendFileTest):
+ metadata = {'FORM_TYPE': 'this shouldnt be allowed'}
+
+ def request_ft_channel(self):
+ request = { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_FILE_TRANSFER,
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.TARGET_HANDLE: self.handle,
+ cs.FT_CONTENT_TYPE: self.file.content_type,
+ cs.FT_FILENAME: self.file.name,
+ cs.FT_SIZE: self.file.size,
+ cs.FT_CONTENT_HASH_TYPE: self.file.hash_type,
+ cs.FT_CONTENT_HASH: self.file.hash,
+ cs.FT_DESCRIPTION: self.file.description,
+ cs.FT_DATE: self.file.date,
+ cs.FT_INITIAL_OFFSET: 0,
+ cs.FT_SERVICE_NAME: self.service_name,
+ cs.FT_METADATA: dbus.Dictionary(self.metadata, signature='sas')}
+
+ call_async(self.q, self.conn.Requests, 'CreateChannel', request)
+
+ # FORM_TYPE is not allowed, soz
+ self.q.expect('dbus-error', method='CreateChannel', name=cs.INVALID_ARGUMENT)
+
+ return True
+
+class SendFileBadContact(SendFileTest):
+ def announce_contact(self):
+ SendFileTest.announce_contact(self, metadata=False)
+
+ def request_ft_channel(self):
+ request = { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_FILE_TRANSFER,
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.TARGET_HANDLE: self.handle,
+ cs.FT_CONTENT_TYPE: self.file.content_type,
+ cs.FT_FILENAME: self.file.name,
+ cs.FT_SIZE: self.file.size,
+ cs.FT_CONTENT_HASH_TYPE: self.file.hash_type,
+ cs.FT_CONTENT_HASH: self.file.hash,
+ cs.FT_DESCRIPTION: self.file.description,
+ cs.FT_DATE: self.file.date,
+ cs.FT_INITIAL_OFFSET: 0,
+ cs.FT_SERVICE_NAME: self.service_name,
+ cs.FT_METADATA: dbus.Dictionary(self.metadata, signature='sas')}
+
+ call_async(self.q, self.conn.Requests, 'CreateChannel', request)
+
+ # no support for metadata, soz
+ self.q.expect('dbus-error', method='CreateChannel', name=cs.NOT_CAPABLE)
+
+ return True
+
+if __name__ == '__main__':
+ test = SendFileNoMetadata()
+ exec_test(test.test)
+
+ test = ReceiveFileNoMetadata()
+ exec_test(test.test)
+
+ test = SendFileBadProps()
+ exec_test(test.test)
+
+ test = SendFileBadContact()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-and-send-file.py b/salut/tests/twisted/avahi/file-transfer/receive-and-send-file.py
new file mode 100644
index 000000000..1c6dd418e
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-and-send-file.py
@@ -0,0 +1,36 @@
+import urlparse
+import urllib
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest, SendFileTest
+
+from twisted.words.xish import xpath
+
+class ReceiveAndSendFileTest(ReceiveFileTest, SendFileTest):
+ def __init__(self):
+ ReceiveFileTest.__init__(self)
+ SendFileTest.__init__(self)
+
+ self._actions = [self.connect, self.announce_contact,self.wait_for_contact,
+ self.connect_to_salut,
+ # receive file
+ self.setup_http_server, self.send_ft_offer_iq, self.check_new_channel,
+ self.create_ft_channel, self.set_uri, self.accept_file,
+ self.receive_file, self.close_channel,
+
+ # now send a file. We'll reuse the same XMPP connection
+ self.request_ft_channel, self.create_ft_channel, self.got_send_iq,
+ self.provide_file, self.client_request_file, self.send_file,
+ self.close_channel]
+
+ def got_send_iq(self):
+ # reuse the existing XMPP connection
+ self.incoming = self.outbound
+
+ iq_event = self.q.expect('stream-iq')
+
+ self._check_oob_iq(iq_event)
+
+if __name__ == '__main__':
+ test = ReceiveAndSendFileTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file-and-disconnect.py b/salut/tests/twisted/avahi/file-transfer/receive-file-and-disconnect.py
new file mode 100644
index 000000000..39dca2169
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file-and-disconnect.py
@@ -0,0 +1,18 @@
+import socket
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest
+
+class ReceiveFileAndDisconnectTest(ReceiveFileTest):
+ def receive_file(self):
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.address)
+
+ # disconnect
+ self.conn.Disconnect()
+ self.q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+ return True
+
+if __name__ == '__main__':
+ test = ReceiveFileAndDisconnectTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file-and-sender-disconnect-while-pending.py b/salut/tests/twisted/avahi/file-transfer/receive-file-and-sender-disconnect-while-pending.py
new file mode 100644
index 000000000..e3bb73db9
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file-and-sender-disconnect-while-pending.py
@@ -0,0 +1,32 @@
+import dbus
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest
+
+import constants as cs
+
+class ReceiveFileAndSenderDisconnectWhilePendingTest(ReceiveFileTest):
+ def accept_file(self):
+ # The sender of the file disconnects
+ self.outbound.transport.loseConnection()
+ self.contact_service.stop()
+
+ self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+
+ # We can't accept the transfer now
+ try:
+ self.ft_channel.AcceptFile(cs.SOCKET_ADDRESS_TYPE_UNIX,
+ cs.SOCKET_ACCESS_CONTROL_LOCALHOST, "", 0, byte_arrays=True)
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == cs.NOT_AVAILABLE
+ else:
+ assert False, "Should raise NotAvailable error"
+
+ self.close_channel()
+
+ # stop the test
+ return True
+
+if __name__ == '__main__':
+ test = ReceiveFileAndSenderDisconnectWhilePendingTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file-and-sender-disconnect-while-transfering.py b/salut/tests/twisted/avahi/file-transfer/receive-file-and-sender-disconnect-while-transfering.py
new file mode 100644
index 000000000..fef5cf266
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file-and-sender-disconnect-while-transfering.py
@@ -0,0 +1,29 @@
+import dbus
+import socket
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest
+
+class ReceiveFileAndSenderDisconnectWhileTransfering(ReceiveFileTest):
+ def accept_file(self):
+ ReceiveFileTest.accept_file(self)
+
+ # The sender of the file disconnects
+ self.outbound.transport.loseConnection()
+ self.contact_service.stop()
+ # we continue the transfer as it was already accepted
+
+ def receive_file(self):
+ # Connect to Salut's socket
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.address)
+
+ self.httpd.handle_request()
+
+ # Salut doesn't send the IQ reply as the XMPP connection was broken
+
+ self._read_file_from_socket(s)
+
+if __name__ == '__main__':
+ test = ReceiveFileAndSenderDisconnectWhileTransfering()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file-and-xmpp-disconnect.py b/salut/tests/twisted/avahi/file-transfer/receive-file-and-xmpp-disconnect.py
new file mode 100644
index 000000000..63fa2dd3d
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file-and-xmpp-disconnect.py
@@ -0,0 +1,28 @@
+import dbus
+
+import socket
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest
+
+class ReceiveFileAndXmppDisconnectTest(ReceiveFileTest):
+ def accept_file(self):
+ # The XMPP connection is broken
+ self.outbound.transport.loseConnection()
+
+ ReceiveFileTest.accept_file(self)
+
+ def receive_file(self):
+ # Connect to Salut's socket
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.address)
+
+ self.httpd.handle_request()
+
+ # Salut doesn't send the IQ reply as the XMPP connection was broken
+
+ self._read_file_from_socket(s)
+
+if __name__ == '__main__':
+ test = ReceiveFileAndXmppDisconnectTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file-cancelled-immediately.py b/salut/tests/twisted/avahi/file-transfer/receive-file-cancelled-immediately.py
new file mode 100644
index 000000000..fabf826e9
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file-cancelled-immediately.py
@@ -0,0 +1,29 @@
+import socket
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest
+
+import constants as cs
+
+class ReceiveFileCancelledImmediatelyTest(ReceiveFileTest):
+ def accept_file(self):
+ # sender cancels FT immediately so stop to listen to the HTTP socket
+ # before we accept the transfer.
+ self.httpd.server_close()
+
+ ReceiveFileTest.accept_file(self)
+
+ def receive_file(self):
+ # Connect to Salut's socket
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.address)
+
+ # Salut can't connect to download the file
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_CANCELLED
+ assert reason == cs.FT_STATE_CHANGE_REASON_REMOTE_STOPPED
+
+if __name__ == '__main__':
+ test = ReceiveFileCancelledImmediatelyTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file-decline.py b/salut/tests/twisted/avahi/file-transfer/receive-file-decline.py
new file mode 100644
index 000000000..c2964235a
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file-decline.py
@@ -0,0 +1,44 @@
+from servicetest import make_channel_proxy
+from saluttest import exec_test
+
+from file_transfer_helper import ReceiveFileTest
+
+import constants as cs
+
+class ReceiveFileDeclineTest(ReceiveFileTest):
+ def accept_file(self):
+ # decline FT
+ self. channel.Close()
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_CANCELLED
+ assert reason == cs.FT_STATE_CHANGE_REASON_LOCAL_STOPPED
+ self.q.expect('dbus-signal', signal='Closed')
+
+ # Re send offer (this is a regression test as Salut used to crash at this
+ # point)
+ self.send_ft_offer_iq()
+
+ e = self.q.expect('dbus-signal', signal='NewChannels')
+ channels = e.args[0]
+ assert len(channels) == 1
+ path, props = channels[0]
+
+ channel = make_channel_proxy(self.conn, path, 'Channel')
+
+ # decline FT
+ channel.Close()
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_CANCELLED
+ assert reason == cs.FT_STATE_CHANGE_REASON_LOCAL_STOPPED
+ self.q.expect('dbus-signal', signal='Closed')
+
+ # stop test
+ return True
+
+if __name__ == '__main__':
+ test = ReceiveFileDeclineTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file-ipv6.py b/salut/tests/twisted/avahi/file-transfer/receive-file-ipv6.py
new file mode 100644
index 000000000..a525db021
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file-ipv6.py
@@ -0,0 +1,80 @@
+import avahi
+import urllib
+import BaseHTTPServer
+import SocketServer
+import socket
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest
+
+from avahitest import AvahiAnnouncer, get_host_name, AvahiListener,\
+ check_ipv6_enabled
+from xmppstream import connect_to_stream6, setup_stream_listener6
+
+from twisted.words.xish import domish
+
+class TestReceiveFileIPv6(ReceiveFileTest):
+ CONTACT_NAME = 'test-ft'
+
+ service_name = ''
+ metadata = {}
+
+ def announce_contact(self, name=CONTACT_NAME):
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ self.contact_name = '%s@%s' % (name, get_host_name())
+ self.listener, port = setup_stream_listener6(self.q, self.contact_name)
+
+ self.contact_service = AvahiAnnouncer(self.contact_name, "_presence._tcp", port,
+ basic_txt, proto=avahi.PROTO_INET6)
+
+ if not check_ipv6_enabled():
+ print "Skipped test as IPv6 doesn't seem to be available"
+ return True
+
+ def _resolve_salut_presence(self):
+ AvahiListener(self.q).listen_for_service("_presence._tcp")
+ e = self.q.expect('service-added', name = self.self_handle_name,
+ protocol = avahi.PROTO_INET6)
+ service = e.service
+ service.resolve()
+
+ e = self.q.expect('service-resolved', service = service,
+ protocol = avahi.PROTO_INET6)
+ return str(e.pt), e.port
+
+ def connect_to_salut(self):
+ host, port = self._resolve_salut_presence()
+
+ self.outbound = connect_to_stream6(self.q, self.contact_name,
+ self.self_handle_name, host, port)
+
+ e = self.q.expect('connection-result')
+ assert e.succeeded, e.reason
+ self.q.expect('stream-opened', connection = self.outbound)
+
+ def send_ft_offer_iq(self):
+ iq = domish.Element((None, 'iq'))
+ iq['to'] = self.self_handle_name
+ iq['from'] = self.contact_name
+ iq['type'] = 'set'
+ iq['id'] = 'gibber-file-transfer-0'
+ query = iq.addElement(('jabber:iq:oob', 'query'))
+ url = 'http://[::1]:%u/gibber-file-transfer-0/%s' % \
+ (self.httpd.server_port, urllib.quote(self.file.name))
+ url_node = query.addElement('url', content=url)
+ url_node['type'] = 'file'
+ url_node['size'] = str(self.file.size)
+ url_node['mimeType'] = self.file.content_type
+ query.addElement('desc', content=self.file.description)
+ self.outbound.send(iq)
+
+ def _get_http_server_class(self):
+ class HTTPServer6(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
+ address_family = getattr(socket, 'AF_INET6', None)
+
+ return HTTPServer6
+
+if __name__ == '__main__':
+ test = TestReceiveFileIPv6()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file-not-found.py b/salut/tests/twisted/avahi/file-transfer/receive-file-not-found.py
new file mode 100644
index 000000000..50244a164
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file-not-found.py
@@ -0,0 +1,58 @@
+import os
+import socket
+import BaseHTTPServer
+import urllib
+
+from twisted.words.xish import xpath
+
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest
+
+import constants as cs
+
+class ReceiveFileNotFound(ReceiveFileTest):
+ def setup_http_server(self):
+ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ def do_GET(self_):
+ # is that the right file ?
+ filename = self_.path.rsplit('/', 2)[-1]
+ assert filename == urllib.quote(self.file.name)
+
+ self_.send_response(404)
+ self_.end_headers()
+ self_.wfile.write(self.file.data)
+
+ def log_message(self, format, *args):
+ if 'CHECK_TWISTED_VERBOSE' in os.environ:
+ BaseHTTPServer.BaseHTTPRequestHandler.log_message(self, format, *args)
+
+ self.httpd = BaseHTTPServer.HTTPServer(('', 0), HTTPHandler)
+
+ def receive_file(self):
+ # Connect to Salut's socket
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.address)
+
+ self.httpd.handle_request()
+
+ # Receiver inform us he can't download the file
+ e = self.q.expect('stream-iq', iq_type='error')
+ iq = e.stanza
+ error_node = xpath.queryForNodes("/iq/error", iq)[0]
+ assert error_node['code'] == '404'
+ assert error_node['type'] == 'cancel'
+ not_found_node = error_node.firstChildElement()
+ assert not_found_node.name == 'item-not-found'
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_CANCELLED
+ assert reason == cs.FT_STATE_CHANGE_REASON_LOCAL_ERROR
+
+ transferred = self.ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'TransferredBytes')
+ # no byte has been transferred as the transfer failed
+ assert transferred == 0
+
+if __name__ == '__main__':
+ test = ReceiveFileNotFound()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/receive-file.py b/salut/tests/twisted/avahi/file-transfer/receive-file.py
new file mode 100644
index 000000000..f98c6bf37
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/receive-file.py
@@ -0,0 +1,6 @@
+from saluttest import exec_test
+from file_transfer_helper import ReceiveFileTest
+
+if __name__ == '__main__':
+ test = ReceiveFileTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/send-file-and-cancel-immediately.py b/salut/tests/twisted/avahi/file-transfer/send-file-and-cancel-immediately.py
new file mode 100644
index 000000000..e2cb5da5b
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/send-file-and-cancel-immediately.py
@@ -0,0 +1,41 @@
+import errno
+import httplib
+import socket
+
+from saluttest import exec_test
+from file_transfer_helper import SendFileTest
+
+import constants as cs
+
+class SendFileAndCancelImmediatelyTest(SendFileTest):
+ def provide_file(self):
+ SendFileTest.provide_file(self)
+
+ # cancel the transfer before the receiver accepts it
+ self.channel.Close()
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_CANCELLED
+ assert reason == cs.FT_STATE_CHANGE_REASON_LOCAL_STOPPED
+
+ self.q.expect('dbus-signal', signal='Closed')
+
+ def client_request_file(self):
+ # Connect HTTP client to the CM and request the file
+ http = httplib.HTTPConnection(self.host)
+ # can't retry the file as the transfer was cancelled
+ try:
+ http.request('GET', self.filename)
+ except socket.error, e:
+ code, msg = e.args
+ assert errno.errorcode[code] == 'ECONNREFUSED'
+ else:
+ assert False, "Should raise a socket error"
+
+ # stop test
+ return True
+
+if __name__ == '__main__':
+ test = SendFileAndCancelImmediatelyTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/send-file-and-disconnect.py b/salut/tests/twisted/avahi/file-transfer/send-file-and-disconnect.py
new file mode 100644
index 000000000..5090da52c
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/send-file-and-disconnect.py
@@ -0,0 +1,17 @@
+from saluttest import exec_test
+from file_transfer_helper import SendFileTest
+
+class SendFileAndDisconnectTest(SendFileTest):
+ def provide_file(self):
+ SendFileTest.provide_file(self)
+
+ # regression test: Salut used to crash at this point
+ self.conn.Disconnect()
+ self.q.expect('dbus-signal', signal='StatusChanged', args=[2L, 1L])
+
+ # stop test
+ return True
+
+if __name__ == '__main__':
+ test = SendFileAndDisconnectTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/send-file-declined.py b/salut/tests/twisted/avahi/file-transfer/send-file-declined.py
new file mode 100644
index 000000000..28e3157df
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/send-file-declined.py
@@ -0,0 +1,41 @@
+from saluttest import exec_test
+from file_transfer_helper import SendFileTest
+
+from twisted.words.xish import domish
+import constants as cs
+
+class SendFileDeclinedTest(SendFileTest):
+ def got_send_iq(self):
+ SendFileTest.got_send_iq(self)
+
+ # Receiver declines the file offer
+ reply = domish.Element(('', 'iq'))
+ reply['to'] = self.iq['from']
+ reply['from'] = self.iq['to']
+ reply['type'] = 'error'
+ reply['id'] = self.iq['id']
+ query = reply.addElement(('jabber:iq:oob', 'query'))
+ url_node = query.addElement('url', content=self.url)
+ query.addElement('desc', content=self.desc)
+ error_node = reply.addElement((None, 'error'))
+ error_node['code'] = '406'
+ error_node['type'] = 'modify'
+ not_acceptable_node = error_node.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas',
+ 'not-acceptable'))
+ self.incoming.send(reply)
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_CANCELLED, state
+ assert reason == cs.FT_STATE_CHANGE_REASON_REMOTE_STOPPED
+
+ transferred = self.ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'TransferredBytes')
+ # no byte has been transferred as the file was declined
+ assert transferred == 0
+
+ # stop test
+ return True
+
+if __name__ == '__main__':
+ test = SendFileDeclinedTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/send-file-ipv6.py b/salut/tests/twisted/avahi/file-transfer/send-file-ipv6.py
new file mode 100644
index 000000000..963a84ce7
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/send-file-ipv6.py
@@ -0,0 +1,48 @@
+import avahi
+from saluttest import exec_test
+from avahitest import AvahiAnnouncer, get_host_name
+from xmppstream import setup_stream_listener6
+from file_transfer_helper import SendFileTest
+
+import constants as cs
+
+print "FIXME: disabled because of a bug in Python's httplib. http://bugs.python.org/issue5111"
+# exiting 77 causes automake to consider the test to have been skipped
+raise SystemExit(77)
+
+class SendFileTransferIPv6(SendFileTest):
+ CONTACT_NAME = 'test-ft'
+
+ def announce_contact(self, name=CONTACT_NAME):
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ self.contact_name = '%s@%s' % (name, get_host_name())
+ self.listener, port = setup_stream_listener6(self.q, self.contact_name)
+
+ self.contact_service = AvahiAnnouncer(self.contact_name, "_presence._tcp", port,
+ basic_txt, proto=avahi.PROTO_INET6)
+
+ def provide_file(self):
+ SendFileTest.provide_file(self)
+
+ # state is still Pending as remote didn't accept the transfer yet
+ state = self.ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'State')
+ assert state == cs.FT_STATE_PENDING
+
+ def client_request_file(self):
+ SendFileTest.client_request_file(self)
+
+ e = self.q.expect('dbus-signal', signal='InitialOffsetDefined')
+ offset = e.args[0]
+ # We don't support resume
+ assert offset == 0
+
+ # Channel is open. We can start to send the file
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_OPEN
+ assert reason == cs.FT_STATE_CHANGE_REASON_NONE
+
+if __name__ == '__main__':
+ test = SendFileTransferIPv6()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/send-file-item-not-found.py b/salut/tests/twisted/avahi/file-transfer/send-file-item-not-found.py
new file mode 100644
index 000000000..fdbbb645f
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/send-file-item-not-found.py
@@ -0,0 +1,40 @@
+from saluttest import exec_test
+from file_transfer_helper import SendFileTest
+
+from twisted.words.xish import domish
+
+import constants as cs
+
+class SendFileItemNotFound(SendFileTest):
+ def client_request_file(self):
+ # Receiver can't retrieve the file
+ reply = domish.Element(('', 'iq'))
+ reply['to'] = self.iq['from']
+ reply['from'] = self.iq['to']
+ reply['type'] = 'error'
+ reply['id'] = self.iq['id']
+ query = reply.addElement(('jabber:iq:oob', 'query'))
+ url_node = query.addElement('url', content=self.url)
+ query.addElement('desc', content=self.desc)
+ error_node = reply.addElement((None, 'error'))
+ error_node['code'] = '404'
+ error_node['type'] = 'modify'
+ error_node.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas',
+ 'item-not-found'))
+ self.incoming.send(reply)
+
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_CANCELLED, state
+ assert reason == cs.FT_STATE_CHANGE_REASON_REMOTE_ERROR
+
+ transferred = self.ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'TransferredBytes')
+ # no byte has been transferred as the transfer failed
+ assert transferred == 0
+
+ # stop test
+ return True
+
+if __name__ == '__main__':
+ test = SendFileItemNotFound()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/send-file-provide-immediately.py b/salut/tests/twisted/avahi/file-transfer/send-file-provide-immediately.py
new file mode 100644
index 000000000..2b52ea6a9
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/send-file-provide-immediately.py
@@ -0,0 +1,30 @@
+from saluttest import exec_test
+from file_transfer_helper import SendFileTest
+
+import constants as cs
+
+class SendFileTransferProvideImmediately(SendFileTest):
+ def provide_file(self):
+ SendFileTest.provide_file(self)
+
+ # state is still Pending as remote didn't accept the transfer yet
+ state = self.ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'State')
+ assert state == cs.FT_STATE_PENDING
+
+ def client_request_file(self):
+ SendFileTest.client_request_file(self)
+
+ e = self.q.expect('dbus-signal', signal='InitialOffsetDefined')
+ offset = e.args[0]
+ # We don't support resume
+ assert offset == 0
+
+ # Channel is open. We can start to send the file
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_OPEN
+ assert reason == cs.FT_STATE_CHANGE_REASON_NONE
+
+if __name__ == '__main__':
+ test = SendFileTransferProvideImmediately()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/send-file-to-unknown-contact.py b/salut/tests/twisted/avahi/file-transfer/send-file-to-unknown-contact.py
new file mode 100644
index 000000000..14f952429
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/send-file-to-unknown-contact.py
@@ -0,0 +1,29 @@
+import dbus
+
+from saluttest import exec_test
+from file_transfer_helper import SendFileTest
+
+from avahitest import get_host_name
+import constants as cs
+
+class SendFileTransferToUnknownContactTest(SendFileTest):
+ def __init__(self):
+ SendFileTest.__init__(self)
+
+ self._actions = [self.connect, self.check_ft_available, self.my_request_ft_channel]
+
+ def my_request_ft_channel(self):
+ self.contact_name = '%s@%s' % (self.CONTACT_NAME, get_host_name())
+ self.handle = self.conn.RequestHandles(cs.HT_CONTACT, [self.contact_name])[0]
+
+ try:
+ self.request_ft_channel()
+ except dbus.DBusException, e:
+ if e.get_dbus_name() != cs.NOT_AVAILABLE:
+ raise
+ else:
+ assert False, "Should raise NotAvailable error"
+
+if __name__ == '__main__':
+ test = SendFileTransferToUnknownContactTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/file-transfer/send-file-wait-to-provide.py b/salut/tests/twisted/avahi/file-transfer/send-file-wait-to-provide.py
new file mode 100644
index 000000000..b7bf47da5
--- /dev/null
+++ b/salut/tests/twisted/avahi/file-transfer/send-file-wait-to-provide.py
@@ -0,0 +1,43 @@
+from saluttest import exec_test
+from file_transfer_helper import SendFileTest
+
+import constants as cs
+
+class SendFileTransferWaitToProvideTest(SendFileTest):
+ def __init__(self):
+ SendFileTest.__init__(self)
+
+ self._actions = [self.connect, self.check_ft_available, self.announce_contact, self.wait_for_contact,
+ self.check_ft_available, self.request_ft_channel, self.create_ft_channel, self.got_send_iq,
+ self.client_request_file, self.provide_file, self.send_file, self.close_channel]
+
+ def client_request_file(self):
+ # state is still Pending as remote didn't accept the transfer yet
+ state = self.ft_props.Get(cs.CHANNEL_TYPE_FILE_TRANSFER, 'State')
+ assert state == cs.FT_STATE_PENDING
+
+ SendFileTest.client_request_file(self)
+
+ # Remote accepted the transfer
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_ACCEPTED, state
+ assert reason == cs.FT_STATE_CHANGE_REASON_NONE
+
+ def provide_file(self):
+ SendFileTest.provide_file(self)
+
+ e = self.q.expect('dbus-signal', signal='InitialOffsetDefined')
+ offset = e.args[0]
+ # We don't support resume
+ assert offset == 0
+
+ # Channel is open. We can start to send the file
+ e = self.q.expect('dbus-signal', signal='FileTransferStateChanged')
+ state, reason = e.args
+ assert state == cs.FT_STATE_OPEN
+ assert reason == cs.FT_STATE_CHANGE_REASON_REQUESTED
+
+if __name__ == '__main__':
+ test = SendFileTransferWaitToProvideTest()
+ exec_test(test.test)
diff --git a/salut/tests/twisted/avahi/ichat-composing.py b/salut/tests/twisted/avahi/ichat-composing.py
new file mode 100644
index 000000000..db81e5213
--- /dev/null
+++ b/salut/tests/twisted/avahi/ichat-composing.py
@@ -0,0 +1,75 @@
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+import avahi
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy
+
+from twisted.words.xish import xpath, domish
+
+
+import time
+import dbus
+
+CHANNEL_TYPE_TEXT = "org.freedesktop.Telepathy.Channel.Type.Text"
+HT_CONTACT = 1
+HT_CONTACT_LIST = 3
+OUTGOING_MESSAGE = "This is a message"
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ contact_name = "test-text-channel@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+
+ e = q.expect('service-resolved', service = service)
+
+ xmpp_connection = connect_to_stream(q, contact_name,
+ self_handle_name, str(e.pt), e.port)
+
+ e = q.expect('connection-result')
+ assert e.succeeded, e.reason
+
+ e = q.expect('stream-opened', connection = xmpp_connection)
+
+ # connected to salut, now send a messages as composing part
+ # here be sillyness
+ parts = OUTGOING_MESSAGE.split(" ")
+
+ for x in xrange(1,len(parts)):
+ message = domish.Element(('', 'message'))
+ message.addElement('body', content=' '.join(parts[:x]))
+ event = message.addElement('x', 'jabber:x:event')
+ event.addElement('composing')
+ event.addElement('id')
+ xmpp_connection.send(message)
+
+ message = domish.Element(('', 'message'))
+ message.addElement('body', content=OUTGOING_MESSAGE)
+ event = message.addElement('x', 'jabber:x:event')
+ event.addElement('composing')
+ xmpp_connection.send(message)
+
+ e = q.expect('dbus-signal', signal='Received')
+ assert e.args[2] == handle
+ assert e.args[5] == OUTGOING_MESSAGE
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/ichat-incoming-msg.py b/salut/tests/twisted/avahi/ichat-incoming-msg.py
new file mode 100644
index 000000000..6a16f069b
--- /dev/null
+++ b/salut/tests/twisted/avahi/ichat-incoming-msg.py
@@ -0,0 +1,66 @@
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name, skip_if_another_llxmpp
+import avahi
+
+from xmppstream import setup_stream_listener, connect_to_stream, OutgoingXmppiChatStream
+from servicetest import make_channel_proxy
+
+from twisted.words.xish import xpath, domish
+
+
+import time
+import dbus
+
+CHANNEL_TYPE_TEXT = "org.freedesktop.Telepathy.Channel.Type.Text"
+HT_CONTACT = 1
+HT_CONTACT_LIST = 3
+TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0)
+
+INCOMING_MESSAGE = "Test 123"
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+
+ contact_name = "test-ichat-incoming-msg@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ # Create a connection to send msg stanza
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+ e = q.expect('service-resolved', service = service)
+
+ outbound = connect_to_stream(q, contact_name,
+ self_handle_name, str(e.pt), e.port, OutgoingXmppiChatStream)
+
+ e = q.expect('connection-result')
+ assert e.succeeded, e.reason
+ e = q.expect('stream-opened', connection = outbound)
+
+ msg = domish.Element((None, 'message'))
+ msg['to'] = self_handle_name
+ msg['type'] = 'chat'
+ boddy = msg.addElement('body', content='hi')
+ outbound.send(msg)
+
+ e = q.expect('dbus-signal', signal='Received')
+ assert e.args[2] == handle
+ assert e.args[3] == TEXT_MESSAGE_TYPE_NORMAL
+ assert e.args[5] == "hi"
+
+if __name__ == '__main__':
+ skip_if_another_llxmpp()
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/muc-invite.py b/salut/tests/twisted/avahi/muc-invite.py
new file mode 100644
index 000000000..0cafad00d
--- /dev/null
+++ b/salut/tests/twisted/avahi/muc-invite.py
@@ -0,0 +1,120 @@
+"""
+Test receiving and sending muc invitations
+"""
+
+import avahi
+import dbus
+
+from saluttest import exec_test
+from saluttest import (exec_test, wait_for_contact_in_publish)
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import (make_channel_proxy, assertEquals, assertContains)
+
+from twisted.words.xish import domish
+
+import constants as cs
+
+HT_CONTACT = 1
+HT_ROOM = 2
+
+NS_CLIQUE = "http://telepathy.freedesktop.org/xmpp/clique"
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+
+ contact_name = "test-muc@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ requests_iface = dbus.Interface(conn, cs.CONN_IFACE_REQUESTS)
+
+ # Create a connection to send the muc invite
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+ e = q.expect('service-resolved', service = service)
+
+ outbound = connect_to_stream(q, contact_name,
+ self_handle_name, str(e.pt), e.port)
+
+ e = q.expect('connection-result')
+ assert e.succeeded, e.reason
+ e = q.expect('stream-opened', connection = outbound)
+
+ # connected to Salut, now send the muc invite
+ msg = domish.Element((None, 'message'))
+ msg['to'] = self_handle_name
+ msg['from'] = contact_name
+ msg['type'] = 'normal'
+ body = msg.addElement('body', content='You got a Clique chatroom invitation')
+ invite = msg.addElement((NS_CLIQUE, 'invite'))
+ invite.addElement('roomname', content='my-room')
+ invite.addElement('reason', content='Inviting to this room')
+ # FIXME: we should create a real Clique room and use its IP and port.
+ # Hardcode values for now. The IP is a multicast address.
+ invite.addElement('address', content='239.255.71.249')
+ invite.addElement('port', content='62472')
+ outbound.send(msg)
+
+ e = q.expect('dbus-signal', signal='NewChannels',
+ predicate=lambda e:
+ e.args[0][0][1][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TEXT)
+ channels = e.args[0]
+ assert len(channels) == 1
+ path, props = channels[0]
+ channel = make_channel_proxy(conn, path, "Channel")
+ channel_group = make_channel_proxy(conn, path, "Channel.Interface.Group")
+
+ muc_handle = conn.RequestHandles(HT_ROOM, ['my-room'])[0]
+
+ # check channel properties
+ # org.freedesktop.Telepathy.Channel D-Bus properties
+ assert props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TEXT
+ assertContains(cs.CHANNEL_IFACE_GROUP, props[cs.INTERFACES])
+ assertContains(cs.CHANNEL_IFACE_MESSAGES, props[cs.INTERFACES])
+ assert props[cs.TARGET_HANDLE] == muc_handle
+ assert props[cs.TARGET_ID] == 'my-room'
+ assert props[cs.TARGET_HANDLE_TYPE] == HT_ROOM
+ assert props[cs.REQUESTED] == False
+ assert props[cs.INITIATOR_HANDLE] == handle
+ assert props[cs.INITIATOR_ID] == contact_name
+
+ # we are added to local pending
+ e = q.expect('dbus-signal', signal='MembersChanged')
+ msg, added, removed, lp, rp, actor, reason = e.args
+ assert msg == 'Inviting to this room'
+ assert added == []
+ assert removed == []
+ assert lp == [self_handle]
+ assert rp == []
+ assert actor == handle
+ assert reason == 4 # invited
+
+ # TODO: join the muc, check if we are added to remote-pending and then
+ # to members. This would need some tweak in Salut and/or the test framework
+ # as Clique takes too much time to join the room and so the event times
+ # out.
+
+ # TODO: test sending invitations
+
+ # FIXME: fd.o #30531: this ought to work, but doesn't
+ #channel_group.RemoveMembersWithReason([self_handle], "bored now", 0)
+ channel.Close()
+ q.expect('dbus-signal', signal='Closed')
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/olpc-activity-announcements.py b/salut/tests/twisted/avahi/olpc-activity-announcements.py
new file mode 100644
index 000000000..6b4d01a2d
--- /dev/null
+++ b/salut/tests/twisted/avahi/olpc-activity-announcements.py
@@ -0,0 +1,115 @@
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer, AvahiRecordAnnouncer, AvahiListener
+from avahitest import get_host_name, get_domain_name
+import avahi
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy, format_event, EventPattern
+
+from twisted.words.xish import xpath, domish
+import constants as cs
+
+import time
+import dbus
+import socket
+
+CHANNEL_TYPE_TEXT = "org.freedesktop.Telepathy.Channel.Type.Text"
+HT_CONTACT = 1
+HT_ROOM = 2
+HT_CONTACT_LIST = 3
+
+PUBLISHED_NAME = "acttest"
+TESTSUITE_PUBLISHED_NAME = "salutacttest"
+ACTIVITY_ID = str(time.time())
+
+def announce_address(hostname, address):
+ "Announce IN A record, address is assume to be ipv4"
+
+ data = reduce (lambda x, y: (x << 8) + int(y), address.split("."), 0)
+ ndata = socket.htonl(data)
+ rdata = [ (ndata >> (24 - x)) & 0xff for x in xrange(0, 32, 8)]
+
+ AvahiRecordAnnouncer(hostname, 0x1, 0x01, rdata)
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+
+ activity_txt = { "type": "org.laptop.HelloMesh",
+ "name": "HelloMesh",
+ "color": "#7b83c1,#260993",
+ "txtvers": "0",
+ "activity-id": ACTIVITY_ID,
+ "room": ACTIVITY_ID
+ }
+
+ # Listen for announcements
+ l = AvahiListener(q).listen_for_service("_olpc-activity1._udp")
+
+ # Assert that the testsuite doesn't announce the activity
+ service_name = ACTIVITY_ID + ":" + TESTSUITE_PUBLISHED_NAME + "@" + get_host_name()
+ forbiden_event = EventPattern('service-added', name=service_name)
+ q.forbid_events([forbiden_event])
+
+ contact_name = PUBLISHED_NAME + "@" + get_host_name()
+
+ activity_name = ACTIVITY_ID + ":" + PUBLISHED_NAME + "@" + get_host_name()
+
+ AvahiAnnouncer(contact_name, "_presence._tcp", 1234, {})
+
+ act_hostname = ACTIVITY_ID + ":" + PUBLISHED_NAME + \
+ "._clique._udp." + get_domain_name()
+ act_address = "239.253.70.70"
+
+ announce_address(act_hostname, act_address)
+
+ # FIXME, if we use the same name as the running salut will MembersChanged
+ # isn't signalled later on, needs to be fixed.
+ AvahiAnnouncer(ACTIVITY_ID + ":" + PUBLISHED_NAME,
+ "_clique._udp", 12345, {}, hostname = act_hostname)
+ AvahiAnnouncer(activity_name, "_olpc-activity1._udp",
+ 0, activity_txt)
+
+ # Publish a contact, now get it's handle
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ # Assert that the remote handles signals it joined the activity
+ while True:
+ e = q.expect('dbus-signal', signal = 'ActivitiesChanged')
+ if e.args[0] == handle and e.args[1] != []:
+ assert len(e.args[1]) == 1
+ assert e.args[1][0][0] == ACTIVITY_ID
+ activity_handle = e.args[1][0][1]
+ break
+
+ act_prop_iface = dbus.Interface(conn, cs.ACTIVITY_PROPERTIES)
+ act_properties = act_prop_iface.GetProperties(activity_handle)
+ assert act_properties['private'] == False
+ assert act_properties['color'] == activity_txt['color']
+ assert act_properties['name'] == activity_txt['name']
+ assert act_properties['type'] == activity_txt['type']
+
+ room_channel = conn.RequestChannel(CHANNEL_TYPE_TEXT,
+ HT_ROOM, activity_handle, True)
+
+ q.expect('dbus-signal', signal='MembersChanged', path=room_channel,
+ args = [u'', [1L], [], [], [], 1L, 0L])
+
+ # Make it public that we joined the activity
+ q.unforbid_events([forbiden_event])
+ buddy_info_iface = dbus.Interface(conn, cs.BUDDY_INFO)
+ buddy_info_iface.SetActivities([(ACTIVITY_ID, activity_handle)])
+
+ q.expect('service-added',
+ name = ACTIVITY_ID + ":" + TESTSUITE_PUBLISHED_NAME +
+ "@" + get_host_name())
+
+ buddy_info_iface.SetActivities([])
+
+ q.expect('service-removed',
+ name = ACTIVITY_ID + ":" + TESTSUITE_PUBLISHED_NAME +
+ "@" + get_host_name())
+
+if __name__ == '__main__':
+ exec_test(test, { "published-name": TESTSUITE_PUBLISHED_NAME}, timeout=15)
diff --git a/salut/tests/twisted/avahi/register.py b/salut/tests/twisted/avahi/register.py
new file mode 100644
index 000000000..dddde3816
--- /dev/null
+++ b/salut/tests/twisted/avahi/register.py
@@ -0,0 +1,36 @@
+from saluttest import exec_test
+import avahitest
+from avahitest import AvahiListener
+from avahitest import txt_get_key
+from avahi import txt_array_to_string_array
+
+import time
+
+PUBLISHED_NAME="test-register"
+FIRST_NAME="lastname"
+LAST_NAME="lastname"
+
+def test(q, bus, conn):
+ AvahiListener(q).listen_for_service("_presence._tcp")
+
+ conn.Connect()
+
+ e = q.expect('service-added',
+ name=PUBLISHED_NAME + "@" + avahitest.get_host_name())
+
+ service = e.service
+ service.resolve()
+
+ e = q.expect('service-resolved', service = service)
+
+ for (key, val) in { "1st": FIRST_NAME,
+ "last": LAST_NAME,
+ "status": "avail",
+ "txtvers": "1" }.iteritems():
+ v = txt_get_key(e.txt, key)
+ assert v == val, (key, val, v)
+
+if __name__ == '__main__':
+ exec_test(test, { "published-name": PUBLISHED_NAME,
+ "first-name": FIRST_NAME,
+ "last-name": LAST_NAME })
diff --git a/salut/tests/twisted/avahi/request-im.py b/salut/tests/twisted/avahi/request-im.py
new file mode 100644
index 000000000..a61a3684f
--- /dev/null
+++ b/salut/tests/twisted/avahi/request-im.py
@@ -0,0 +1,163 @@
+
+"""
+Test requesting of text 1-1 channels using the old and new request API.
+"""
+
+import dbus
+
+from saluttest import (exec_test, wait_for_contact_list,
+ wait_for_contact_in_publish)
+from servicetest import call_async, EventPattern, \
+ tp_name_prefix, make_channel_proxy
+from avahitest import get_host_name, AvahiAnnouncer
+from xmppstream import setup_stream_listener
+
+CHANNEL_TYPE_TEXT = 'org.freedesktop.Telepathy.Channel.Type.Text'
+
+HT_CONTACT = 1
+
+def test(q, bus, conn):
+ self_name = 'testsuite' + '@' + get_host_name()
+
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+ basic_txt = { "txtvers": "1", "status": "avail" }
+ contact_name = "test-request-im@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ # FIXME: this is a hack to be sure to have all the contact list channels
+ # announced so they won't interfere with the muc ones announces.
+ wait_for_contact_list(q, conn)
+
+ AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ # check if we can request roomlist channels
+ properties = conn.GetAll(
+ tp_name_prefix + '.Connection.Interface.Requests',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ assert ({tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_TEXT,
+ tp_name_prefix + '.Channel.TargetHandleType': HT_CONTACT,
+ },
+ [tp_name_prefix + '.Channel.TargetHandle',
+ tp_name_prefix + '.Channel.TargetID'],
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+
+ # request a muc channel using the old API
+ handle = conn.RequestHandles(HT_CONTACT, [contact_name])[0]
+ call_async(q, conn, 'RequestChannel', CHANNEL_TYPE_TEXT, HT_CONTACT, handle, True)
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='RequestChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+
+ path1 = ret.value[0]
+ chan = make_channel_proxy(conn, path1, "Channel")
+
+ assert new_sig.args[0][0][0] == path1
+
+ props = new_sig.args[0][0][1]
+ assert props[tp_name_prefix + '.Channel.ChannelType'] ==\
+ CHANNEL_TYPE_TEXT
+ assert props[tp_name_prefix + '.Channel.TargetHandleType'] == HT_CONTACT
+ assert props[tp_name_prefix + '.Channel.TargetHandle'] == handle
+ assert props[tp_name_prefix + '.Channel.TargetID'] == contact_name
+ assert props[tp_name_prefix + '.Channel.Requested'] == True
+ assert props[tp_name_prefix + '.Channel.InitiatorHandle'] \
+ == conn.GetSelfHandle()
+ assert props[tp_name_prefix + '.Channel.InitiatorID'] \
+ == self_name
+
+ assert old_sig.args[0] == path1
+ assert old_sig.args[1] == CHANNEL_TYPE_TEXT
+ assert old_sig.args[2] == HT_CONTACT # handle type
+ assert old_sig.args[3] == handle # handle
+
+ # Exercise basic Channel Properties from spec 0.17.7
+ channel_props = chan.GetAll(
+ tp_name_prefix + '.Channel',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ assert channel_props.get('TargetHandle') == handle,\
+ channel_props.get('TargetHandle')
+ assert channel_props['TargetID'] == contact_name, channel_props
+ assert channel_props.get('TargetHandleType') == HT_CONTACT,\
+ channel_props.get('TargetHandleType')
+ assert channel_props.get('ChannelType') == \
+ CHANNEL_TYPE_TEXT, channel_props.get('ChannelType')
+ assert channel_props['Requested'] == True
+ assert channel_props['InitiatorID'] == self_name
+ assert channel_props['InitiatorHandle'] == conn.GetSelfHandle()
+
+ requestotron = dbus.Interface(conn,
+ tp_name_prefix + '.Connection.Interface.Requests')
+
+ chan.Close()
+ q.expect_many(
+ EventPattern('dbus-signal', signal='ChannelClosed', args=[path1]),
+ EventPattern('dbus-signal', signal='Closed', path=path1))
+
+ # create muc channel using new API
+ call_async(q, requestotron, 'CreateChannel',
+ { tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_TEXT,
+ tp_name_prefix + '.Channel.TargetHandleType': HT_CONTACT,
+ tp_name_prefix + '.Channel.TargetID': contact_name,
+ })
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='CreateChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+ path2 = ret.value[0]
+ chan = make_channel_proxy(conn, path2, "Channel")
+
+ props = ret.value[1]
+ assert props[tp_name_prefix + '.Channel.ChannelType'] ==\
+ CHANNEL_TYPE_TEXT
+ assert props[tp_name_prefix + '.Channel.TargetHandleType'] == HT_CONTACT
+ assert props[tp_name_prefix + '.Channel.TargetHandle'] == handle
+ assert props[tp_name_prefix + '.Channel.TargetID'] == contact_name
+ assert props[tp_name_prefix + '.Channel.Requested'] == True
+ assert props[tp_name_prefix + '.Channel.InitiatorHandle'] \
+ == conn.GetSelfHandle()
+ assert props[tp_name_prefix + '.Channel.InitiatorID'] \
+ == self_name
+
+ assert new_sig.args[0][0][0] == path2
+ assert new_sig.args[0][0][1] == props
+
+ assert old_sig.args[0] == path2
+ assert old_sig.args[1] == CHANNEL_TYPE_TEXT
+ assert old_sig.args[2] == HT_CONTACT # handle type
+ assert old_sig.args[3] == handle # handle
+ assert old_sig.args[4] == True # suppress handler
+
+ # ensure roomlist channel
+ yours, ensured_path, ensured_props = requestotron.EnsureChannel(
+ { tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_TEXT,
+ tp_name_prefix + '.Channel.TargetHandleType': HT_CONTACT,
+ tp_name_prefix + '.Channel.TargetHandle': handle,
+ })
+
+ assert not yours
+ assert ensured_path == path2, (ensured_path, path2)
+
+ conn.Disconnect()
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='Closed',
+ path=path2),
+ EventPattern('dbus-signal', signal='ChannelClosed', args=[path2]),
+ EventPattern('dbus-signal', signal='StatusChanged', args=[2, 1]),
+ )
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/request-muc.py b/salut/tests/twisted/avahi/request-muc.py
new file mode 100644
index 000000000..73497d3d8
--- /dev/null
+++ b/salut/tests/twisted/avahi/request-muc.py
@@ -0,0 +1,157 @@
+
+"""
+Test requesting of muc text channels using the old and new request API.
+"""
+
+import dbus
+import avahitest
+
+from twisted.words.xish import domish
+
+from saluttest import exec_test, wait_for_contact_list
+from servicetest import call_async, EventPattern, \
+ tp_name_prefix, tp_path_prefix, make_channel_proxy
+
+CHANNEL_TYPE_TEXT = 'org.freedesktop.Telepathy.Channel.Type.Text'
+
+HT_ROOM = 2
+
+def test(q, bus, conn):
+ self_name = 'testsuite' + '@' + avahitest.get_host_name()
+
+ conn.Connect()
+
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+ # FIXME: this is a hack to be sure to have all the contact list channels
+ # announced so they won't interfere with the muc ones announces.
+ wait_for_contact_list(q, conn)
+
+ # check if we can request roomlist channels
+ properties = conn.GetAll(
+ tp_name_prefix + '.Connection.Interface.Requests',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ assert ({tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_TEXT,
+ tp_name_prefix + '.Channel.TargetHandleType': HT_ROOM,
+ },
+ [tp_name_prefix + '.Channel.TargetHandle',
+ tp_name_prefix + '.Channel.TargetID'],
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+
+ # request a muc channel using the old API
+ handle = conn.RequestHandles(HT_ROOM, ['my-first-room'])[0]
+ call_async(q, conn, 'RequestChannel', CHANNEL_TYPE_TEXT, HT_ROOM, handle, True)
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='RequestChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+
+ path1 = ret.value[0]
+ chan = make_channel_proxy(conn, path1, "Channel")
+
+ assert new_sig.args[0][0][0] == path1
+
+ props = new_sig.args[0][0][1]
+ assert props[tp_name_prefix + '.Channel.ChannelType'] ==\
+ CHANNEL_TYPE_TEXT
+ assert props[tp_name_prefix + '.Channel.TargetHandleType'] == HT_ROOM
+ assert props[tp_name_prefix + '.Channel.TargetHandle'] == handle
+ assert props[tp_name_prefix + '.Channel.TargetID'] == 'my-first-room'
+ assert props[tp_name_prefix + '.Channel.Requested'] == True
+ assert props[tp_name_prefix + '.Channel.InitiatorHandle'] \
+ == conn.GetSelfHandle()
+ assert props[tp_name_prefix + '.Channel.InitiatorID'] \
+ == self_name
+
+ assert old_sig.args[0] == path1
+ assert old_sig.args[1] == CHANNEL_TYPE_TEXT
+ assert old_sig.args[2] == HT_ROOM # handle type
+ assert old_sig.args[3] == handle # handle
+
+ # Exercise basic Channel Properties from spec 0.17.7
+ channel_props = chan.GetAll(
+ tp_name_prefix + '.Channel',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ assert channel_props.get('TargetHandle') == handle,\
+ channel_props.get('TargetHandle')
+ assert channel_props['TargetID'] == 'my-first-room', channel_props
+ assert channel_props.get('TargetHandleType') == HT_ROOM,\
+ channel_props.get('TargetHandleType')
+ assert channel_props.get('ChannelType') == \
+ CHANNEL_TYPE_TEXT, channel_props.get('ChannelType')
+ assert channel_props['Requested'] == True
+ assert channel_props['InitiatorID'] == self_name
+ assert channel_props['InitiatorHandle'] == conn.GetSelfHandle()
+
+ requestotron = dbus.Interface(conn,
+ tp_name_prefix + '.Connection.Interface.Requests')
+
+ # create muc channel using new API
+ call_async(q, requestotron, 'CreateChannel',
+ { tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_TEXT,
+ tp_name_prefix + '.Channel.TargetHandleType': HT_ROOM,
+ tp_name_prefix + '.Channel.TargetID': 'my-second-room',
+ })
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='CreateChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+ path2 = ret.value[0]
+ chan = make_channel_proxy(conn, path2, "Channel")
+
+ handle = conn.RequestHandles(HT_ROOM, ['my-second-room'])[0]
+
+ props = ret.value[1]
+ assert props[tp_name_prefix + '.Channel.ChannelType'] ==\
+ CHANNEL_TYPE_TEXT
+ assert props[tp_name_prefix + '.Channel.TargetHandleType'] == HT_ROOM
+ assert props[tp_name_prefix + '.Channel.TargetHandle'] == handle
+ assert props[tp_name_prefix + '.Channel.TargetID'] == 'my-second-room'
+ assert props[tp_name_prefix + '.Channel.Requested'] == True
+ assert props[tp_name_prefix + '.Channel.InitiatorHandle'] \
+ == conn.GetSelfHandle()
+ assert props[tp_name_prefix + '.Channel.InitiatorID'] \
+ == self_name
+
+ assert new_sig.args[0][0][0] == path2
+ assert new_sig.args[0][0][1] == props
+
+ assert old_sig.args[0] == path2
+ assert old_sig.args[1] == CHANNEL_TYPE_TEXT
+ assert old_sig.args[2] == HT_ROOM # handle type
+ assert old_sig.args[3] == handle # handle
+ assert old_sig.args[4] == True # suppress handler
+
+ # ensure roomlist channel
+ yours, ensured_path, ensured_props = ret.value = requestotron.EnsureChannel(
+ { tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_TEXT,
+ tp_name_prefix + '.Channel.TargetHandleType': HT_ROOM,
+ tp_name_prefix + '.Channel.TargetHandle': handle,
+ })
+
+ assert not yours
+ assert ensured_path == path2, (ensured_path, path2)
+
+ conn.Disconnect()
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='Closed',
+ path=path1),
+ EventPattern('dbus-signal', signal='Closed',
+ path=path2),
+ EventPattern('dbus-signal', signal='ChannelClosed', args=[path1]),
+ EventPattern('dbus-signal', signal='ChannelClosed', args=[path2]),
+ EventPattern('dbus-signal', signal='StatusChanged', args=[2, 1]),
+ )
+
+if __name__ == '__main__':
+ exec_test(test)
+
diff --git a/salut/tests/twisted/avahi/roomlist.py b/salut/tests/twisted/avahi/roomlist.py
new file mode 100644
index 000000000..f5ffd85ea
--- /dev/null
+++ b/salut/tests/twisted/avahi/roomlist.py
@@ -0,0 +1,174 @@
+
+"""
+Test room list support.
+"""
+
+import dbus
+import avahitest
+
+from twisted.words.xish import domish
+
+from saluttest import exec_test, wait_for_contact_list
+from servicetest import call_async, EventPattern, \
+ tp_name_prefix, tp_path_prefix, make_channel_proxy
+
+CHANNEL_TYPE_ROOMLIST = 'org.freedesktop.Telepathy.Channel.Type.RoomList'
+
+def test(q, bus, conn):
+ self_name = 'testsuite' + '@' + avahitest.get_host_name()
+
+ conn.Connect()
+
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+ # FIXME: this is a hack to be sure to have all the contact list channels
+ # announced so they won't interfere with the roomlist ones announces.
+ wait_for_contact_list(q, conn)
+
+ # check if we can request roomlist channels
+ properties = conn.GetAll(
+ tp_name_prefix + '.Connection.Interface.Requests',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ assert ({tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_ROOMLIST,
+ tp_name_prefix + '.Channel.TargetHandleType': 0,
+ },
+ [],
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+
+ # request a roomlist channel using the old API
+ call_async(q, conn, 'RequestChannel', CHANNEL_TYPE_ROOMLIST, 0, 0, True)
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='RequestChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+
+ path1 = ret.value[0]
+ chan = make_channel_proxy(conn, path1, "Channel.Type.RoomList")
+
+ assert new_sig.args[0][0][0] == path1
+
+ props = new_sig.args[0][0][1]
+ assert props[tp_name_prefix + '.Channel.ChannelType'] ==\
+ CHANNEL_TYPE_ROOMLIST
+ assert props[tp_name_prefix + '.Channel.TargetHandleType'] == 0
+ assert props[tp_name_prefix + '.Channel.TargetHandle'] == 0
+ assert props[tp_name_prefix + '.Channel.TargetID'] == ''
+ assert props[tp_name_prefix + '.Channel.Requested'] == True
+ assert props[tp_name_prefix + '.Channel.InitiatorHandle'] \
+ == conn.GetSelfHandle()
+ assert props[tp_name_prefix + '.Channel.InitiatorID'] \
+ == self_name
+ assert props[tp_name_prefix + '.Channel.Type.RoomList.Server'] == ''
+
+ assert old_sig.args[0] == path1
+ assert old_sig.args[1] == CHANNEL_TYPE_ROOMLIST
+ assert old_sig.args[2] == 0 # handle type
+ assert old_sig.args[3] == 0 # handle
+ assert old_sig.args[4] == 1 # suppress handler
+
+ # Exercise basic Channel Properties from spec 0.17.7
+ channel_props = chan.GetAll(
+ tp_name_prefix + '.Channel',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ assert channel_props.get('TargetHandle') == 0,\
+ channel_props.get('TargetHandle')
+ assert channel_props['TargetID'] == '', channel_props
+ assert channel_props.get('TargetHandleType') == 0,\
+ channel_props.get('TargetHandleType')
+ assert channel_props.get('ChannelType') == \
+ CHANNEL_TYPE_ROOMLIST, channel_props.get('ChannelType')
+ assert channel_props['Requested'] == True
+ assert channel_props['InitiatorID'] == self_name
+ assert channel_props['InitiatorHandle'] == conn.GetSelfHandle()
+
+ assert chan.Get(
+ CHANNEL_TYPE_ROOMLIST, 'Server',
+ dbus_interface='org.freedesktop.DBus.Properties') == ''
+
+ # list rooms
+ chan.ListRooms()
+
+ q.expect('dbus-signal', signal='ListingRooms', args=[True])
+
+ e = q.expect('dbus-signal', signal='GotRooms')
+ rooms = e.args[0]
+ # FIXME: this will fail if there is room announced on the network
+ #assert rooms == []
+
+ q.expect('dbus-signal', signal='ListingRooms', args=[False])
+
+ # FIXME: announce some Clique rooms and check is they are properly listed
+
+ requestotron = dbus.Interface(conn,
+ tp_name_prefix + '.Connection.Interface.Requests')
+
+ # create roomlist channel using new API
+ call_async(q, requestotron, 'CreateChannel',
+ { tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_ROOMLIST,
+ tp_name_prefix + '.Channel.TargetHandleType': 0,
+ })
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='CreateChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+ path2 = ret.value[0]
+ chan = make_channel_proxy(conn, path2, "Channel.Type.RoomList")
+
+ props = ret.value[1]
+ assert props[tp_name_prefix + '.Channel.ChannelType'] ==\
+ CHANNEL_TYPE_ROOMLIST
+ assert props[tp_name_prefix + '.Channel.TargetHandleType'] == 0
+ assert props[tp_name_prefix + '.Channel.TargetHandle'] == 0
+ assert props[tp_name_prefix + '.Channel.TargetID'] == ''
+ assert props[tp_name_prefix + '.Channel.Requested'] == True
+ assert props[tp_name_prefix + '.Channel.InitiatorHandle'] \
+ == conn.GetSelfHandle()
+ assert props[tp_name_prefix + '.Channel.InitiatorID'] \
+ == self_name
+ assert props[tp_name_prefix + '.Channel.Type.RoomList.Server'] == ''
+
+ assert new_sig.args[0][0][0] == path2
+ assert new_sig.args[0][0][1] == props
+
+ assert old_sig.args[0] == path2
+ assert old_sig.args[1] == CHANNEL_TYPE_ROOMLIST
+ assert old_sig.args[2] == 0 # handle type
+ assert old_sig.args[3] == 0 # handle
+ assert old_sig.args[4] == 1 # suppress handler
+
+ assert chan.Get(
+ CHANNEL_TYPE_ROOMLIST, 'Server',
+ dbus_interface='org.freedesktop.DBus.Properties') == ''
+
+ # ensure roomlist channel
+ yours, ensured_path, ensured_props = requestotron.EnsureChannel(
+ { tp_name_prefix + '.Channel.ChannelType':
+ CHANNEL_TYPE_ROOMLIST,
+ tp_name_prefix + '.Channel.TargetHandleType': 0,
+ })
+
+ assert not yours
+ assert ensured_path == path2, (ensured_path, path2)
+
+ conn.Disconnect()
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='Closed',
+ path=path1),
+ EventPattern('dbus-signal', signal='Closed',
+ path=path2),
+ EventPattern('dbus-signal', signal='ChannelClosed', args=[path1]),
+ EventPattern('dbus-signal', signal='ChannelClosed', args=[path2]),
+ EventPattern('dbus-signal', signal='StatusChanged', args=[2, 1]),
+ )
+
+if __name__ == '__main__':
+ exec_test(test)
+
diff --git a/salut/tests/twisted/avahi/set-presence.py b/salut/tests/twisted/avahi/set-presence.py
new file mode 100644
index 000000000..16ecfb754
--- /dev/null
+++ b/salut/tests/twisted/avahi/set-presence.py
@@ -0,0 +1,61 @@
+
+"""
+Test requesting of muc text channels using the old and new request API.
+"""
+
+import dbus
+import avahi
+
+from saluttest import exec_test
+from avahitest import AvahiListener, txt_get_key
+import constants as cs
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_NONE_SPECIFIED])
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(cs.HT_CONTACT, [self_handle])[0]
+
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name, protocol = avahi.PROTO_INET)
+ service = e.service
+
+ service.resolve()
+
+ def wait_for_presence_announce():
+ e = q.expect('service-resolved', service=service)
+ return txt_get_key(e.txt, 'status'), txt_get_key(e.txt, 'msg')
+
+ # initial presence is available
+ status, msg = wait_for_presence_announce()
+ assert status == 'avail', status
+ assert msg is None, msg
+
+ statuses = conn.Get(cs.CONN_IFACE_SIMPLE_PRESENCE, 'Statuses', dbus_interface=dbus.PROPERTIES_IFACE)
+ assert 'available' in statuses
+ assert 'dnd' in statuses
+ assert 'away' in statuses
+
+
+ simple_presence = dbus.Interface(conn, cs.CONN_IFACE_SIMPLE_PRESENCE)
+ # set your status to away
+ simple_presence.SetPresence('away', 'At the pub')
+
+ status, msg = wait_for_presence_announce()
+ assert status == 'away', status
+ assert msg == 'At the pub', msg
+
+ # set your status to available without msg
+ simple_presence.SetPresence('available', '')
+
+ status, msg = wait_for_presence_announce()
+ assert status == 'avail', status
+ assert msg is None, msg
+
+ conn.Disconnect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_DISCONNECTED, cs.CSR_REQUESTED])
+
+if __name__ == '__main__':
+ exec_test(test)
+
diff --git a/salut/tests/twisted/avahi/text-channel.py b/salut/tests/twisted/avahi/text-channel.py
new file mode 100644
index 000000000..68970cac0
--- /dev/null
+++ b/salut/tests/twisted/avahi/text-channel.py
@@ -0,0 +1,87 @@
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+import avahi
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy
+
+from twisted.words.xish import xpath, domish
+
+
+import time
+import dbus
+
+CHANNEL_TYPE_TEXT = "org.freedesktop.Telepathy.Channel.Type.Text"
+HT_CONTACT = 1
+HT_CONTACT_LIST = 3
+TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0)
+
+INCOMING_MESSAGE = "Test 123"
+OUTGOING_MESSAGE = "Test 321"
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ contact_name = "test-text-channel@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ t = conn.RequestChannel(CHANNEL_TYPE_TEXT, HT_CONTACT, handle,
+ True)
+ text_channel = make_channel_proxy(conn, t, "Channel.Type.Text")
+ text_channel.Send(TEXT_MESSAGE_TYPE_NORMAL, INCOMING_MESSAGE)
+
+ e = q.expect('incoming-connection', listener = listener)
+ incoming = e.connection
+
+ e = q.expect('stream-message', connection = incoming)
+ assert e.message_type == "chat"
+ body = xpath.queryForNodes("/message/body", e.stanza )
+ assert map(str, body) == [ INCOMING_MESSAGE ]
+
+ # drop the connection
+ incoming.transport.loseConnection()
+
+ # Now send a message to salut
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+
+
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+
+
+ e = q.expect('service-resolved', service = service)
+
+ outbound = connect_to_stream(q, contact_name,
+ self_handle_name, str(e.pt), e.port)
+
+ e = q.expect('connection-result')
+ assert e.succeeded, e.reason
+
+ e = q.expect('stream-opened', connection = outbound)
+
+ # connected to salut, now send a message
+ message = domish.Element(('', 'message'))
+ message['type'] = "chat"
+ message.addElement('body', content=OUTGOING_MESSAGE)
+
+ e.connection.send(message)
+
+ e = q.expect('dbus-signal', signal='Received')
+ assert e.args[2] == handle
+ assert e.args[3] == TEXT_MESSAGE_TYPE_NORMAL
+ assert e.args[5] == OUTGOING_MESSAGE
+
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/tubes/disabled-1-1-tubes.py b/salut/tests/twisted/avahi/tubes/disabled-1-1-tubes.py
new file mode 100644
index 000000000..a30acd56a
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/disabled-1-1-tubes.py
@@ -0,0 +1,62 @@
+"""
+Test if 1-1 tubes support is properly disabled.
+This test should be removed as soon as we re-enable 1-1 tubes support.
+"""
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+import avahi
+import dbus
+import os
+import errno
+import string
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy, Event
+
+from twisted.words.xish import xpath, domish
+from twisted.internet.protocol import Factory, Protocol, ClientCreator
+from twisted.internet import reactor
+
+import constants as cs
+
+PUBLISHED_NAME="test-tube"
+
+CHANNEL_TYPE_TUBES = "org.freedesktop.Telepathy.Channel.Type.Tubes"
+HT_CONTACT = 1
+HT_CONTACT_LIST = 3
+TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0)
+SOCKET_ADDRESS_TYPE_UNIX = dbus.UInt32(0)
+SOCKET_ADDRESS_TYPE_IPV4 = dbus.UInt32(2)
+SOCKET_ACCESS_CONTROL_LOCALHOST = dbus.UInt32(0)
+
+sample_parameters = dbus.Dictionary({
+ 's': 'hello',
+ 'ay': dbus.ByteArray('hello'),
+ 'u': dbus.UInt32(123),
+ 'i': dbus.Int32(-123),
+ }, signature='sv')
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ contact_name = PUBLISHED_NAME + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ # we can't request 1-1 tubes channel
+ try:
+ conn.RequestChannel(CHANNEL_TYPE_TUBES, HT_CONTACT, handle,
+ True)
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == cs.NOT_IMPLEMENTED
+ else:
+ assert False, "Should raise NotImplemented error"
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/tubes/offer-private-stream-tube.py b/salut/tests/twisted/avahi/tubes/offer-private-stream-tube.py
new file mode 100644
index 000000000..68fcb2cb9
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/offer-private-stream-tube.py
@@ -0,0 +1,360 @@
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+import avahi
+import dbus
+import os
+import errno
+import string
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy, Event, EventPattern, call_async, \
+ tp_name_prefix, sync_dbus
+
+from twisted.words.xish import xpath, domish
+from twisted.internet.protocol import Factory, Protocol, ClientCreator
+from twisted.internet import reactor
+
+from constants import INVALID_ARGUMENT, NOT_IMPLEMENTED
+
+PUBLISHED_NAME="test-tube"
+
+CHANNEL_TYPE_TUBES = "org.freedesktop.Telepathy.Channel.Type.Tubes"
+HT_CONTACT = 1
+HT_CONTACT_LIST = 3
+TEXT_MESSAGE_TYPE_NORMAL = dbus.UInt32(0)
+SOCKET_ADDRESS_TYPE_UNIX = dbus.UInt32(0)
+SOCKET_ADDRESS_TYPE_IPV4 = dbus.UInt32(2)
+SOCKET_ADDRESS_TYPE_IPV6 = dbus.UInt32(3)
+SOCKET_ACCESS_CONTROL_LOCALHOST = dbus.UInt32(0)
+
+sample_parameters = dbus.Dictionary({
+ 's': 'hello',
+ 'ay': dbus.ByteArray('hello'),
+ 'u': dbus.UInt32(123),
+ 'i': dbus.Int32(-123),
+ }, signature='sv')
+
+test_string = "This string travels on a tube !"
+
+print "FIXME: disabled because 1-1 tubes are disabled for now"
+# exiting 77 causes automake to consider the test to have been skipped
+raise SystemExit(77)
+
+def check_conn_properties(q, bus, conn, channel_list=None):
+ properties = conn.GetAll(
+ 'org.freedesktop.Telepathy.Connection.Interface.Requests',
+ dbus_interface='org.freedesktop.DBus.Properties')
+
+ if channel_list == None:
+ assert properties.get('Channels') == [], properties['Channels']
+ else:
+ for i in channel_list:
+ assert i in properties['Channels'], \
+ (i, properties['Channels'])
+
+ assert ({'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.Tubes',
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType': HT_CONTACT,
+ },
+ ['org.freedesktop.Telepathy.Channel.TargetHandle',
+ ]
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+ assert ({'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube',
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType': HT_CONTACT,
+ },
+ ['org.freedesktop.Telepathy.Channel.TargetHandle',
+ 'org.freedesktop.Telepathy.Channel.TargetID',
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube.Service',
+ ]
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+
+def check_channel_properties(q, bus, conn, channel, channel_type,
+ contact_handle, contact_id, state=None):
+ # Exercise basic Channel Properties from spec 0.17.7
+ # on the channel of type channel_type
+ channel_props = channel.GetAll(
+ 'org.freedesktop.Telepathy.Channel',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ assert channel_props.get('TargetHandle') == contact_handle,\
+ (channel_props.get('TargetHandle'), contact_handle)
+ assert channel_props.get('TargetHandleType') == HT_CONTACT,\
+ channel_props.get('TargetHandleType')
+ assert channel_props.get('ChannelType') == \
+ 'org.freedesktop.Telepathy.Channel.Type.' + channel_type,\
+ channel_props.get('ChannelType')
+ assert 'Interfaces' in channel_props, channel_props
+ assert 'org.freedesktop.Telepathy.Channel.Interface.Group' not in \
+ channel_props['Interfaces'], \
+ channel_props['Interfaces']
+ assert channel_props['TargetID'] == contact_id
+
+ if channel_type == "Tubes":
+ assert state is None
+ else:
+ assert state is not None
+ tube_props = channel.GetAll(
+ 'org.freedesktop.Telepathy.Channel.Interface.Tube',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ assert tube_props['State'] == state, tube_props['State']
+ # no strict check but at least check the properties exist
+ assert tube_props.has_key('Parameters')
+
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+
+ assert channel_props['Requested'] == True
+ assert channel_props['InitiatorID'] == self_handle_name
+ assert channel_props['InitiatorHandle'] == self_handle
+
+
+def check_NewChannel_signal(old_sig, channel_type, chan_path, contact_handle):
+ assert old_sig[0] == chan_path, old_sig[0]
+ assert old_sig[1] == tp_name_prefix + '.Channel.Type.' + channel_type
+ assert old_sig[2] == HT_CONTACT
+ assert old_sig[3] == contact_handle
+ assert old_sig[4] == True # suppress handler
+
+def check_NewChannels_signal(conn, new_sig, channel_type, chan_path, contact_handle,
+ contact_id, initiator_handle):
+ assert len(new_sig) == 1
+ assert len(new_sig[0]) == 1 # one channel
+ assert len(new_sig[0][0]) == 2 # two struct members
+ assert new_sig[0][0][0] == chan_path
+ emitted_props = new_sig[0][0][1]
+
+ initiator_name = conn.InspectHandles(HT_CONTACT, [initiator_handle])[0]
+ assert emitted_props[tp_name_prefix + '.Channel.ChannelType'] ==\
+ tp_name_prefix + '.Channel.Type.' + channel_type
+ assert emitted_props[tp_name_prefix + '.Channel.TargetHandleType'] == \
+ HT_CONTACT
+ assert emitted_props[tp_name_prefix + '.Channel.TargetHandle'] ==\
+ contact_handle
+ assert emitted_props[tp_name_prefix + '.Channel.TargetID'] == \
+ contact_id
+ assert emitted_props[tp_name_prefix + '.Channel.Requested'] == True
+ assert emitted_props[tp_name_prefix + '.Channel.InitiatorHandle'] \
+ == initiator_handle
+ assert emitted_props[tp_name_prefix + '.Channel.InitiatorID'] == \
+ initiator_name
+
+def test(q, bus, conn):
+
+ # define a basic tcp server that echoes what the client says, but with
+ # swapcase
+ class TrivialServer(Protocol):
+ def dataReceived(self, data):
+ self.transport.write(string.swapcase(data))
+ e = Event('server-data-received', service = self, data = data)
+ q.append(e)
+
+ # define a basic tcp client
+ class ClientGreeter(Protocol):
+ def dataReceived(self, data):
+ e = Event('client-data-received', service = self, data = data)
+ q.append(e)
+ def client_connected_cb(p):
+ e = Event('client-connected', transport = p.transport)
+ q.append(e)
+
+ # create the server
+ factory = Factory()
+ factory.protocol = TrivialServer
+ server_socket_address = os.getcwd() + '/stream'
+ try:
+ os.remove(server_socket_address)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ l = reactor.listenUNIX(server_socket_address, factory)
+
+
+ check_conn_properties(q, bus, conn)
+
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ contact_name = PUBLISHED_NAME + "@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ # NewChannels would be emitted for the contact list channels, we don't
+ # want this to interfere with the NewChannels signals for the requested
+ # tubes channel
+ sync_dbus(bus, q, conn)
+
+ # old requestotron
+ call_async(q, conn, 'RequestChannel',
+ CHANNEL_TYPE_TUBES, HT_CONTACT, handle, True);
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='RequestChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+
+ assert len(ret.value) == 1
+ chan_path = ret.value[0]
+
+ check_NewChannel_signal(old_sig.args, "Tubes", chan_path, handle)
+ check_NewChannels_signal(conn, new_sig.args, "Tubes", chan_path,
+ handle, contact_name, conn.GetSelfHandle())
+ emitted_props = new_sig.args[0][0][1]
+ old_tubes_channel_properties = new_sig.args[0][0]
+
+ check_conn_properties(q, bus, conn, [old_tubes_channel_properties])
+
+ # new requestotron
+ requestotron = dbus.Interface(conn,
+ 'org.freedesktop.Telepathy.Connection.Interface.Requests')
+
+ # Try to CreateChannel with unknown properties
+ # Salut must return an error
+ try:
+ requestotron.CreateChannel(
+ {'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube',
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType':
+ HT_CONTACT,
+ 'org.freedesktop.Telepathy.Channel.TargetHandle':
+ handle,
+ 'this.property.does.not.exist':
+ 'this.value.should.not.exist'
+ })
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == NOT_IMPLEMENTED, e.get_dbus_name()
+ else:
+ assert False, "Should raise NotImplemented error"
+
+ # CreateChannel failed, we expect no new channel
+ check_conn_properties(q, bus, conn, [old_tubes_channel_properties])
+
+ # Try to CreateChannel with missing properties ("Service")
+ # Salut must return an error
+ try:
+ requestotron.CreateChannel(
+ {'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube',
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType':
+ HT_CONTACT,
+ 'org.freedesktop.Telepathy.Channel.TargetHandle':
+ handle
+ });
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == INVALID_ARGUMENT, e.get_dbus_name()
+ else:
+ assert False, "Should raise InvalidArgument error"
+
+ # CreateChannel failed, we expect no new channel
+ check_conn_properties(q, bus, conn, [old_tubes_channel_properties])
+
+ # Try to CreateChannel with correct properties
+ # Salut must succeed
+ call_async(q, requestotron, 'CreateChannel',
+ {'org.freedesktop.Telepathy.Channel.ChannelType':
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube',
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType':
+ HT_CONTACT,
+ 'org.freedesktop.Telepathy.Channel.TargetHandle':
+ handle,
+ 'org.freedesktop.Telepathy.Channel.Type.StreamTube.Service':
+ "newecho"
+ });
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='CreateChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+
+ assert len(ret.value) == 2 # CreateChannel returns 2 values: o, a{sv}
+ new_chan_path = ret.value[0]
+ stream_tube_channel_properties = ret.value
+ # The path of the Channel.Type.Tubes object MUST be different to the path
+ # of the Channel.Type.StreamTube object !
+ assert chan_path != new_chan_path
+
+ channels = new_sig.args[0]
+ # tubes and tube channels are announced
+ assert len(channels) == 2
+
+ check_conn_properties(q, bus, conn,
+ [old_tubes_channel_properties, stream_tube_channel_properties])
+
+ assert stream_tube_channel_properties[1]['org.freedesktop.Telepathy.Channel.Type.StreamTube.Service'] == \
+ 'newecho'
+ assert stream_tube_channel_properties[1]['org.freedesktop.Telepathy.Channel.Type.StreamTube.SupportedSocketTypes'] == \
+ {SOCKET_ADDRESS_TYPE_UNIX: [SOCKET_ACCESS_CONTROL_LOCALHOST],
+ SOCKET_ADDRESS_TYPE_IPV4: [SOCKET_ACCESS_CONTROL_LOCALHOST],
+ SOCKET_ADDRESS_TYPE_IPV6: [SOCKET_ACCESS_CONTROL_LOCALHOST]}
+
+ # continue
+ tubes_channel = make_channel_proxy(conn, chan_path, "Channel.Type.Tubes")
+ tube_channel = make_channel_proxy(conn, new_chan_path,
+ "Channel.Type.StreamTube")
+ check_channel_properties(q, bus, conn, tubes_channel, "Tubes", handle,
+ contact_name)
+ check_channel_properties(q, bus, conn, tube_channel, "StreamTube",
+ handle, contact_name, 3)
+
+ tube_channel.Offer(SOCKET_ADDRESS_TYPE_UNIX, dbus.ByteArray(server_socket_address),
+ SOCKET_ACCESS_CONTROL_LOCALHOST, {'foo': 'bar'})
+
+ e = q.expect('stream-iq')
+ iq_tube = xpath.queryForNodes('/iq/tube', e.stanza)[0]
+ transport = xpath.queryForNodes('/iq/tube/transport', e.stanza)[0]
+ assert iq_tube.attributes['type'] == 'stream'
+ assert iq_tube.attributes['service'] == 'newecho', \
+ iq_tube.attributes['service']
+ assert iq_tube.attributes['id'] is not None
+ port = transport.attributes['port']
+ assert port is not None
+ port = int(port)
+ assert port > 1024
+ assert port < 65536
+
+ params = {}
+ parameter_nodes = xpath.queryForNodes('/iq/tube/parameters/parameter',
+ e.stanza)
+ for node in parameter_nodes:
+ assert node['name'] not in params
+ params[node['name']] = (node['type'], str(node))
+ assert params == {'foo': ('str', 'bar')}, params
+
+ # find the right host/IP address because Salut checks it
+ self_handle = conn.GetSelfHandle()
+ self_handle_name = conn.InspectHandles(HT_CONTACT, [self_handle])[0]
+ AvahiListener(q).listen_for_service("_presence._tcp")
+ e = q.expect('service-added', name = self_handle_name,
+ protocol = avahi.PROTO_INET)
+ service = e.service
+ service.resolve()
+ e = q.expect('service-resolved', service = service)
+ host_name = e.host_name
+
+ client = ClientCreator(reactor, ClientGreeter)
+ client.connectTCP(host_name, port).addCallback(client_connected_cb)
+
+ e = q.expect('client-connected')
+ client_transport = e.transport
+ client_transport.write(test_string)
+
+ e = q.expect('server-data-received')
+ assert e.data == test_string
+
+ e = q.expect('client-data-received')
+ assert e.data == string.swapcase(test_string)
+
+ # Close the tubes propertly
+ for i in tubes_channel.ListTubes():
+ tubes_channel.CloseTube(i[0])
+ conn.Disconnect()
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/tubes/request-invalid-dbus-tube.py b/salut/tests/twisted/avahi/tubes/request-invalid-dbus-tube.py
new file mode 100644
index 000000000..f1cb8792b
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/request-invalid-dbus-tube.py
@@ -0,0 +1,62 @@
+from saluttest import exec_test
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+import avahi
+import dbus
+import os
+import errno
+import string
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy, Event, EventPattern, call_async, \
+ tp_name_prefix, sync_dbus
+
+from twisted.words.xish import xpath, domish
+from twisted.internet.protocol import Factory, Protocol, ClientCreator
+from twisted.internet import reactor
+import constants as cs
+
+print "FIXME: disabled because new DBus tube API is not implemented"
+# exiting 77 causes automake to consider the test to have been skipped
+raise SystemExit(77)
+
+PUBLISHED_NAME="test-tube"
+
+CHANNEL_TYPE_TUBES = "org.freedesktop.Telepathy.Channel.Type.Tubes"
+C_T_DTUBE = 'org.freedesktop.Telepathy.Channel.Type.DBusTube'
+HT_CONTACT = 1
+
+invalid_service_names = [ 'invalidServiceName'
+ , 'one ten hundred thousand million'
+ , 'me.is.it.you?.hello.you.sexy.sons.o.@#$%.heh'
+ , ':1.1'
+ , ''
+ ]
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+ requestotron = dbus.Interface(conn,
+ 'org.freedesktop.Telepathy.Connection.Interface.Requests')
+
+ for invalid_service_name in invalid_service_names:
+ try:
+ requestotron.CreateChannel(
+ {'org.freedesktop.Telepathy.Channel.ChannelType':
+ C_T_DTUBE,
+ 'org.freedesktop.Telepathy.Channel.TargetHandleType':
+ HT_CONTACT,
+ 'org.freedesktop.Telepathy.Channel.TargetID':
+ 'alice',
+ C_T_DTUBE + '.ServiceName':
+ invalid_service_name
+ });
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == cs.INVALID_ARGUMENT,\
+ (e.get_dbus_name(), invalid_service_name)
+ else:
+ assert False, "Should raise InvalidArgument error"
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/tubes/request-muc-tubes.py b/salut/tests/twisted/avahi/tubes/request-muc-tubes.py
new file mode 100644
index 000000000..999e85bb6
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/request-muc-tubes.py
@@ -0,0 +1,155 @@
+
+"""
+Test requesting of muc tubes channels using the old and new request API.
+"""
+
+import dbus
+import avahitest
+
+from twisted.words.xish import domish
+
+from saluttest import exec_test, wait_for_contact_list
+from servicetest import call_async, EventPattern, make_channel_proxy
+from constants import *
+
+def test(q, bus, conn):
+ self_name = 'testsuite' + '@' + avahitest.get_host_name()
+
+ conn.Connect()
+
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+ # FIXME: this is a hack to be sure to have all the contact list channels
+ # announced so they won't interfere with the muc ones announces.
+ wait_for_contact_list(q, conn)
+
+ # check if we can request roomlist channels
+ properties = conn.GetAll(CONN_IFACE_REQUESTS, dbus_interface=PROPERTIES_IFACE)
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_TUBES,
+ TARGET_HANDLE_TYPE: HT_ROOM},
+ [TARGET_HANDLE, TARGET_ID],
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+
+ # request a muc tubes channel using the old API
+ handle = conn.RequestHandles(HT_ROOM, ['my-first-room'])[0]
+ call_async(q, conn, 'RequestChannel', CHANNEL_TYPE_TUBES, HT_ROOM, handle, True)
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='RequestChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+
+ path1 = ret.value[0]
+ chan = make_channel_proxy(conn, path1, "Channel")
+
+ # text and tubes channels are announced
+ channels = new_sig.args[0]
+ assert len(channels) == 2
+ got_text, got_tubes = False, False
+
+ for path, props in channels:
+ if props[CHANNEL_TYPE] == CHANNEL_TYPE_TEXT:
+ got_text = True
+ assert props[REQUESTED] == False
+ elif props[CHANNEL_TYPE] == CHANNEL_TYPE_TUBES:
+ got_tubes = True
+ assert props[REQUESTED] == True
+ else:
+ assert False
+
+ assert props[TARGET_HANDLE_TYPE] == HT_ROOM
+ assert props[TARGET_HANDLE] == handle
+ assert props[TARGET_ID] == 'my-first-room'
+ assert props[INITIATOR_HANDLE] == conn.GetSelfHandle()
+ assert props[INITIATOR_ID] == self_name
+
+ # Exercise basic Channel Properties from spec 0.17.7
+ channel_props = chan.GetAll(CHANNEL, dbus_interface=PROPERTIES_IFACE)
+ assert channel_props.get('TargetHandle') == handle,\
+ channel_props.get('TargetHandle')
+ assert channel_props['TargetID'] == 'my-first-room', channel_props
+ assert channel_props.get('TargetHandleType') == HT_ROOM,\
+ channel_props.get('TargetHandleType')
+ assert channel_props.get('ChannelType') == \
+ CHANNEL_TYPE_TUBES, channel_props.get('ChannelType')
+ assert channel_props['Requested'] == True
+ assert channel_props['InitiatorID'] == self_name
+ assert channel_props['InitiatorHandle'] == conn.GetSelfHandle()
+
+ requestotron = dbus.Interface(conn, CONN_IFACE_REQUESTS)
+
+ # create muc channel using new API
+ call_async(q, requestotron, 'CreateChannel',
+ { CHANNEL_TYPE: CHANNEL_TYPE_TUBES,
+ TARGET_HANDLE_TYPE: HT_ROOM,
+ TARGET_ID: 'my-second-room',
+ })
+
+ ret, old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='CreateChannel'),
+ EventPattern('dbus-signal', signal='NewChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'),
+ )
+ path2 = ret.value[0]
+ chan = make_channel_proxy(conn, path2, "Channel")
+
+ handle = conn.RequestHandles(HT_ROOM, ['my-second-room'])[0]
+
+ tubes_props = ret.value[1]
+ assert tubes_props[CHANNEL_TYPE] == CHANNEL_TYPE_TUBES
+ assert tubes_props[TARGET_HANDLE_TYPE] == HT_ROOM
+ assert tubes_props[TARGET_HANDLE] == handle
+ assert tubes_props[TARGET_ID] == 'my-second-room'
+ assert tubes_props[REQUESTED] == True
+ assert tubes_props[INITIATOR_HANDLE] == conn.GetSelfHandle()
+ assert tubes_props[INITIATOR_ID] == self_name
+
+ # text and tubes channels are announced
+ channels = new_sig.args[0]
+ assert len(channels) == 2
+ got_text, got_tubes = False, False
+
+ for path, props in channels:
+ if props[CHANNEL_TYPE] == CHANNEL_TYPE_TEXT:
+ got_text = True
+ assert props[REQUESTED] == False
+ elif props[CHANNEL_TYPE] == CHANNEL_TYPE_TUBES:
+ got_tubes = True
+ assert props == tubes_props
+ assert path == path2
+ else:
+ assert False
+
+ assert props[TARGET_HANDLE_TYPE] == HT_ROOM
+ assert props[TARGET_HANDLE] == handle
+ assert props[TARGET_ID] == 'my-second-room'
+ assert props[INITIATOR_HANDLE] == conn.GetSelfHandle()
+ assert props[INITIATOR_ID] == self_name
+
+ # ensure roomlist channel
+ yours, ensured_path, ensured_props = requestotron.EnsureChannel(
+ { CHANNEL_TYPE: CHANNEL_TYPE_TUBES,
+ TARGET_HANDLE_TYPE: HT_ROOM,
+ TARGET_HANDLE: handle,
+ })
+
+ assert not yours
+ assert ensured_path == path2, (ensured_path, path2)
+
+ conn.Disconnect()
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='Closed',
+ path=path1),
+ EventPattern('dbus-signal', signal='Closed',
+ path=path2),
+ EventPattern('dbus-signal', signal='ChannelClosed', args=[path1]),
+ EventPattern('dbus-signal', signal='ChannelClosed', args=[path2]),
+ EventPattern('dbus-signal', signal='StatusChanged', args=[2, 1]),
+ )
+
+if __name__ == '__main__':
+ exec_test(test)
+
diff --git a/salut/tests/twisted/avahi/tubes/tube-close.py b/salut/tests/twisted/avahi/tubes/tube-close.py
new file mode 100644
index 000000000..ae2c2af0b
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/tube-close.py
@@ -0,0 +1,71 @@
+"""
+Offer a 1-1 stream tube and close the connection. Salut must just send a
+stanza to close the tube and disconnect.
+"""
+
+from saluttest import exec_test, wait_for_contact_in_publish
+from avahitest import AvahiAnnouncer, AvahiListener
+from avahitest import get_host_name
+import avahi
+
+from xmppstream import setup_stream_listener, connect_to_stream
+from servicetest import make_channel_proxy
+
+from twisted.words.xish import xpath, domish
+
+import dbus
+
+PUBLISHED_NAME="test-tube"
+
+CHANNEL_TYPE_TUBES = "org.freedesktop.Telepathy.Channel.Type.Tubes"
+HT_CONTACT = 1
+HT_CONTACT_LIST = 3
+SOCKET_ADDRESS_TYPE_IPV4 = dbus.UInt32(2)
+SOCKET_ACCESS_CONTROL_LOCALHOST = dbus.UInt32(0)
+
+#print "FIXME: test-tube-close.py disabled because sending a close stanza on "
+#print "disconnection is not yet implemented in telepathy-salut. It requires "
+#print "to ensure the XmppConnection and reestablish it"
+print "FIXME: disabled because 1-1 tubes are disabled for now"
+# exiting 77 causes automake to consider the test to have been skipped
+raise SystemExit(77)
+
+def test(q, bus, conn):
+ # Salut will not connect to this socket, the test finishs before
+ socket_address = ('0.0.0.0', dbus.UInt16(0))
+
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+ basic_txt = { "txtvers": "1", "status": "avail" }
+
+ contact_name = PUBLISHED_NAME + "@" + get_host_name()
+ listener, port = setup_stream_listener(q, contact_name)
+
+ announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, basic_txt)
+
+ handle = wait_for_contact_in_publish(q, bus, conn, contact_name)
+
+ t = conn.RequestChannel(CHANNEL_TYPE_TUBES, HT_CONTACT, handle,
+ True)
+ tubes_channel = make_channel_proxy(conn, t, "Channel.Type.Tubes")
+
+ tubes_channel.OfferStreamTube("http", dbus.Dictionary({}),
+ SOCKET_ADDRESS_TYPE_IPV4, socket_address,
+ SOCKET_ACCESS_CONTROL_LOCALHOST, "")
+
+ e = q.expect('stream-iq')
+
+ # Close the connection just after the tube has been offered.
+ conn.Disconnect()
+
+ # receive the close stanza for the tube
+ event = q.expect('stream-message')
+ message = event.stanza
+ close_node = xpath.queryForNodes('/message/close[@xmlns="%s"]' % NS_TUBES,
+ message)
+ assert close_node is not None
+
+ q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/tubes/tubes-to-nonexistant-ids.py b/salut/tests/twisted/avahi/tubes/tubes-to-nonexistant-ids.py
new file mode 100644
index 000000000..7fb9b162a
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/tubes-to-nonexistant-ids.py
@@ -0,0 +1,54 @@
+"""
+Test that requests for Tubes and StreamTube channels to ids which aren't
+actually on the network fail gracefully with NotAvailable
+"""
+
+from saluttest import exec_test
+
+from constants import (
+ HT_CONTACT, CONN_IFACE_REQUESTS,
+ CHANNEL_TYPE, TARGET_HANDLE_TYPE, TARGET_HANDLE,
+ CHANNEL_TYPE_TUBES, CHANNEL_TYPE_STREAM_TUBE,
+ NOT_AVAILABLE
+ )
+
+import dbus
+
+arbitrary_ids = [ "DooN4Bei@TheeK6bo-Tegh4aci", "ahrui1iM@Dai6igho-ADetaes3" ]
+
+print "FIXME: disabled because 1-1 tubes are disabled for now"
+# exiting 77 causes automake to consider the test to have been skipped
+raise SystemExit(77)
+
+def test(q, bus, conn):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L])
+
+ h1, h2 = conn.RequestHandles(HT_CONTACT, arbitrary_ids)
+
+ try:
+ conn.RequestChannel(CHANNEL_TYPE_TUBES, HT_CONTACT, h1, True)
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == NOT_AVAILABLE, e.get_dbus_name()
+ else:
+ assert False, "Should raise NotAvailable error"
+
+ requestotron = dbus.Interface(conn, CONN_IFACE_REQUESTS)
+
+ try:
+ requestotron.CreateChannel({
+ CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE,
+ TARGET_HANDLE_TYPE: HT_CONTACT,
+ TARGET_HANDLE: h2,
+ CHANNEL_TYPE_STREAM_TUBE + ".Service": "com.example",
+ })
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == NOT_AVAILABLE, e.get_dbus_name()
+ else:
+ assert False, "Should raise NotAvailable error"
+
+ conn.Disconnect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahi/tubes/tubetestutil.py b/salut/tests/twisted/avahi/tubes/tubetestutil.py
new file mode 100644
index 000000000..bec6ad484
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/tubetestutil.py
@@ -0,0 +1,122 @@
+from saluttest import make_connection, wait_for_contact_list
+from avahitest import get_host_name
+from servicetest import make_channel_proxy
+
+import constants as cs
+
+def connect_two_accounts(q, bus, conn):
+ # first connection: connect
+ contact1_name = "testsuite" + "@" + get_host_name()
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_NONE_SPECIFIED])
+
+ # FIXME: this is a hack to be sure to have all the contact list channels
+ # announced so they won't interfere with other channels announces.
+ wait_for_contact_list(q, conn)
+
+ # second connection: connect
+ conn2_params = {
+ 'published-name': 'testsuite2',
+ 'first-name': 'test2',
+ 'last-name': 'suite2',
+ }
+ contact2_name = "testsuite2" + "@" + get_host_name()
+ conn2 = make_connection(bus, lambda x: None, conn2_params)
+ conn2.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_NONE_SPECIFIED])
+
+ wait_for_contact_list(q, conn2)
+
+ # first connection: get the contact list
+ publish_handle = conn.RequestHandles(cs.HT_LIST, ["publish"])[0]
+ conn1_publish = conn.RequestChannel(cs.CHANNEL_TYPE_CONTACT_LIST,
+ cs.HT_LIST, publish_handle, False)
+ conn1_publish_proxy = bus.get_object(conn.bus_name, conn1_publish)
+
+ # second connection: get the contact list
+ publish_handle = conn2.RequestHandles(cs.HT_LIST, ["publish"])[0]
+ conn2_publish = conn2.RequestChannel(cs.CHANNEL_TYPE_CONTACT_LIST,
+ cs.HT_LIST, publish_handle, False)
+ conn2_publish_proxy = bus.get_object(conn2.bus_name, conn2_publish)
+
+ # first connection: wait to see contact2
+ # The signal MembersChanged may be already emitted... check the Members
+ # property first
+ contact2_handle_on_conn1 = 0
+ conn1_members = conn1_publish_proxy.Get(cs.CHANNEL_IFACE_GROUP, 'Members',
+ dbus_interface=cs.PROPERTIES_IFACE)
+ for h in conn1_members:
+ name = conn.InspectHandles(cs.HT_CONTACT, [h])[0]
+ if name == contact2_name:
+ contact2_handle_on_conn1 = h
+ while contact2_handle_on_conn1 == 0:
+ e = q.expect('dbus-signal', signal='MembersChanged',
+ path=conn1_publish)
+ for h in e.args[1]:
+ name = conn.InspectHandles(cs.HT_CONTACT, [h])[0]
+ if name == contact2_name:
+ contact2_handle_on_conn1 = h
+
+ # second connection: wait to see contact1
+ # The signal MembersChanged may be already emitted... check the Members
+ # property first
+ contact1_handle_on_conn2 = 0
+ conn2_members = conn2_publish_proxy.Get(
+ 'org.freedesktop.Telepathy.Channel.Interface.Group', 'Members',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ for h in conn2_members:
+ name = conn2.InspectHandles(cs.HT_CONTACT, [h])[0]
+ if name == contact1_name:
+ contact1_handle_on_conn2 = h
+ while contact1_handle_on_conn2 == 0:
+ e = q.expect('dbus-signal', signal='MembersChanged',
+ path=conn2_publish)
+ for h in e.args[1]:
+ name = conn2.InspectHandles(cs.HT_CONTACT, [h])[0]
+ if name == contact1_name:
+ contact1_handle_on_conn2 = h
+
+ return contact1_name, conn2, contact2_name, contact2_handle_on_conn1, contact1_handle_on_conn2
+
+def join_muc(q, conn, muc_name):
+ self_handle = conn.GetSelfHandle()
+ muc_handle = conn.RequestHandles(cs.HT_ROOM, [muc_name])[0]
+ path = conn.RequestChannel(cs.CHANNEL_TYPE_TEXT, cs.HT_ROOM, muc_handle, True)
+ # added as remote pending
+ q.expect('dbus-signal', signal='MembersChanged', path=path,
+ args=['', [], [], [], [self_handle], self_handle, 0])
+ # added as member
+ q.expect('dbus-signal', signal='MembersChanged', path=path,
+ args=['', [self_handle], [], [], [], self_handle, 0])
+ group = make_channel_proxy(conn, path, "Channel.Interface.Group")
+
+ return muc_handle, group
+
+def invite_to_muc(q, group1, conn2, invited_handle, inviter_handle):
+ # first connection: invite contact
+ group1.AddMembers([invited_handle], "Let's tube!")
+
+ # channel is created on conn2
+ e = q.expect('dbus-signal', signal='NewChannel', path=conn2.object_path)
+ path = e.args[0]
+ group2 = make_channel_proxy(conn2, path, "Channel.Interface.Group")
+
+ # we are invited to the muc
+ # added as local pending
+ conn2_self_handle = conn2.GetSelfHandle()
+ q.expect('dbus-signal', signal='MembersChanged', path=path,
+ args=["Let's tube!", [], [], [conn2_self_handle], [],
+ inviter_handle, 4])
+
+ # second connection: accept the invite
+ group2.AddMembers([conn2_self_handle], "")
+
+ # added as remote pending
+ q.expect('dbus-signal', signal='MembersChanged', path=path,
+ args=['', [], [], [], [conn2_self_handle], conn2_self_handle, 0])
+
+ # added as member
+ q.expect('dbus-signal', signal='MembersChanged', path=path,
+ args=['', [conn2_self_handle], [], [], [], conn2_self_handle, 0])
+
+ return group2
diff --git a/salut/tests/twisted/avahi/tubes/two-muc-dbus-tubes.py b/salut/tests/twisted/avahi/tubes/two-muc-dbus-tubes.py
new file mode 100644
index 000000000..a87a4cbe4
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/two-muc-dbus-tubes.py
@@ -0,0 +1,275 @@
+from saluttest import exec_test
+import dbus
+from dbus.service import method, signal, Object
+
+from servicetest import make_channel_proxy, call_async, EventPattern, Event
+
+import constants as cs
+import tubetestutil as t
+
+sample_parameters = dbus.Dictionary({
+ 's': 'hello',
+ 'ay': dbus.ByteArray('hello'),
+ 'u': dbus.UInt32(123),
+ 'i': dbus.Int32(-123),
+ }, signature='sv')
+
+muc_name = "test-two-muc-stream-tubes"
+
+def check_dbus_names(tube, members):
+ names = tube.Get(cs.CHANNEL_TYPE_DBUS_TUBE, 'DBusNames',
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert set(names.keys()) == set(members), names.keys()
+
+SERVICE = "org.freedesktop.Telepathy.Tube.Test"
+IFACE = SERVICE
+PATH = "/org/freedesktop/Telepathy/Tube/Test"
+
+class Test(Object):
+ def __init__(self, tube, q):
+ super(Test, self).__init__(tube, PATH)
+ self.tube = tube
+ self.q = q
+
+ @signal(dbus_interface=IFACE, signature='s')
+ def MySig(self, arg):
+ pass
+
+ @method(dbus_interface=IFACE, in_signature='u', out_signature='u')
+ def MyMethod(self, arg):
+ self.q.append(Event('tube-dbus-call', method='MyMethod', args=[arg]))
+ return arg * 10
+
+def test(q, bus, conn):
+
+ contact1_name, conn2, contact2_name, contact2_handle_on_conn1,\
+ contact1_handle_on_conn2 = t.connect_two_accounts(q, bus, conn)
+
+ conn1_self_handle = conn.GetSelfHandle()
+ conn2_self_handle = conn2.GetSelfHandle()
+
+ # first connection: join muc
+ muc_handle1, group1 = t.join_muc(q, conn, muc_name)
+
+ # Can we request muc D-Bus tubes?
+ properties = conn.GetAll(cs.CONN_IFACE_REQUESTS,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ assert ({cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_DBUS_TUBE,
+ cs.TARGET_HANDLE_TYPE: cs.HT_ROOM},
+ [cs.TARGET_HANDLE, cs.TARGET_ID, cs.DBUS_TUBE_SERVICE_NAME]
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+
+ # request a stream tube channel (new API)
+ requestotron = dbus.Interface(conn, cs.CONN_IFACE_REQUESTS)
+
+ requestotron.CreateChannel({
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_DBUS_TUBE,
+ cs.TARGET_HANDLE_TYPE: cs.HT_ROOM,
+ cs.TARGET_ID: muc_name,
+ cs.DBUS_TUBE_SERVICE_NAME: 'com.example.TestCase'})
+
+ e = q.expect('dbus-signal', signal='NewChannels')
+ channels = e.args[0]
+ assert len(channels) == 2
+
+ # get the list of all channels to check that newly announced ones are in it
+ all_channels = conn.Get(cs.CONN_IFACE_REQUESTS, 'Channels', dbus_interface=cs.PROPERTIES_IFACE,
+ byte_arrays=True)
+
+ got_tubes, got_tube = False, False
+ for path, props in channels:
+ if props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TUBES:
+ got_tubes = True
+ assert props[cs.REQUESTED] == False
+ assert props[cs.INTERFACES] == [cs.CHANNEL_IFACE_GROUP]
+ elif props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_DBUS_TUBE:
+ got_tube = True
+ assert props[cs.REQUESTED] == True
+ assert props[cs.INTERFACES] == [cs.CHANNEL_IFACE_GROUP,
+ cs.CHANNEL_IFACE_TUBE]
+ assert props[cs.DBUS_TUBE_SERVICE_NAME] == 'com.example.TestCase'
+ assert props[cs.DBUS_TUBE_SUPPORTED_ACCESS_CONTROLS] == [
+ cs.SOCKET_ACCESS_CONTROL_CREDENTIALS, cs.SOCKET_ACCESS_CONTROL_LOCALHOST]
+
+ contact1_tube = bus.get_object(conn.bus_name, path)
+ contact1_dbus_tube = make_channel_proxy(conn, path,
+ "Channel.Type.DBusTube")
+ contact1_tube_channel = make_channel_proxy(conn, path, "Channel")
+ tube1_path = path
+ else:
+ assert False
+
+ assert props[cs.INITIATOR_HANDLE] == conn1_self_handle
+ assert props[cs.INITIATOR_ID] == contact1_name
+ assert props[cs.TARGET_ID] == muc_name
+
+ assert (path, props) in all_channels, (path, props)
+
+ assert got_tubes
+ assert got_tube
+
+ state = contact1_dbus_tube.Get(cs.CHANNEL_IFACE_TUBE, 'State',
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert state == cs.TUBE_CHANNEL_STATE_NOT_OFFERED
+
+ call_async(q, contact1_dbus_tube, 'Offer', sample_parameters,
+ cs.SOCKET_ACCESS_CONTROL_CREDENTIALS)
+
+ _, e = q.expect_many(
+ EventPattern('dbus-signal', signal='TubeChannelStateChanged',
+ args=[cs.TUBE_CHANNEL_STATE_OPEN]),
+ EventPattern('dbus-return', method='Offer'))
+
+ tube_addr1 = e.value[0]
+
+ state = contact1_dbus_tube.Get(cs.CHANNEL_IFACE_TUBE, 'State',
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert state == cs.TUBE_CHANNEL_STATE_OPEN
+
+ check_dbus_names(contact1_dbus_tube, [conn1_self_handle])
+
+ t.invite_to_muc(q, group1, conn2, contact2_handle_on_conn1, contact1_handle_on_conn2)
+
+ # tubes channel is created
+ e, dbus_names_e = q.expect_many(
+ EventPattern('dbus-signal', signal='NewChannels'),
+ EventPattern('dbus-signal', signal='DBusNamesChanged', interface=cs.CHANNEL_TYPE_DBUS_TUBE))
+
+ channels = e.args[0]
+ assert len(channels) == 2
+
+ # get the list of all channels to check that newly announced ones are in it
+ all_channels = conn2.Get(cs.CONN_IFACE_REQUESTS, 'Channels', dbus_interface=cs.PROPERTIES_IFACE,
+ byte_arrays=True)
+
+ got_tubes, got_tube = False, False
+ for path, props in channels:
+ if props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TUBES:
+ got_tubes = True
+ assert props[cs.REQUESTED] == False
+ assert props[cs.INTERFACES] == [cs.CHANNEL_IFACE_GROUP]
+ elif props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_DBUS_TUBE:
+ got_tube = True
+ assert props[cs.REQUESTED] == False
+ assert props[cs.INTERFACES] == [cs.CHANNEL_IFACE_GROUP,
+ cs.CHANNEL_IFACE_TUBE]
+ assert props[cs.TUBE_PARAMETERS] == sample_parameters
+ assert props[cs.DBUS_TUBE_SERVICE_NAME] == 'com.example.TestCase'
+ assert props[cs.DBUS_TUBE_SUPPORTED_ACCESS_CONTROLS] == [
+ cs.SOCKET_ACCESS_CONTROL_CREDENTIALS, cs.SOCKET_ACCESS_CONTROL_LOCALHOST]
+
+ contact2_tube = bus.get_object(conn.bus_name, path)
+ contact2_dbus_tube = make_channel_proxy(conn, path,
+ "Channel.Type.DBusTube")
+ contact2_tube_channel = make_channel_proxy(conn, path, "Channel")
+ tube2_path = path
+ else:
+ assert False
+
+ assert props[cs.INITIATOR_HANDLE] == contact1_handle_on_conn2
+ assert props[cs.INITIATOR_ID] == contact1_name
+ assert props[cs.TARGET_ID] == muc_name
+
+ assert (path, props) in all_channels, (path, props)
+
+ assert got_tubes
+ assert got_tube
+
+ # second connection: check DBusNamesChanged signal
+ assert dbus_names_e.path == tube2_path
+ added, removed = dbus_names_e.args
+ assert added.keys() == [contact1_handle_on_conn2]
+ assert removed == []
+
+ state = contact2_tube.Get(cs.CHANNEL_IFACE_TUBE, 'State',
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert state == cs.TUBE_CHANNEL_STATE_LOCAL_PENDING
+
+ # first connection: contact2 is not in the tube yet
+ check_dbus_names(contact1_dbus_tube, [conn1_self_handle])
+
+ # second connection: accept the tube (new API)
+ tube_addr2 = unix_socket_adr = contact2_dbus_tube.Accept(cs.SOCKET_ACCESS_CONTROL_CREDENTIALS)
+
+ state = contact2_tube.Get(cs.CHANNEL_IFACE_TUBE, 'State',
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert state == cs.TUBE_CHANNEL_STATE_OPEN
+
+ e, dbus_names_e = q.expect_many(
+ EventPattern('dbus-signal', signal='TubeChannelStateChanged',
+ path=tube2_path, args=[cs.TUBE_CHANNEL_STATE_OPEN]),
+ EventPattern('dbus-signal', signal='DBusNamesChanged',
+ interface=cs.CHANNEL_TYPE_DBUS_TUBE, path=tube1_path))
+
+ added, removed = dbus_names_e.args
+ assert added.keys() == [contact2_handle_on_conn1]
+ assert removed == []
+
+ check_dbus_names(contact1_dbus_tube, [conn1_self_handle, contact2_handle_on_conn1])
+ check_dbus_names(contact2_dbus_tube, [conn2_self_handle, contact1_handle_on_conn2])
+
+ tube2_names = contact2_dbus_tube.Get(cs.CHANNEL_TYPE_DBUS_TUBE, 'DBusNames',
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ tube_conn1 = dbus.connection.Connection(tube_addr1)
+ tube_conn2 = dbus.connection.Connection(tube_addr2)
+
+ obj1 = Test(tube_conn1, q)
+
+ # fire 'MySig' signal on the tube
+ def my_sig_cb (arg, sender=None):
+ assert tube2_names[contact1_handle_on_conn2] == sender
+
+ q.append(Event('tube-dbus-signal', signal='MySig', args=[arg]))
+
+ tube_conn2.add_signal_receiver(my_sig_cb, 'MySig', IFACE, path=PATH,
+ sender_keyword='sender')
+
+ obj1.MySig('hello')
+ q.expect('tube-dbus-signal', signal='MySig', args=['hello'])
+
+ # call remote method
+ def my_method_cb(result):
+ q.append(Event('tube-dbus-return', method='MyMethod', value=[result]))
+
+ def my_method_error(e):
+ assert False, e
+
+ tube_conn2.get_object(tube2_names[contact1_handle_on_conn2], PATH).MyMethod(
+ 42, dbus_interface=IFACE,
+ reply_handler=my_method_cb, error_handler=my_method_error)
+
+ q.expect('tube-dbus-call', method='MyMethod', args=[42])
+ q.expect('tube-dbus-return', method='MyMethod', value=[420])
+
+ call_async(q, contact1_tube_channel, 'Close')
+ _, _, _, _, dbus_names_e = q.expect_many(
+ EventPattern('dbus-return', method='Close'),
+ EventPattern('dbus-signal', signal='Closed'),
+ EventPattern('dbus-signal', signal='TubeClosed'),
+ EventPattern('dbus-signal', signal='ChannelClosed'),
+ EventPattern('dbus-signal', signal='DBusNamesChanged',
+ interface=cs.CHANNEL_TYPE_DBUS_TUBE, path=tube2_path))
+
+ # Contact1 is removed from the tube
+ added, removed = dbus_names_e.args
+ assert added == {}
+ assert removed == [contact1_handle_on_conn2]
+
+ check_dbus_names(contact2_dbus_tube, [conn2_self_handle])
+
+ call_async(q, contact2_tube_channel, 'Close')
+ q.expect_many(
+ EventPattern('dbus-return', method='Close'),
+ EventPattern('dbus-signal', signal='Closed'),
+ EventPattern('dbus-signal', signal='TubeClosed'),
+ EventPattern('dbus-signal', signal='ChannelClosed'))
+
+ conn.Disconnect()
+ conn2.Disconnect()
+
+if __name__ == '__main__':
+ # increase timer because Clique takes some time to join an existing muc
+ exec_test(test, timeout=60)
diff --git a/salut/tests/twisted/avahi/tubes/two-muc-stream-tubes.py b/salut/tests/twisted/avahi/tubes/two-muc-stream-tubes.py
new file mode 100644
index 000000000..53d45366a
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/two-muc-stream-tubes.py
@@ -0,0 +1,376 @@
+from saluttest import exec_test
+import dbus
+import os
+import errno
+import string
+
+from servicetest import make_channel_proxy, Event, call_async, EventPattern
+
+from twisted.internet.protocol import Factory, Protocol, ClientCreator
+from twisted.internet import reactor
+from constants import *
+import tubetestutil as t
+
+sample_parameters = dbus.Dictionary({
+ 's': 'hello',
+ 'ay': dbus.ByteArray('hello'),
+ 'u': dbus.UInt32(123),
+ 'i': dbus.Int32(-123),
+ }, signature='sv')
+
+test_string = "This string travels on a tube !"
+
+muc_name = "test-two-muc-stream-tubes"
+muc2_name = "test-two-muc-stream-tubes-2"
+
+SERVER_WELCOME_MSG = "Welcome!"
+
+def test(q, bus, conn):
+
+ # define a basic tcp server that echoes what the client says, but with
+ # swapcase
+ class TrivialServer(Protocol):
+ def dataReceived(self, data):
+ self.transport.write(string.swapcase(data))
+ e = Event('server-data-received', service = self, data = data)
+ q.append(e)
+
+ def connectionMade(self):
+ e = Event('server-connected', transport = self.transport)
+ q.append(e)
+
+ # send welcome message to the client
+ self.transport.write(SERVER_WELCOME_MSG)
+
+ # define a basic tcp client
+ class ClientGreeter(Protocol):
+ def dataReceived(self, data):
+ e = Event('client-data-received', service = self, data = data)
+ q.append(e)
+
+ def client_connected_cb(p):
+ e = Event('client-connected', transport = p.transport)
+ q.append(e)
+
+ # create the server
+ factory = Factory()
+ factory.protocol = TrivialServer
+ server_socket_address = os.getcwd() + '/stream'
+ try:
+ os.remove(server_socket_address)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ l = reactor.listenUNIX(server_socket_address, factory)
+
+ contact1_name, conn2, contact2_name, contact2_handle_on_conn1,\
+ contact1_handle_on_conn2 = t.connect_two_accounts(q, bus, conn)
+
+ conn1_self_handle = conn.GetSelfHandle()
+
+ # first connection: join muc
+ muc_handle1, group1 = t.join_muc(q, conn, muc_name)
+
+ t.invite_to_muc(q, group1, conn2, contact2_handle_on_conn1, contact1_handle_on_conn2)
+
+ # first connection: offer a muc stream tube (old API)
+ tubes1_path = conn.RequestChannel(CHANNEL_TYPE_TUBES, HT_ROOM, muc_handle1,
+ True)
+ contact1_tubes_channel = make_channel_proxy(conn, tubes1_path,
+ "Channel.Type.Tubes")
+
+ q.expect('dbus-signal', signal='NewChannel',
+ args=[tubes1_path, CHANNEL_TYPE_TUBES, HT_ROOM, muc_handle1, True])
+
+ conn1_tube_id = contact1_tubes_channel.OfferStreamTube("http",
+ sample_parameters, SOCKET_ADDRESS_TYPE_UNIX,
+ dbus.ByteArray(server_socket_address),
+ SOCKET_ACCESS_CONTROL_LOCALHOST, "")
+
+ e = q.expect('dbus-signal', signal='NewTube', path=tubes1_path)
+ tube = e.args
+ assert tube[1] == conn1_self_handle # initiator
+ assert tube[2] == 1 # type = stream tube
+ assert tube[3] == 'http' # service
+ assert tube[4] == sample_parameters # paramaters
+ assert tube[5] == TUBE_CHANNEL_STATE_OPEN
+
+ contact2_channeltype = None
+ while contact2_channeltype == None:
+ e = q.expect('dbus-signal', signal='NewChannel')
+ if (e.args[1] == CHANNEL_TYPE_TUBES) and \
+ (e.path.endswith("testsuite2") == True):
+ tubes2_path = e.args[0]
+ contact2_channeltype = e.args[1]
+
+ contact2_tubes_channel = make_channel_proxy(conn2, tubes2_path,
+ "Channel.Type.Tubes")
+
+ contact2_tubes = contact2_tubes_channel.ListTubes()
+ assert len(contact2_tubes) == 1
+ contact2_tube = contact2_tubes[0]
+ assert contact2_tube[0] is not None # tube id
+ conn2_tube_id = contact2_tube[0]
+ assert contact2_tube[1] is not None # initiator
+ assert contact2_tube[2] == 1 # type = stream tube
+ assert contact2_tube[3] == 'http' # service = http
+ assert contact2_tube[4] is not None # parameters
+ assert contact2_tube[5] == 0, contact2_tube[5] # status = local pending
+
+ # second connection: accept the tube (old API)
+ unix_socket_adr = contact2_tubes_channel.AcceptStreamTube(
+ contact2_tube[0], 0, 0, '', byte_arrays=True)
+
+ e = q.expect('dbus-signal', signal='TubeStateChanged', path=tubes2_path)
+ id, state = e.args
+ assert id == conn2_tube_id
+ assert state == TUBE_CHANNEL_STATE_OPEN
+
+ client = ClientCreator(reactor, ClientGreeter)
+ client.connectUNIX(unix_socket_adr).addCallback(client_connected_cb)
+
+ # server got the connection
+ _, e = q.expect_many(
+ EventPattern('server-connected'),
+ EventPattern('client-connected'))
+
+ client_transport = e.transport
+
+ sig, e = q.expect_many(
+ EventPattern('dbus-signal', signal='StreamTubeNewConnection',
+ path=tubes1_path),
+ EventPattern('client-data-received'))
+
+ id, handle = sig.args
+ assert id == conn1_tube_id
+ assert handle == contact2_handle_on_conn1
+
+ # client receives server's welcome message
+ assert e.data == SERVER_WELCOME_MSG
+
+ client_transport.write(test_string)
+
+ server_received, client_received = q.expect_many(
+ EventPattern('server-data-received'),
+ EventPattern('client-data-received'))
+
+ assert server_received.data == test_string
+ assert client_received.data == string.swapcase(test_string)
+
+ # contact1 closes the tube
+ contact1_tubes_channel.CloseTube(conn1_tube_id)
+ q.expect('dbus-signal', signal='TubeClosed', args=[conn1_tube_id])
+
+ # contact2 closes the tube
+ contact2_tubes_channel.CloseTube(conn2_tube_id)
+ q.expect('dbus-signal', signal='TubeClosed', args=[conn2_tube_id])
+
+ # Now contact1 will create a new muc stream tube to another room using the
+ # new API
+
+ # Can we request muc stream tubes?
+ properties = conn.GetAll(CONN_IFACE_REQUESTS,
+ dbus_interface=PROPERTIES_IFACE)
+
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE,
+ TARGET_HANDLE_TYPE: HT_ROOM},
+ [TARGET_HANDLE, TARGET_ID, STREAM_TUBE_SERVICE]
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+
+ # request a stream tube channel (new API)
+ requestotron = dbus.Interface(conn, CONN_IFACE_REQUESTS)
+
+ requestotron.CreateChannel({
+ CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE,
+ TARGET_HANDLE_TYPE: HT_ROOM,
+ TARGET_ID: muc2_name,
+ STREAM_TUBE_SERVICE: 'test'})
+
+ e = q.expect('dbus-signal', signal='NewChannels')
+ channels = e.args[0]
+ assert len(channels) == 3
+
+ # get the list of all channels to check that newly announced ones are in it
+ all_channels = conn.Get(CONN_IFACE_REQUESTS, 'Channels', dbus_interface=PROPERTIES_IFACE,
+ byte_arrays=True)
+
+ got_text, got_tubes, got_tube = False, False, False
+ for path, props in channels:
+ if props[CHANNEL_TYPE] == CHANNEL_TYPE_TEXT:
+ got_text = True
+ assert props[REQUESTED] == False
+ group1 = make_channel_proxy(conn, path, "Channel.Interface.Group")
+ txt_path = path
+ elif props[CHANNEL_TYPE] == CHANNEL_TYPE_TUBES:
+ got_tubes = True
+ assert props[REQUESTED] == False
+ assert props[INTERFACES] == [CHANNEL_IFACE_GROUP]
+ elif props[CHANNEL_TYPE] == CHANNEL_TYPE_STREAM_TUBE:
+ got_tube = True
+ assert props[REQUESTED] == True
+ assert props[INTERFACES] == [CHANNEL_IFACE_GROUP,
+ CHANNEL_IFACE_TUBE]
+ assert props[STREAM_TUBE_SERVICE] == 'test'
+
+ contact1_tube = bus.get_object(conn.bus_name, path)
+ contact1_stream_tube = make_channel_proxy(conn, path,
+ "Channel.Type.StreamTube")
+ contact1_tube_channel = make_channel_proxy(conn, path, "Channel")
+ tube1_path = path
+ else:
+ assert False
+
+ assert props[INITIATOR_HANDLE] == conn1_self_handle
+ assert props[INITIATOR_ID] == contact1_name
+ assert props[TARGET_ID] == muc2_name
+
+ assert (path, props) in all_channels, (path, props)
+
+ assert got_text
+ assert got_tubes
+ assert got_tube
+
+ state = contact1_stream_tube.Get(CHANNEL_IFACE_TUBE, 'State',
+ dbus_interface=PROPERTIES_IFACE)
+ assert state == TUBE_CHANNEL_STATE_NOT_OFFERED
+
+ # added as member
+ q.expect('dbus-signal', signal='MembersChanged', path=txt_path,
+ args=['', [conn1_self_handle], [], [], [], conn1_self_handle, 0])
+
+ call_async(q, contact1_stream_tube, 'Offer',
+ SOCKET_ADDRESS_TYPE_UNIX, dbus.ByteArray(server_socket_address),
+ SOCKET_ACCESS_CONTROL_LOCALHOST, sample_parameters)
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='TubeChannelStateChanged',
+ args=[TUBE_CHANNEL_STATE_OPEN]),
+ EventPattern('dbus-return', method='Offer'))
+
+ state = contact1_stream_tube.Get(CHANNEL_IFACE_TUBE, 'State',
+ dbus_interface=PROPERTIES_IFACE)
+ assert state == TUBE_CHANNEL_STATE_OPEN
+
+ t.invite_to_muc(q, group1, conn2, contact2_handle_on_conn1, contact1_handle_on_conn2)
+
+ # tubes channel is created
+ e = q.expect('dbus-signal', signal='NewChannels')
+ channels = e.args[0]
+ assert len(channels) == 2
+
+ # get the list of all channels to check that newly announced ones are in it
+ all_channels = conn2.Get(CONN_IFACE_REQUESTS, 'Channels', dbus_interface=PROPERTIES_IFACE,
+ byte_arrays=True)
+
+ got_tubes, got_tube = False, False
+ for path, props in channels:
+ if props[CHANNEL_TYPE] == CHANNEL_TYPE_TUBES:
+ got_tubes = True
+ assert props[REQUESTED] == False
+ assert props[INTERFACES] == [CHANNEL_IFACE_GROUP]
+ elif props[CHANNEL_TYPE] == CHANNEL_TYPE_STREAM_TUBE:
+ got_tube = True
+ assert props[REQUESTED] == False
+ assert props[INTERFACES] == [CHANNEL_IFACE_GROUP,
+ CHANNEL_IFACE_TUBE]
+ assert props[STREAM_TUBE_SERVICE] == 'test'
+ assert props[TUBE_PARAMETERS] == sample_parameters
+
+ contact2_tube = bus.get_object(conn.bus_name, path)
+ contact2_stream_tube = make_channel_proxy(conn, path,
+ "Channel.Type.StreamTube")
+ contact2_tube_channel = make_channel_proxy(conn, path, "Channel")
+ tube2_path = path
+ else:
+ assert False
+
+ assert props[INITIATOR_HANDLE] == contact1_handle_on_conn2
+ assert props[INITIATOR_ID] == contact1_name
+ assert props[TARGET_ID] == muc2_name
+
+ assert (path, props) in all_channels, (path, props)
+
+ assert got_tubes
+ assert got_tube
+
+ state = contact2_tube.Get(CHANNEL_IFACE_TUBE, 'State',
+ dbus_interface=PROPERTIES_IFACE)
+ assert state == TUBE_CHANNEL_STATE_LOCAL_PENDING
+
+ # second connection: accept the tube (new API)
+ unix_socket_adr = contact2_stream_tube.Accept(
+ SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, '',
+ byte_arrays=True)
+
+ state = contact2_tube.Get(CHANNEL_IFACE_TUBE, 'State',
+ dbus_interface=PROPERTIES_IFACE)
+ assert state == TUBE_CHANNEL_STATE_OPEN
+
+ e = q.expect('dbus-signal', signal='TubeChannelStateChanged',
+ path=tube2_path, args=[TUBE_CHANNEL_STATE_OPEN])
+
+ client = ClientCreator(reactor, ClientGreeter)
+ client.connectUNIX(unix_socket_adr).addCallback(client_connected_cb)
+
+ # server got the connection
+ _, client_connected, remote_sig, local_sig, data_received = q.expect_many(
+ EventPattern('server-connected'),
+ EventPattern('client-connected'),
+ EventPattern('dbus-signal', signal='NewRemoteConnection',
+ path=tube1_path),
+ EventPattern('dbus-signal', signal='NewLocalConnection',
+ path=tube2_path),
+ EventPattern('client-data-received'))
+
+ client_transport = client_connected.transport
+
+ handle, conn_param, contact1_tube_conn_id = remote_sig.args
+ assert handle == contact2_handle_on_conn1
+ assert contact1_tube_conn_id != 0
+
+ contact2_tube_conn_id = local_sig.args[0]
+ assert contact2_tube_conn_id != 0
+
+ # client receives server's welcome message
+ assert data_received.data == SERVER_WELCOME_MSG
+
+ client_transport.write(test_string)
+
+ server_received, client_received = q.expect_many(
+ EventPattern('server-data-received'),
+ EventPattern('client-data-received'))
+
+ assert server_received.data == test_string
+ assert client_received.data == string.swapcase(test_string)
+
+ call_async(q, contact1_tube_channel, 'Close')
+ _, e1, e2, _, _, _ = q.expect_many(
+ EventPattern('dbus-return', method='Close'),
+ EventPattern('dbus-signal', signal='ConnectionClosed', path=tube1_path),
+ EventPattern('dbus-signal', signal='ConnectionClosed', path=tube2_path),
+ EventPattern('dbus-signal', signal='Closed'),
+ EventPattern('dbus-signal', signal='TubeClosed'),
+ EventPattern('dbus-signal', signal='ChannelClosed'))
+
+ conn_id, error, dbus_msg = e1.args
+ assert conn_id == contact1_tube_conn_id
+ assert error == CANCELLED
+
+ conn_id, error, dbus_msg = e2.args
+ assert conn_id == contact2_tube_conn_id
+ assert error == CONNECTION_LOST
+
+ call_async(q, contact2_tube_channel, 'Close')
+ q.expect_many(
+ EventPattern('dbus-return', method='Close'),
+ EventPattern('dbus-signal', signal='Closed'),
+ EventPattern('dbus-signal', signal='TubeClosed'),
+ EventPattern('dbus-signal', signal='ChannelClosed'))
+
+ conn.Disconnect()
+ conn2.Disconnect()
+
+if __name__ == '__main__':
+ # increase timer because Clique takes some time to join an existing muc
+ exec_test(test, timeout=60)
diff --git a/salut/tests/twisted/avahi/tubes/two-private-stream-tubes.py b/salut/tests/twisted/avahi/tubes/two-private-stream-tubes.py
new file mode 100644
index 000000000..e1956be95
--- /dev/null
+++ b/salut/tests/twisted/avahi/tubes/two-private-stream-tubes.py
@@ -0,0 +1,329 @@
+from saluttest import exec_test
+import dbus
+import os
+import errno
+import string
+
+from servicetest import make_channel_proxy, Event, EventPattern, call_async
+
+from twisted.internet.protocol import Factory, Protocol, ClientCreator
+from twisted.internet import reactor
+from constants import *
+import tubetestutil as t
+
+sample_parameters = dbus.Dictionary({
+ 's': 'hello',
+ 'ay': dbus.ByteArray('hello'),
+ 'u': dbus.UInt32(123),
+ 'i': dbus.Int32(-123),
+ }, signature='sv')
+
+test_string = "This string travels on a tube !"
+
+SERVER_WELCOME_MSG = "Welcome!"
+
+print "FIXME: disabled because 1-1 tubes are disabled for now"
+# exiting 77 causes automake to consider the test to have been skipped
+raise SystemExit(77)
+
+def test(q, bus, conn):
+
+ # define a basic tcp server that echoes what the client says, but with
+ # swapcase
+ class TrivialServer(Protocol):
+ def dataReceived(self, data):
+ self.transport.write(string.swapcase(data))
+ e = Event('server-data-received', service = self, data = data)
+ q.append(e)
+
+ def connectionMade(self):
+ e = Event('server-connected', transport = self.transport)
+ q.append(e)
+
+ # send welcome message to the client
+ self.transport.write(SERVER_WELCOME_MSG)
+
+ # define a basic tcp client
+ class ClientGreeter(Protocol):
+ def dataReceived(self, data):
+ e = Event('client-data-received', service = self, data = data)
+ q.append(e)
+
+ def client_connected_cb(p):
+ e = Event('client-connected', transport = p.transport)
+ q.append(e)
+
+ # create the server
+ factory = Factory()
+ factory.protocol = TrivialServer
+ server_socket_address = os.getcwd() + '/stream'
+ try:
+ os.remove(server_socket_address)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ l = reactor.listenUNIX(server_socket_address, factory)
+
+ contact1_name, conn2, contact2_name, contact2_handle_on_conn1,\
+ contact1_handle_on_conn2 = t.connect_two_accounts(q, bus, conn)
+
+ conn1_self_handle = conn.GetSelfHandle()
+
+ # contact1 offers stream tube to contact2 (old API)
+ contact1_tubes_channel_path = conn.RequestChannel(CHANNEL_TYPE_TUBES, HT_CONTACT,
+ contact2_handle_on_conn1, True)
+ contact1_tubes_channel = make_channel_proxy(conn, contact1_tubes_channel_path, "Channel.Type.Tubes")
+ contact1_tubes_channel_iface = make_channel_proxy(conn, contact1_tubes_channel_path, "Channel")
+
+ tube_id = contact1_tubes_channel.OfferStreamTube("http", sample_parameters,
+ SOCKET_ADDRESS_TYPE_UNIX, dbus.ByteArray(server_socket_address),
+ SOCKET_ACCESS_CONTROL_LOCALHOST, "")
+
+ contact2_tubes_channel_path = None
+ while contact2_tubes_channel_path is None:
+ e = q.expect('dbus-signal', signal='NewChannel')
+ if (e.args[1] == CHANNEL_TYPE_TUBES) and (e.path.endswith("testsuite2") == True):
+ contact2_tubes_channel_path = e.args[0]
+
+ contact2_tubes_channel = make_channel_proxy(conn2, contact2_tubes_channel_path, "Channel.Type.Tubes")
+ contact2_tubes_channel_iface = make_channel_proxy(conn, contact2_tubes_channel_path, "Channel")
+
+ contact2_tubes = contact2_tubes_channel.ListTubes()
+ assert len(contact2_tubes) == 1
+ contact2_tube = contact2_tubes[0]
+ assert contact2_tube[0] is not None # tube id
+ assert contact2_tube[1] is not None # initiator
+ assert contact2_tube[2] == 1 # type = stream tube
+ assert contact2_tube[3] == 'http' # service = http
+ assert contact2_tube[4] is not None # parameters
+ assert contact2_tube[5] == 0, contact2_tube[5] # status = local pending
+
+ unix_socket_adr = contact2_tubes_channel.AcceptStreamTube(
+ contact2_tube[0], 0, 0, '', byte_arrays=True)
+
+ client = ClientCreator(reactor, ClientGreeter)
+ client.connectUNIX(unix_socket_adr).addCallback(client_connected_cb)
+
+ # server got the connection
+ _, e, new_conn_event, data_event = q.expect_many(
+ EventPattern('server-connected'),
+ EventPattern('client-connected'),
+ EventPattern('dbus-signal', signal='StreamTubeNewConnection', path=contact1_tubes_channel_path),
+ EventPattern('client-data-received'))
+
+ client_transport = e.transport
+
+ id, handle = new_conn_event.args
+ assert id == tube_id
+ assert handle == contact2_handle_on_conn1
+
+ # client receives server's welcome message
+ assert data_event.data == SERVER_WELCOME_MSG
+
+ client_transport.write(test_string)
+
+ server_received, client_received = q.expect_many(
+ EventPattern('server-data-received'),
+ EventPattern('client-data-received'))
+
+ assert server_received.data == test_string
+ assert client_received.data == string.swapcase(test_string)
+
+ # Close the tube propertly
+ call_async(q, contact1_tubes_channel, 'CloseTube', tube_id)
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='TubeClosed', path=contact1_tubes_channel_path),
+ EventPattern('dbus-signal', signal='TubeClosed', path=contact2_tubes_channel_path),
+ EventPattern('dbus-return', method='CloseTube'))
+
+ # close both tubes channels
+ contact1_tubes_channel_iface.Close()
+ contact2_tubes_channel_iface.Close()
+
+ # now contact1 will offer another stream tube to contact2 using the new
+ # API
+
+ # Can we request private stream tubes?
+ properties = conn.GetAll(CONN_IFACE_REQUESTS, dbus_interface=PROPERTIES_IFACE)
+
+ assert ({CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE,
+ TARGET_HANDLE_TYPE: HT_CONTACT},
+ [TARGET_HANDLE, TARGET_ID, STREAM_TUBE_SERVICE]
+ ) in properties.get('RequestableChannelClasses'),\
+ properties['RequestableChannelClasses']
+
+ requestotron = dbus.Interface(conn, CONN_IFACE_REQUESTS)
+
+ requestotron.CreateChannel({
+ CHANNEL_TYPE: CHANNEL_TYPE_STREAM_TUBE,
+ TARGET_HANDLE_TYPE: HT_CONTACT,
+ TARGET_ID: contact2_name,
+ STREAM_TUBE_SERVICE: 'test'})
+
+ # tubes and tube channels are created on the first connection
+ e = q.expect('dbus-signal', signal='NewChannels', path=conn.object.object_path)
+ channels = e.args[0]
+ assert len(channels) == 2
+
+ # get the list of all channels to check that newly announced ones are in it
+ all_channels = conn.Get(CONN_IFACE_REQUESTS, 'Channels', dbus_interface=PROPERTIES_IFACE,
+ byte_arrays=True)
+
+ got_tubes, got_tube = False, False
+ for path, props in channels:
+ if props[CHANNEL_TYPE] == CHANNEL_TYPE_TUBES:
+ got_tubes = True
+ assert props[REQUESTED] == False
+ assert props[INTERFACES] == []
+ elif props[CHANNEL_TYPE] == CHANNEL_TYPE_STREAM_TUBE:
+ got_tube = True
+ assert props[REQUESTED] == True
+ assert props[INTERFACES] == [CHANNEL_IFACE_TUBE]
+ assert props[STREAM_TUBE_SERVICE] == 'test'
+
+ contact1_tube = bus.get_object(conn.bus_name, path)
+ contact1_stream_tube = make_channel_proxy(conn, path, "Channel.Type.StreamTube")
+ contact1_tube_channel = make_channel_proxy(conn, path, "Channel")
+ tube1_path = path
+ else:
+ assert False
+
+ assert props[INITIATOR_HANDLE] == conn1_self_handle
+ assert props[INITIATOR_ID] == contact1_name
+ assert props[TARGET_ID] == contact2_name
+
+ assert (path, props) in all_channels, (path, props)
+
+ assert got_tubes
+ assert got_tube
+
+ state = contact1_stream_tube.Get(CHANNEL_IFACE_TUBE, 'State', dbus_interface=PROPERTIES_IFACE)
+ assert state == TUBE_CHANNEL_STATE_NOT_OFFERED
+
+ call_async(q, contact1_stream_tube, 'Offer', SOCKET_ADDRESS_TYPE_UNIX,
+ dbus.ByteArray(server_socket_address), SOCKET_ACCESS_CONTROL_LOCALHOST, sample_parameters)
+
+ _, return_event, new_chans = q.expect_many(
+ EventPattern('dbus-signal', signal='TubeChannelStateChanged',
+ args=[TUBE_CHANNEL_STATE_REMOTE_PENDING]),
+ EventPattern('dbus-return', method='Offer'),
+ EventPattern('dbus-signal', signal='NewChannels', path=conn2.object.object_path))
+
+ state = contact1_stream_tube.Get(CHANNEL_IFACE_TUBE, 'State', dbus_interface=PROPERTIES_IFACE)
+ assert state == TUBE_CHANNEL_STATE_REMOTE_PENDING
+
+ # tube and tubes channels have been created on conn2
+ channels = new_chans.args[0]
+ assert len(channels) == 2
+
+ # get the list of all channels to check that newly announced ones are in it
+ all_channels = conn2.Get(CONN_IFACE_REQUESTS, 'Channels', dbus_interface=PROPERTIES_IFACE,
+ byte_arrays=True)
+
+ got_tubes, got_tube = False, False
+ for path, props in channels:
+ if props[CHANNEL_TYPE] == CHANNEL_TYPE_TUBES:
+ got_tubes = True
+ assert props[REQUESTED] == False
+ assert props[INTERFACES] == []
+ elif props[CHANNEL_TYPE] == CHANNEL_TYPE_STREAM_TUBE:
+ got_tube = True
+ assert props[REQUESTED] == False
+ assert props[INTERFACES] == [CHANNEL_IFACE_TUBE]
+ assert props[STREAM_TUBE_SERVICE] == 'test'
+
+ contact2_tube = bus.get_object(conn.bus_name, path)
+ contact2_stream_tube = make_channel_proxy(conn, path, "Channel.Type.StreamTube")
+ contact2_tube_channel = make_channel_proxy(conn, path, "Channel")
+ tube2_path = path
+ else:
+ assert False
+
+ assert props[INITIATOR_HANDLE] == contact1_handle_on_conn2
+ assert props[INITIATOR_ID] == contact1_name
+ assert props[TARGET_ID] == contact1_name
+
+ assert (path, props) in all_channels, (path, props)
+
+ assert got_tubes
+ assert got_tube
+
+ state = contact2_tube.Get(CHANNEL_IFACE_TUBE, 'State', dbus_interface=PROPERTIES_IFACE)
+ assert state == TUBE_CHANNEL_STATE_LOCAL_PENDING
+
+ # second connection: accept the tube (new API)
+ unix_socket_adr = contact2_stream_tube.Accept(SOCKET_ADDRESS_TYPE_UNIX,
+ SOCKET_ACCESS_CONTROL_LOCALHOST, '', byte_arrays=True)
+
+ state = contact2_tube.Get(CHANNEL_IFACE_TUBE, 'State', dbus_interface=PROPERTIES_IFACE)
+ assert state == TUBE_CHANNEL_STATE_OPEN
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='TubeChannelStateChanged', path=tube2_path,
+ args=[TUBE_CHANNEL_STATE_OPEN]),
+ EventPattern('dbus-signal', signal='TubeChannelStateChanged', path=tube1_path,
+ args=[TUBE_CHANNEL_STATE_OPEN]))
+
+ client = ClientCreator(reactor, ClientGreeter)
+ client.connectUNIX(unix_socket_adr).addCallback(client_connected_cb)
+
+ # server got the connection
+ _, client_connected, remote_sig, local_sig, data_received = q.expect_many(
+ EventPattern('server-connected'),
+ EventPattern('client-connected'),
+ EventPattern('dbus-signal', signal='NewRemoteConnection',
+ path=tube1_path),
+ EventPattern('dbus-signal', signal='NewLocalConnection',
+ path=tube2_path),
+ EventPattern('client-data-received'))
+
+ client_transport = client_connected.transport
+
+ handle, conn_param, contact1_tube_conn_id = remote_sig.args
+ assert handle == contact2_handle_on_conn1
+ assert contact1_tube_conn_id != 0
+
+ contact2_tube_conn_id = local_sig.args[0]
+ assert contact2_tube_conn_id != 0
+
+ # client receives server's welcome message
+ assert data_received.data == SERVER_WELCOME_MSG
+
+ client_transport.write(test_string)
+
+ server_received, client_received = q.expect_many(
+ EventPattern('server-data-received'),
+ EventPattern('client-data-received'))
+
+ assert server_received.data == test_string
+ assert client_received.data == string.swapcase(test_string)
+
+ # contact1 close the tube
+ call_async(q, contact1_tube_channel, 'Close')
+
+ # tube is closed on both sides
+ _, e1, e2, _, _, _, _ = q.expect_many(
+ EventPattern('dbus-return', method='Close'),
+ EventPattern('dbus-signal', signal='ConnectionClosed', path=tube1_path),
+ EventPattern('dbus-signal', signal='ConnectionClosed', path=tube2_path),
+ EventPattern('dbus-signal', signal='Closed', path=tube1_path),
+ EventPattern('dbus-signal', signal='Closed', path=tube2_path),
+ EventPattern('dbus-signal', signal='ChannelClosed', path=conn.object.object_path),
+ EventPattern('dbus-signal', signal='ChannelClosed', path=conn2.object.object_path))
+
+ conn_id, error, dbus_msg = e1.args
+ assert conn_id == contact1_tube_conn_id
+ assert error == CANCELLED
+
+ conn_id, error, dbus_msg = e2.args
+ assert conn_id == contact2_tube_conn_id
+ assert error == CANCELLED
+
+ conn.Disconnect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+ conn2.Disconnect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/avahimock.py b/salut/tests/twisted/avahimock.py
new file mode 100755
index 000000000..8bd6e2ccc
--- /dev/null
+++ b/salut/tests/twisted/avahimock.py
@@ -0,0 +1,430 @@
+#!/usr/bin/python
+
+import socket
+
+import dbus
+import dbus.service
+from dbus.lowlevel import SignalMessage
+import gobject
+import glib
+
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+
+from avahitest import check_ipv6_enabled
+
+AVAHI_NAME = 'org.freedesktop.Avahi'
+AVAHI_IFACE_SERVER = 'org.freedesktop.Avahi.Server'
+AVAHI_IFACE_ENTRY_GROUP = 'org.freedesktop.Avahi.EntryGroup'
+AVAHI_IFACE_SERVICE_BROWSER = 'org.freedesktop.Avahi.ServiceBrowser'
+AVAHI_IFACE_SERVICE_RESOLVER = 'org.freedesktop.Avahi.ServiceResolver'
+
+AVAHI_DNS_CLASS_IN = 1
+AVAHI_DNS_TYPE_A = 1
+
+AVAHI_PROTO_INET = 0
+AVAHI_PROTO_INET6 = 1
+AVAHI_PROTO_UNSPEC = -1
+
+AVAHI_SERVER_INVALID = 0
+AVAHI_SERVER_REGISTERING = 1
+AVAHI_SERVER_RUNNING = 2
+AVAHI_SERVER_COLLISION = 3
+AVAHI_SERVER_FAILURE = 4
+
+DOMAIN = 'local'
+
+def emit_signal(object_path, interface, name, destination, signature, *args):
+ message = SignalMessage(object_path, interface, name)
+ message.append(*args, signature=signature)
+
+ if destination is not None:
+ message.set_destination(destination)
+
+ dbus.SystemBus().send_message(message)
+
+class Model(object):
+ def __init__(self):
+ self._service_browsers = []
+ self._service_browser_index = 1
+ self._service_resolvers = []
+ self._service_resolver_index = 1
+ self._entries = []
+ self._address_records = {}
+
+ def new_service_browser(self, type_, client):
+ service_browser = ServiceBrowser(client, self._service_browser_index, type_)
+ self._service_browser_index += 1
+ self._service_browsers.append(service_browser)
+
+ glib.idle_add(self.__browse_idle_cb, service_browser)
+
+ return service_browser.object_path
+
+ def __browse_idle_cb(self, service_browser):
+ for entry in self._entries:
+ if entry.type == service_browser.type:
+ self._emit_new_item(service_browser, entry)
+
+ def _find_entry(self, type_, name):
+ for entry in self._entries:
+ if entry.type == type_ and entry.name == name:
+ return entry
+ return None
+
+ def new_service_resolver(self, type_, name, protocol, client):
+ entry = self._find_entry(type_, name)
+ service_resolver = ServiceResolver(self._service_resolver_index,
+ client, type_, name, protocol)
+ self._service_resolver_index += 1
+ self._service_resolvers.append(service_resolver)
+
+ glib.idle_add(self.__entry_found_idle_cb, service_resolver, entry)
+
+ return service_resolver.object_path
+
+ def __entry_found_idle_cb(self, service_resolver, entry):
+ if entry is None:
+ emit_signal(service_resolver.object_path,
+ AVAHI_IFACE_SERVICE_RESOLVER, 'Failure',
+ service_resolver.client, 's',
+ 'no entry could be found')
+ else:
+ self._emit_found(service_resolver, entry)
+
+ def _resolve_hostname(self, protocol, hostname):
+ if hostname in self._address_records:
+ return self._address_records[hostname]
+ else:
+ if protocol == AVAHI_PROTO_INET6:
+ return '::1'
+ else:
+ return '127.0.0.1'
+
+ def update_entry(self, interface, protocol, flags, name, type_, domain,
+ host, port, txt):
+ entry = self._find_entry(type_, name)
+
+ if interface == -1:
+ interface = 0
+
+ if host is None:
+ host = entry.host
+
+ if port is None:
+ port = entry.port
+
+ if entry is None:
+ entry = Entry(interface, protocol, flags, name, type_, domain,
+ host, port, txt)
+ self._entries.append(entry)
+ else:
+ entry.update(interface, protocol, flags, domain, host, port, txt)
+
+ for service_browser in self._service_browsers:
+ if service_browser.type == type_:
+ self._emit_new_item(service_browser, entry)
+
+ for service_resolver in self._service_resolvers:
+ if service_resolver.type == type_ and \
+ service_resolver.name == name:
+ self._emit_found(service_resolver, entry)
+
+ def add_record(self, interface, protocol, flags, name, clazz, type_, ttl,
+ rdata):
+ if clazz == AVAHI_DNS_CLASS_IN and type_ == AVAHI_DNS_TYPE_A:
+ self._address_records[name] = socket.inet_ntoa(rdata)
+
+ def remove_entry(self, type_, name):
+ entry = self._find_entry(type_, name)
+ if entry is None:
+ # Entry may have been created by more than one EntryGroup
+ return
+
+ for service_browser in self._service_browsers:
+ if service_browser.type == type_:
+ self._emit_item_remove(service_browser, entry)
+
+ self._entries.remove(entry)
+
+ def _emit_new_item(self, service_browser, entry):
+ if entry.protocol == AVAHI_PROTO_UNSPEC:
+ protocols = (AVAHI_PROTO_INET, AVAHI_PROTO_INET6)
+ else:
+ protocols = (entry.protocol,)
+
+ for protocol in protocols:
+ emit_signal(service_browser.object_path,
+ AVAHI_IFACE_SERVICE_BROWSER, 'ItemNew',
+ service_browser.client, 'iisssu',
+ entry.interface, protocol, entry.name, entry.type,
+ entry.domain, entry.flags)
+
+ def _emit_item_remove(self, service_browser, entry):
+ if entry.protocol == AVAHI_PROTO_UNSPEC:
+ protocols = (AVAHI_PROTO_INET, AVAHI_PROTO_INET6)
+ else:
+ protocols = (entry.protocol,)
+
+ for protocol in protocols:
+ emit_signal(service_browser.object_path,
+ AVAHI_IFACE_SERVICE_BROWSER, 'ItemRemove',
+ service_browser.client, 'iisssu',
+ entry.interface, protocol, entry.name, entry.type,
+ entry.domain, entry.flags)
+
+ def _emit_found(self, service_resolver, entry):
+ protocols = []
+ if service_resolver.protocol in [AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET]:
+ if entry.protocol in [AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET]:
+ protocols.append(AVAHI_PROTO_INET)
+
+ if check_ipv6_enabled() and \
+ service_resolver.protocol in [AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET6]:
+ if entry.protocol in [AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET6]:
+ protocols.append(AVAHI_PROTO_INET6)
+
+ for protocol in protocols:
+ address = self._resolve_hostname(protocol, entry.host)
+ emit_signal(service_resolver.object_path,
+ AVAHI_IFACE_SERVICE_RESOLVER, 'Found',
+ service_resolver.client, 'iissssisqaayu',
+ entry.interface, protocol, entry.name, entry.type,
+ entry.domain, entry.host, entry.aprotocol,
+ address, entry.port, entry.txt, entry.flags)
+
+ def remove_client(self, client):
+ for service_browser in self._service_browsers[:]:
+ if service_browser.client == client:
+ service_browser.Free()
+ service_browser.remove_from_connection()
+ self._service_browsers.remove(service_browser)
+
+ for service_resolver in self._service_resolvers[:]:
+ if service_resolver.client == client:
+ service_resolver.Free()
+ service_resolver.remove_from_connection()
+ self._service_resolvers.remove(service_resolver)
+
+
+class Entry(object):
+ def __init__(self, interface, protocol, flags, name, type_, domain, host,
+ port, txt):
+ self.name = name
+ self.type = type_
+
+ self.interface = None
+ self.protocol = None
+ self.aprotocol = None
+ self.flags = None
+ self.domain = None
+ self.host = None
+ self.port = None
+ self.txt = None
+
+ self.update(interface, protocol, flags, domain, host, port, txt)
+
+ def update(self, interface, protocol, flags, domain, host, port, txt):
+ self.interface = interface
+ self.protocol = protocol
+ self.aprotocol = protocol
+ self.flags = flags
+ self.domain = domain
+ self.host = host
+ self.port = port
+ self.txt = txt
+
+class Avahi(dbus.service.Object):
+ def __init__(self):
+ bus = dbus.SystemBus()
+ name = dbus.service.BusName(AVAHI_NAME, bus)
+ dbus.service.Object.__init__(self, conn=bus, object_path='/',
+ bus_name=name)
+
+ bus.add_signal_receiver(self.__name_owner_changed_cb,
+ signal_name='NameOwnerChanged',
+ dbus_interface='org.freedesktop.DBus')
+
+ self._entry_groups = []
+ self._entry_group_index = 1
+ self._model = Model()
+
+ def __name_owner_changed_cb(self, name, old_owner, new_owner):
+ if new_owner == '':
+ for entry_group in self._entry_groups[:]:
+ if entry_group.client == name:
+ entry_group.Free()
+ entry_group.remove_from_connection()
+ self._entry_groups.remove(entry_group)
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
+ in_signature='', out_signature='u')
+ def GetAPIVersion(self):
+ return 515
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
+ in_signature='', out_signature='s')
+ def GetHostName(self):
+ return 'testsuite'
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
+ in_signature='', out_signature='s')
+ def GetHostNameFqdn(self):
+ return self.GetHostName() + '.' + self.GetDomainName()
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
+ in_signature='', out_signature='s')
+ def GetDomainName(self):
+ return DOMAIN
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
+ in_signature='', out_signature='i')
+ def GetState(self):
+ return AVAHI_SERVER_RUNNING
+
+ @dbus.service.signal(dbus_interface=AVAHI_IFACE_SERVER, signature='is')
+ def StateChanged(self, state, error):
+ pass
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
+ in_signature='', out_signature='o',
+ sender_keyword='sender')
+ def EntryGroupNew(self, sender):
+ entry_group = EntryGroup(sender, self._entry_group_index, self._model)
+ self._entry_group_index += 1
+ self._entry_groups.append(entry_group)
+ return entry_group.object_path
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
+ in_signature='iissu', out_signature='o',
+ sender_keyword='sender')
+ def ServiceBrowserNew(self, interface, protocol, type_, domain, flags, sender):
+ return self._model.new_service_browser(type_, sender)
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVER,
+ in_signature='iisssiu', out_signature='o',
+ sender_keyword='sender')
+ def ServiceResolverNew(self, interface, protocol, name, type_, domain, aprotocol, flags, sender):
+ return self._model.new_service_resolver(type_, name, protocol, sender)
+
+
+class EntryGroup(dbus.service.Object):
+ def __init__(self, client, index, model):
+ bus = dbus.SystemBus()
+ self.object_path = '/Client%u/EntryGroup%u' % (1, index)
+ dbus.service.Object.__init__(self, conn=bus,
+ object_path=self.object_path)
+
+ self._state = 0
+ self.client = client
+ self._model = model
+
+ self._entries = []
+
+ def get_service(self, name):
+ return self._services.get(name, None)
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
+ in_signature='iiussssqaay', out_signature='',
+ byte_arrays=True)
+ def AddService(self, interface, protocol, flags, name, type_, domain, host,
+ port, txt):
+ if not host:
+ host = socket.gethostname()
+
+ if not domain:
+ domain = DOMAIN
+
+ self._model.update_entry(interface, protocol, flags, name, type_, domain,
+ host, port, txt)
+ self._entries.append((type_, name))
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
+ in_signature='iiusssaay', out_signature='',
+ byte_arrays=True)
+ def UpdateServiceTxt(self, interface, protocol, flags, name, type_, domain, txt):
+ self._model.update_entry(interface, protocol, flags, name, type_, domain,
+ None, None, txt)
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
+ in_signature='', out_signature='')
+ def Commit(self):
+ self._set_state(AVAHI_SERVER_REGISTERING)
+ glib.idle_add(lambda: self._set_state(AVAHI_SERVER_RUNNING))
+
+ def _set_state(self, new_state):
+ self._state = new_state
+
+ message = SignalMessage(self.object_path,
+ AVAHI_IFACE_ENTRY_GROUP,
+ 'StateChanged')
+ message.append(self._state, 'org.freedesktop.Avahi.Success',
+ signature='is')
+ message.set_destination(self.client)
+
+ dbus.SystemBus().send_message(message)
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
+ in_signature='', out_signature='i')
+ def GetState(self):
+ return self._state
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
+ in_signature='iiusqquay', out_signature='',
+ byte_arrays=True)
+ def AddRecord(self, interface, protocol, flags, name, clazz, type_, ttl,
+ rdata):
+ self._model.add_record(interface, protocol, flags, name, clazz, type_,
+ ttl, rdata)
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_ENTRY_GROUP,
+ in_signature='', out_signature='')
+ def Free(self):
+ for type_, name in self._entries[:]:
+ self._model.remove_entry(type_, name)
+ self._entries.remove((type_, name))
+
+
+class ServiceBrowser(dbus.service.Object):
+ def __init__(self, client, index, type_):
+ bus = dbus.SystemBus()
+ self.object_path = '/Client%u/ServiceBrowser%u' % (1, index)
+ dbus.service.Object.__init__(self, conn=bus,
+ object_path=self.object_path)
+
+ self.client = client
+ self.type = type_
+
+ def send_all_for_now():
+ emit_signal(self.object_path,
+ AVAHI_IFACE_SERVICE_BROWSER, 'AllForNow',
+ client, '')
+ gobject.idle_add(send_all_for_now)
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVICE_BROWSER,
+ in_signature='', out_signature='')
+ def Free(self):
+ pass
+
+
+class ServiceResolver(dbus.service.Object):
+ def __init__(self, index, client, type_, name, protocol):
+ bus = dbus.SystemBus()
+ self.object_path = '/Client%u/ServiceResolver%u' % (1, index)
+ dbus.service.Object.__init__(self, conn=bus,
+ object_path=self.object_path)
+ self.client = client
+ self.type = type_
+ self.name = name
+ self.protocol = protocol
+
+ @dbus.service.method(dbus_interface=AVAHI_IFACE_SERVICE_RESOLVER,
+ in_signature='', out_signature='')
+ def Free(self):
+ pass
+
+
+avahi = Avahi()
+
+loop = gobject.MainLoop()
+loop.run()
diff --git a/salut/tests/twisted/avahitest.py b/salut/tests/twisted/avahitest.py
new file mode 100644
index 000000000..eb68504cb
--- /dev/null
+++ b/salut/tests/twisted/avahitest.py
@@ -0,0 +1,269 @@
+"""
+Infrastructure for testing avahi
+"""
+
+import servicetest
+from servicetest import Event, TimeoutError
+import dbus
+import dbus.glib
+import avahi
+import os
+import socket
+
+def get_server():
+ bus = dbus.SystemBus()
+ server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
+ avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+ return server
+
+def get_host_name():
+ return get_server().GetHostName()
+
+def get_host_name_fqdn():
+ return get_server().GetHostNameFqdn()
+
+def get_domain_name():
+ return get_server().GetDomainName()
+
+def txt_get_key(txt, key):
+ for x in txt:
+ if dbus.Byte('=') in x:
+ (rkey, value) = avahi.byte_array_to_string(x).split('=', 1)
+ if rkey == key:
+ return value
+
+ return None
+
+class AvahiService:
+ def __init__(self, event_queue, bus, server, interface, protocol, name,
+ type, domain, aprotocol, flags):
+
+ self.event_queue = event_queue
+ self.server = server
+ self.bus = bus
+ self.interface = interface
+ self.protocol = protocol
+ self.name = name
+ self.type = type
+ self.domain = domain
+ self.aprotocol = aprotocol
+ self.flags = flags
+
+ def resolve(self):
+ resolver_path = self.server.ServiceResolverNew(self.interface,
+ self.protocol, self.name, self.type, self.domain,
+ self.aprotocol, self.flags)
+
+ resolver_obj = self.bus.get_object(avahi.DBUS_NAME, resolver_path)
+ resolver = dbus.Interface(resolver_obj,
+ avahi.DBUS_INTERFACE_SERVICE_RESOLVER)
+
+ resolver.connect_to_signal('Found', self._service_found)
+ resolver.connect_to_signal('Failed', self._service_failed)
+
+ def _service_found(self, interface, protocol, name, stype, domain,
+ host_name, aprotocol, pt, port, txt, flags):
+
+ e = Event('service-resolved', service = self,
+ interface = interface, protocol = protocol, name = name,
+ stype = stype, host_name = host_name, aprotocol = aprotocol,
+ pt = pt, port = port, txt = txt, flags = flags)
+
+ self.event_queue.append(e)
+
+ def _service_failed(self, error):
+ e = Event('service-failed', service = self, error = error)
+ self.event_queue.append(e)
+
+class AvahiListener:
+ def __init__(self, event_queue):
+ self.event_queue = event_queue
+
+ self.bus = dbus.SystemBus()
+ self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME,
+ avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+ self.browsers = []
+
+ def _service_added_cb(self, interface, protocol, name, stype, domain,
+ flags):
+
+ service = AvahiService(self.event_queue, self.bus, self.server,
+ interface, protocol, name, stype,
+ domain, protocol, 0)
+
+ e = Event ('service-added', service = service,
+ interface=interface, protocol=protocol, name=name, stype=stype,
+ domain=domain, flags=flags)
+
+ self.event_queue.append(e)
+
+ def _service_removed_cb(self, interface, protocol, name, stype, domain,
+ flags):
+
+ e = Event ('service-removed',
+ interface=interface, protocol=protocol, name=name, stype=stype,
+ domain=domain, flags=flags)
+ self.event_queue.append(e)
+
+ def _all_for_now_cb(self):
+ self.event_queue.append(Event('service-all-for-now'))
+
+ def listen_for_service(self, sname):
+ browser_path = self.server.ServiceBrowserNew(avahi.IF_UNSPEC,
+ avahi.PROTO_UNSPEC, sname, "local", dbus.UInt32(0));
+ browser_obj = self.bus.get_object(avahi.DBUS_NAME, browser_path)
+ browser = dbus.Interface(browser_obj,
+ avahi.DBUS_INTERFACE_SERVICE_BROWSER)
+
+ browser.connect_to_signal('ItemNew', self._service_added_cb)
+ browser.connect_to_signal('ItemRemove', self._service_removed_cb)
+ browser.connect_to_signal('AllForNow', self._all_for_now_cb)
+
+ self.browsers.append(browser)
+ return self
+
+class AvahiRecordAnnouncer:
+ def __init__(self, name, clazz, type, data):
+ self.name = name
+ self.clazz = clazz
+ self.type = type
+ self.data = data
+
+ self.bus = dbus.SystemBus()
+ self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME,
+ avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+
+ entry_path = self.server.EntryGroupNew()
+ entry_obj = self.bus.get_object(avahi.DBUS_NAME, entry_path)
+ entry = dbus.Interface(entry_obj,
+ avahi.DBUS_INTERFACE_ENTRY_GROUP)
+
+ entry.AddRecord(avahi.IF_UNSPEC, avahi.PROTO_INET,
+ dbus.UInt32(0), name, clazz, type, 120, data)
+
+ entry.Commit()
+
+ self.entry = entry
+
+class AvahiAnnouncer:
+ def __init__(self, name, type, port, txt, hostname=None,
+ proto=avahi.PROTO_INET):
+ self.name = name
+ self.type = type
+ self.port = port
+ self.txt = txt
+ self.proto = proto
+
+ self.bus = dbus.SystemBus()
+ self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME,
+ avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+
+ entry_path = self.server.EntryGroupNew()
+ entry_obj = self.bus.get_object(avahi.DBUS_NAME, entry_path)
+ entry = dbus.Interface(entry_obj,
+ avahi.DBUS_INTERFACE_ENTRY_GROUP)
+
+ if hostname is None:
+ hostname = get_host_name_fqdn()
+
+ entry.AddService(avahi.IF_UNSPEC, self.proto,
+ dbus.UInt32(0), name, type, get_domain_name(), hostname,
+ port, avahi.dict_to_txt_array(txt))
+ entry.Commit()
+
+ self.entry = entry
+
+ def update(self, txt):
+ self.txt.update(txt)
+
+ self.entry.UpdateServiceTxt(avahi.IF_UNSPEC, self.proto,
+ dbus.UInt32(0), self.name, self.type, get_domain_name(),
+ avahi.dict_to_txt_array(self.txt))
+
+ def set(self, txt):
+ self.txt = txt
+ self.entry.UpdateServiceTxt(avahi.IF_UNSPEC, self.proto,
+ dbus.UInt32(0), self.name, self.type, get_domain_name(),
+ avahi.dict_to_txt_array(self.txt))
+
+ def stop(self):
+ self.entry.Free()
+
+def check_ipv6_enabled():
+ s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+
+ try:
+ s.bind(('::1', 0))
+ except socket.error:
+ return False
+
+ return True
+
+def has_another_llxmpp():
+ if 'SALUT_TEST_REAL_AVAHI' not in os.environ:
+ return False
+
+ q = servicetest.IteratingEventQueue()
+
+ l = AvahiListener(q)
+ l.listen_for_service('_presence._tcp')
+
+ has = False
+
+ while True:
+ e = q.wait(['service'])
+
+ if (e.type == 'service-added' and
+ e.flags & (avahi.LOOKUP_RESULT_LOCAL|avahi.LOOKUP_RESULT_OUR_OWN)):
+ has = True
+ elif e.type == 'service-all-for-now':
+ break
+
+ return has
+
+def skip_if_another_llxmpp():
+ if has_another_llxmpp():
+ print ("SKIP: This test fails if there is another LL XMPP instance "
+ "running on the machine.")
+ # exiting 77 causes automake to consider the test to have been skipped
+ raise SystemExit(77)
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+
+ txtdict = { "test0": "0", "test1": "1" }
+
+ a = AvahiAnnouncer("test", "_test._tcp", 1234, txtdict)
+
+ q = servicetest.IteratingEventQueue()
+ # Set verboseness if needed for debugging
+ # q.verbose = True
+
+ l = AvahiListener(q)
+ l.listen_for_service("_test._tcp")
+
+ while True:
+ e = q.expect ('service-added', stype='_test._tcp')
+ # Only care about services we announced ourselves
+ if e.flags & (avahi.LOOKUP_RESULT_LOCAL|avahi.LOOKUP_RESULT_OUR_OWN):
+ break
+
+ assert "test" == e.name[0:len("test")]
+
+ s = e.service
+ s.resolve()
+
+ e = q.expect('service-resolved', service = s)
+ for (key, val ) in txtdict.iteritems():
+ v = txt_get_key(e.txt, key)
+ assert v == val, (key, val, v)
+
+ txtdict["test1"] = "2"
+ txtdict["test2"] = "2"
+
+ a.update(txtdict)
+
+ e = q.expect('service-resolved', service = s)
+ for (key, val ) in txtdict.iteritems():
+ v = txt_get_key(e.txt, key)
+ assert v == val, (key, val, v)
diff --git a/salut/tests/twisted/caps_helper.py b/salut/tests/twisted/caps_helper.py
new file mode 100644
index 000000000..4921e0f62
--- /dev/null
+++ b/salut/tests/twisted/caps_helper.py
@@ -0,0 +1,377 @@
+# vim: set fileencoding=utf-8 :
+import hashlib
+import base64
+import dbus
+
+from avahitest import txt_get_key
+from twisted.words.xish import domish, xpath
+from saluttest import make_result_iq, make_presence, elem_iq, elem
+from servicetest import (
+ EventPattern,
+ assertEquals, assertContains, assertDoesNotContain, assertLength,
+ )
+
+import config
+import ns
+import constants as cs
+
+# The caps we always have, regardless of any clients' caps
+FIXED_CAPS = [
+ ns.JINGLE,
+ ns.JINGLE_015,
+ ns.GOOGLE_FEAT_SESSION,
+ ns.JINGLE_TRANSPORT_RAWUDP,
+ ns.NICK,
+ ns.NICK + '+notify',
+ ns.CHAT_STATES,
+ ns.SI,
+ ns.IBB,
+ ns.BYTESTREAMS,
+ ]
+
+JINGLE_CAPS = [
+ # Additional Jingle transports
+ ns.JINGLE_TRANSPORT_ICEUDP,
+ ns.GOOGLE_P2P,
+ # Jingle content types
+ ns.GOOGLE_FEAT_VOICE,
+ ns.GOOGLE_FEAT_VIDEO,
+ ns.JINGLE_015_AUDIO,
+ ns.JINGLE_015_VIDEO,
+ ns.JINGLE_RTP,
+ ns.JINGLE_RTP_AUDIO,
+ ns.JINGLE_RTP_VIDEO,
+ ]
+
+VARIABLE_CAPS = (
+ JINGLE_CAPS +
+ [
+ ns.FILE_TRANSFER,
+
+ # FIXME: currently we always advertise these, but in future we should
+ # only advertise them if >= 1 client supports them:
+ # ns.TUBES,
+
+ # there is an unlimited set of these; only the ones actually relevant to
+ # the tests so far are shown here
+ ns.TUBES + '/stream#x-abiword',
+ ns.TUBES + '/stream#daap',
+ ns.TUBES + '/stream#http',
+ ns.TUBES + '/dbus#com.example.Go',
+ ns.TUBES + '/dbus#com.example.Xiangqi',
+ ])
+
+def check_caps(namespaces, desired):
+ """Assert that all the FIXED_CAPS are supported, and of the VARIABLE_CAPS,
+ every capability in desired is supported, and every other capability is
+ not.
+ """
+ for c in FIXED_CAPS:
+ assertContains(c, namespaces)
+
+ for c in VARIABLE_CAPS:
+ if c in desired:
+ assertContains(c, namespaces)
+ else:
+ assertDoesNotContain(c, namespaces)
+
+# taken from salut's old caps_helper.py
+def check_caps_txt(txt, ver):
+ for (key, val) in { "1st": "test",
+ "last": "suite",
+ "status": "avail",
+ "txtvers": "1" }.iteritems():
+ v = txt_get_key(txt, key)
+ assert v == val, (key, val, v)
+
+ assert txt_get_key(txt, "hash") == "sha-1"
+ assert txt_get_key(txt, "node") == ns.GABBLE_CAPS
+
+ v = txt_get_key(txt, "ver")
+ assert v == ver, (v, ver)
+
+text_fixed_properties = dbus.Dictionary({
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT
+ })
+text_allowed_properties = dbus.Array([cs.TARGET_HANDLE])
+
+stream_tube_fixed_properties = dbus.Dictionary({
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAM_TUBE
+ })
+stream_tube_allowed_properties = dbus.Array([cs.TARGET_HANDLE,
+ cs.TARGET_ID, cs.STREAM_TUBE_SERVICE])
+
+dbus_tube_fixed_properties = dbus.Dictionary({
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_DBUS_TUBE
+ })
+dbus_tube_allowed_properties = dbus.Array([cs.TARGET_HANDLE,
+ cs.TARGET_ID, cs.DBUS_TUBE_SERVICE_NAME])
+
+ft_fixed_properties = dbus.Dictionary({
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_FILE_TRANSFER,
+ })
+ft_allowed_properties = dbus.Array([
+ cs.CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType',
+ cs.TARGET_HANDLE,
+ cs.TARGET_ID,
+ cs.CHANNEL_TYPE_FILE_TRANSFER + '.ContentType',
+ cs.CHANNEL_TYPE_FILE_TRANSFER + '.Filename',
+ cs.CHANNEL_TYPE_FILE_TRANSFER + '.Size',
+ cs.CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash',
+ cs.CHANNEL_TYPE_FILE_TRANSFER + '.Description',
+ cs.CHANNEL_TYPE_FILE_TRANSFER + '.Date',
+ cs.CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset',
+ cs.FT_URI])
+ft_allowed_properties_with_metadata = ft_allowed_properties + [
+ cs.FT_SERVICE_NAME,
+ cs.FT_METADATA]
+
+fake_client_dataforms = {
+ 'urn:xmpp:dataforms:softwareinfo':
+ {'software': ['A Fake Client with Twisted'],
+ 'software_version': ['5.11.2-svn-20080512'],
+ 'os': ['Debian GNU/Linux unstable (sid) unstable sid'],
+ 'os_version': ['2.6.24-1-amd64'],
+ },
+}
+
+def compute_caps_hash(identities, features, dataforms):
+ """
+ Accepts a list of slash-separated identities, a list of feature namespaces,
+ and a map from FORM_TYPE to (map from field name to values), returns the
+ verification string as defined by
+ <http://xmpp.org/extensions/xep-0115.html#ver>.
+ """
+ components = []
+
+ for identity in sorted(identities):
+ if len(identity.split('/')) != 4:
+ raise ValueError(
+ "expecting identities of the form " +
+ "'category/type/lang/client': got " + repr(identity))
+
+ components.append(identity)
+
+ for feature in sorted(features):
+ components.append(feature)
+
+ for form_type in sorted(dataforms.keys()):
+ components.append(form_type)
+
+ for var in sorted(dataforms[form_type].keys()):
+ components.append(var)
+
+ for value in sorted(dataforms[form_type][var]):
+ components.append(value)
+
+ components.append('')
+
+ m = hashlib.sha1()
+ S = u'<'.join(components)
+ m.update(S.encode('utf-8'))
+ return base64.b64encode(m.digest())
+
+def make_caps_disco_reply(stream, req, identities, features, dataforms={}):
+ iq = make_result_iq(req)
+ query = iq.firstChildElement()
+
+ for identity in identities:
+ category, type_, lang, name = identity.split('/')
+ el = query.addElement('identity')
+ el['category'] = category
+ el['type'] = type_
+ el['name'] = name
+
+ for f in features:
+ el = domish.Element((None, 'feature'))
+ el['var'] = f
+ query.addChild(el)
+
+ add_dataforms(query, dataforms)
+
+ return iq
+
+def add_dataforms(query, dataforms):
+ for type, fields in dataforms.iteritems():
+ x = query.addElement((ns.X_DATA, 'x'))
+ x['type'] = 'result'
+
+ field = x.addElement('field')
+ field['var'] = 'FORM_TYPE'
+ field['type'] = 'hidden'
+ field.addElement('value', content=type)
+
+ for var, values in fields.iteritems():
+ field = x.addElement('field')
+ field['var'] = var
+
+ for value in values:
+ field.addElement('value', content=value)
+
+def receive_presence_and_ask_caps(q, stream, expect_dbus=True):
+ # receive presence stanza
+ if expect_dbus:
+ presence, event_dbus = q.expect_many(
+ EventPattern('stream-presence'),
+ EventPattern('dbus-signal', signal='ContactCapabilitiesChanged')
+ )
+ assertLength(1, event_dbus.args)
+ signaled_caps = event_dbus.args[0]
+ else:
+ presence = q.expect('stream-presence')
+ signaled_caps = None
+
+ return disco_caps(q, stream, presence) + (signaled_caps,)
+
+def extract_data_forms(x_nodes):
+ dataforms = {}
+
+ if not x_nodes:
+ return dataforms
+
+ for form in x_nodes:
+ name = None
+ fields = {}
+ for field in xpath.queryForNodes('/x/field', form):
+ if field['var'] == 'FORM_TYPE':
+ name = str(field.firstChildElement())
+ else:
+ values = [str(x) for x in xpath.queryForNodes('/field/value', field)]
+
+ fields[field['var']] = values
+
+ if name is not None:
+ dataforms[name] = fields
+
+ return dataforms
+
+def disco_caps(q, stream, txt):
+ hash = txt_get_key(txt, 'hash')
+ ver = txt_get_key(txt, 'ver')
+ node = txt_get_key(txt, 'node')
+ assertEquals('sha-1', hash)
+
+ # ask caps
+ request = \
+ elem_iq(stream, 'get', from_='fake_contact@nearby')(
+ elem(ns.DISCO_INFO, 'query', node=(node + '#' + ver))
+ )
+ stream.send(request)
+
+ # receive caps
+ event = q.expect('stream-iq', query_ns=ns.DISCO_INFO, iq_id=request['id'])
+
+ # Check that Gabble's announcing the identity we think it should be.
+ identity_nodes = xpath.queryForNodes('/iq/query/identity', event.stanza)
+ assertLength(1, identity_nodes)
+ identity_node = identity_nodes[0]
+
+ assertEquals('client', identity_node['category'])
+ assertEquals('pc', identity_node['type'])
+ assertEquals(config.PACKAGE_STRING, identity_node['name'])
+ assertDoesNotContain('xml:lang', identity_node.attributes)
+
+ identity = 'client/%s//%s' % ('pc', config.PACKAGE_STRING)
+
+ features = []
+ for feature in xpath.queryForNodes('/iq/query/feature', event.stanza):
+ features.append(feature['var'])
+
+ # Check if the hash matches the announced capabilities
+ assertEquals(compute_caps_hash([identity], features, {}), ver)
+
+ return (event, features)
+
+def caps_contain(event, cap):
+ node = xpath.queryForNodes('/iq/query/feature[@var="%s"]'
+ % cap,
+ event.stanza)
+ if node is None:
+ return False
+ if len(node) != 1:
+ return False
+ var = node[0].attributes['var']
+ if var is None:
+ return False
+ return var == cap
+
+def presence_and_disco(q, conn, stream, contact, disco,
+ client, caps,
+ features, identities=[], dataforms={},
+ initial=True, show=None):
+ h = send_presence(q, conn, stream, contact, caps, initial=initial,
+ show=show)
+
+ if disco:
+ stanza = expect_disco(q, contact, client, caps)
+ send_disco_reply(stream, stanza, identities, features, dataforms)
+
+ return h
+
+def send_presence(q, conn, stream, contact, caps, initial=True, show=None):
+ h = conn.RequestHandles(cs.HT_CONTACT, [contact])[0]
+
+ if initial:
+ stream.send(make_presence(contact, status='hello'))
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='PresenceUpdate',
+ args=[{h:
+ (0L, {u'available': {'message': 'hello'}})}]),
+ EventPattern('dbus-signal', signal='PresencesChanged',
+ args=[{h:
+ (2, u'available', 'hello')}]))
+
+ # no special capabilities
+ assertEquals([(h, cs.CHANNEL_TYPE_TEXT, 3, 0)],
+ conn.Capabilities.GetCapabilities([h]))
+
+ # send updated presence with caps info
+ stream.send(make_presence(contact, show=show, status='hello', caps=caps))
+
+ return h
+
+def expect_disco(q, contact, client, caps):
+ # Gabble looks up our capabilities
+ event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO)
+ assertEquals(client + '#' + caps['ver'], event.query['node'])
+
+ return event.stanza
+
+def send_disco_reply(stream, stanza, identities, features, dataforms={}):
+ stream.send(
+ make_caps_disco_reply(stream, stanza, identities, features, dataforms))
+
+if __name__ == '__main__':
+ # example from XEP-0115
+ assertEquals('QgayPKawpkPSDYmwT/WM94uAlu0=',
+ compute_caps_hash(['client/pc//Exodus 0.9.1'],
+ ["http://jabber.org/protocol/disco#info",
+ "http://jabber.org/protocol/disco#items",
+ "http://jabber.org/protocol/muc",
+ "http://jabber.org/protocol/caps"],
+ {}))
+
+ # another example from XEP-0115
+ identities = [u'client/pc/en/Psi 0.11', u'client/pc/el/Ψ 0.11']
+ features = [
+ u'http://jabber.org/protocol/caps',
+ u'http://jabber.org/protocol/disco#info',
+ u'http://jabber.org/protocol/disco#items',
+ u'http://jabber.org/protocol/muc',
+ ]
+ dataforms = {
+ u'urn:xmpp:dataforms:softwareinfo':
+ { u'ip_version': [u'ipv4', u'ipv6'],
+ u'os': [u'Mac'],
+ u'os_version': [u'10.5.1'],
+ u'software': [u'Psi'],
+ u'software_version': [u'0.11'],
+ },
+ }
+ assertEquals('q07IKJEyjvHSyhy//CH0CxmKi8w=',
+ compute_caps_hash(identities, features, dataforms))
diff --git a/salut/tests/twisted/cm/protocol.py b/salut/tests/twisted/cm/protocol.py
new file mode 100644
index 000000000..f4243c465
--- /dev/null
+++ b/salut/tests/twisted/cm/protocol.py
@@ -0,0 +1,66 @@
+"""
+Test Salut's o.fd.T.Protocol implementation
+"""
+
+import dbus
+from servicetest import (unwrap, tp_path_prefix, assertEquals, assertContains,
+ call_async)
+from saluttest import exec_test
+from avahitest import AvahiListener
+import constants as cs
+
+def test(q, bus, conn):
+ cm = bus.get_object(cs.CM + '.salut',
+ tp_path_prefix + '/ConnectionManager/salut')
+ cm_iface = dbus.Interface(cm, cs.CM)
+ cm_prop_iface = dbus.Interface(cm, cs.PROPERTIES_IFACE)
+
+ protocols = unwrap(cm_prop_iface.Get(cs.CM, 'Protocols'))
+ assertEquals(set(['local-xmpp']), set(protocols.keys()))
+
+ protocol_names = unwrap(cm_iface.ListProtocols())
+ assertEquals(set(['local-xmpp']), set(protocol_names))
+
+ cm_params = cm_iface.GetParameters('local-xmpp')
+ local_props = protocols['local-xmpp']
+ local_params = local_props[cs.PROTOCOL + '.Parameters']
+ assertEquals(cm_params, local_params)
+
+ proto = bus.get_object(cm.bus_name, cm.object_path + '/local_xmpp')
+ proto_iface = dbus.Interface(proto, cs.PROTOCOL)
+ proto_prop_iface = dbus.Interface(proto, cs.PROPERTIES_IFACE)
+ proto_props = unwrap(proto_prop_iface.GetAll(cs.PROTOCOL))
+
+ for key in ['Parameters', 'Interfaces', 'ConnectionInterfaces',
+ 'RequestableChannelClasses', u'VCardField', u'EnglishName', u'Icon']:
+ a = local_props[cs.PROTOCOL + '.' + key]
+ b = proto_props[key]
+ assertEquals(a, b)
+
+ assertEquals('', proto_props['VCardField'])
+ assertEquals('Link-local XMPP', proto_props['EnglishName'])
+ assertEquals('im-local-xmpp', proto_props['Icon'])
+
+ assertContains(cs.CONN_IFACE_ALIASING, proto_props['ConnectionInterfaces'])
+ assertContains(cs.CONN_IFACE_AVATARS, proto_props['ConnectionInterfaces'])
+ assertContains(cs.CONN_IFACE_CONTACTS, proto_props['ConnectionInterfaces'])
+ assertContains(cs.CONN_IFACE_PRESENCE, proto_props['ConnectionInterfaces'])
+ assertContains(cs.CONN_IFACE_SIMPLE_PRESENCE,
+ proto_props['ConnectionInterfaces'])
+ assertContains(cs.CONN_IFACE_REQUESTS, proto_props['ConnectionInterfaces'])
+
+ # local-xmpp has case-sensitive literals as identifiers
+ assertEquals('SMcV@Reptile',
+ unwrap(proto_iface.NormalizeContact('SMcV@Reptile')))
+
+ # (Only) 'first-name' and 'last-name' are mandatory for IdentifyAccount()
+ call_async(q, proto_iface, 'IdentifyAccount', {'first-name': 'Simon'})
+ q.expect('dbus-error', method='IdentifyAccount', name=cs.INVALID_ARGUMENT)
+
+ # Identifying an account doesn't do much, anyway
+ test_params = {'first-name': 'Simon', 'last-name': 'McVittie'}
+ acc_name = unwrap(proto_iface.IdentifyAccount(test_params))
+ assertEquals('', acc_name)
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/constants.py b/salut/tests/twisted/constants.py
new file mode 100644
index 000000000..5b42d3150
--- /dev/null
+++ b/salut/tests/twisted/constants.py
@@ -0,0 +1,444 @@
+"""
+Some handy constants for other tests to share and enjoy.
+"""
+
+from dbus import PROPERTIES_IFACE
+
+CM = "org.freedesktop.Telepathy.ConnectionManager"
+
+HT_NONE = 0
+HT_CONTACT = 1
+HT_ROOM = 2
+HT_LIST = 3
+HT_GROUP = 4
+
+CHANNEL = "org.freedesktop.Telepathy.Channel"
+
+CHANNEL_IFACE_CALL_STATE = CHANNEL + ".Interface.CallState"
+CHANNEL_IFACE_CHAT_STATE = CHANNEL + '.Interface.ChatState'
+CHANNEL_IFACE_DESTROYABLE = CHANNEL + ".Interface.Destroyable"
+CHANNEL_IFACE_DTMF = CHANNEL + ".Interface.DTMF"
+CHANNEL_IFACE_GROUP = CHANNEL + ".Interface.Group"
+CHANNEL_IFACE_HOLD = CHANNEL + ".Interface.Hold"
+CHANNEL_IFACE_MEDIA_SIGNALLING = CHANNEL + ".Interface.MediaSignalling"
+CHANNEL_IFACE_MESSAGES = CHANNEL + ".Interface.Messages"
+CHANNEL_IFACE_PASSWORD = CHANNEL + ".Interface.Password"
+CHANNEL_IFACE_TUBE = CHANNEL + ".Interface.Tube"
+CHANNEL_IFACE_SASL_AUTH = CHANNEL + ".Interface.SASLAuthentication"
+CHANNEL_IFACE_CONFERENCE = CHANNEL + '.Interface.Conference'
+CHANNEL_IFACE_FILE_TRANSFER_METADATA = CHANNEL + '.Interface.FileTransfer.Metadata'
+
+CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call.DRAFT"
+CHANNEL_TYPE_CONTACT_LIST = CHANNEL + ".Type.ContactList"
+CHANNEL_TYPE_CONTACT_SEARCH = CHANNEL + ".Type.ContactSearch"
+CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text"
+CHANNEL_TYPE_TUBES = CHANNEL + ".Type.Tubes"
+CHANNEL_TYPE_STREAM_TUBE = CHANNEL + ".Type.StreamTube"
+CHANNEL_TYPE_DBUS_TUBE = CHANNEL + ".Type.DBusTube"
+CHANNEL_TYPE_STREAMED_MEDIA = CHANNEL + ".Type.StreamedMedia"
+CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text"
+CHANNEL_TYPE_FILE_TRANSFER = CHANNEL + ".Type.FileTransfer"
+CHANNEL_TYPE_SERVER_AUTHENTICATION = \
+ CHANNEL + ".Type.ServerAuthentication"
+CHANNEL_TYPE_SERVER_TLS_CONNECTION = \
+ CHANNEL + ".Type.ServerTLSConnection"
+
+TP_AWKWARD_PROPERTIES = "org.freedesktop.Telepathy.Properties"
+PROPERTY_FLAG_READ = 1
+PROPERTY_FLAG_WRITE = 2
+PROPERTY_FLAGS_RW = PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE
+
+CHANNEL_TYPE = CHANNEL + '.ChannelType'
+TARGET_HANDLE_TYPE = CHANNEL + '.TargetHandleType'
+TARGET_HANDLE = CHANNEL + '.TargetHandle'
+TARGET_ID = CHANNEL + '.TargetID'
+REQUESTED = CHANNEL + '.Requested'
+INITIATOR_HANDLE = CHANNEL + '.InitiatorHandle'
+INITIATOR_ID = CHANNEL + '.InitiatorID'
+INTERFACES = CHANNEL + '.Interfaces'
+
+INITIAL_AUDIO = CHANNEL_TYPE_STREAMED_MEDIA + '.InitialAudio'
+INITIAL_VIDEO = CHANNEL_TYPE_STREAMED_MEDIA + '.InitialVideo'
+IMMUTABLE_STREAMS = CHANNEL_TYPE_STREAMED_MEDIA + '.ImmutableStreams'
+
+CALL_INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio'
+CALL_INITIAL_AUDIO_NAME = CHANNEL_TYPE_CALL + '.InitialAudioName'
+CALL_INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo'
+CALL_INITIAL_VIDEO_NAME = CHANNEL_TYPE_CALL + '.InitialVideoName'
+CALL_MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents'
+
+CALL_CONTENT = 'org.freedesktop.Telepathy.Call.Content.DRAFT'
+CALL_CONTENT_IFACE_MEDIA = \
+ 'org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT'
+
+CALL_CONTENT_CODECOFFER = \
+ 'org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT'
+
+CALL_STREAM = 'org.freedesktop.Telepathy.Call.Stream.DRAFT'
+CALL_STREAM_IFACE_MEDIA = \
+ 'org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT'
+
+CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT'
+
+CALL_MEDIA_TYPE_AUDIO = 0
+CALL_MEDIA_TYPE_VIDEO = 1
+
+CALL_CONTENT_PACKETIZATION_RTP = 0
+CALL_CONTENT_PACKETIZATION_RAW = 1
+CALL_CONTENT_PACKETIZATION_MSN_WEBCAM = 2
+
+CALL_STREAM_TRANSPORT_RAW_UDP = 1
+CALL_STREAM_TRANSPORT_ICE = 2
+CALL_STREAM_TRANSPORT_GOOGLE = 3
+
+CALL_STATE_UNKNOWN = 0
+CALL_STATE_PENDING_INITIATOR = 1
+CALL_STATE_PENDING_RECEIVER = 2
+CALL_STATE_ACCEPTED = 3
+CALL_STATE_ENDED = 4
+
+CALL_MEMBER_FLAG_RINGING = 1
+CALL_MEMBER_FLAG_HELD = 2
+
+CALL_DISPOSITION_NONE = 0
+CALL_DISPOSITION_INITIAL = 1
+
+CALL_SENDING_STATE_NONE = 0
+CALL_SENDING_STATE_PENDING_SEND = 1
+CALL_SENDING_STATE_SENDING = 2
+
+SUBSCRIPTION_STATE_UNKNOWN = 0
+SUBSCRIPTION_STATE_NO = 1
+SUBSCRIPTION_STATE_REMOVED_REMOTELY = 2
+SUBSCRIPTION_STATE_ASK = 3
+SUBSCRIPTION_STATE_YES = 4
+
+CONTACT_LIST_STATE_NONE = 0
+CONTACT_LIST_STATE_WAITING = 1
+CONTACT_LIST_STATE_FAILURE = 2
+CONTACT_LIST_STATE_SUCCESS = 3
+
+CONN = "org.freedesktop.Telepathy.Connection"
+CONN_IFACE_AVATARS = CONN + '.Interface.Avatars'
+CONN_IFACE_ALIASING = CONN + '.Interface.Aliasing'
+CONN_IFACE_CAPS = CONN + '.Interface.Capabilities'
+CONN_IFACE_CONTACTS = CONN + '.Interface.Contacts'
+CONN_IFACE_CONTACT_CAPS = CONN + '.Interface.ContactCapabilities'
+CONN_IFACE_CONTACT_INFO = CONN + ".Interface.ContactInfo"
+CONN_IFACE_PRESENCE = CONN + '.Interface.Presence'
+CONN_IFACE_SIMPLE_PRESENCE = CONN + '.Interface.SimplePresence'
+CONN_IFACE_REQUESTS = CONN + '.Interface.Requests'
+CONN_IFACE_LOCATION = CONN + '.Interface.Location'
+CONN_IFACE_GABBLE_DECLOAK = CONN + '.Interface.Gabble.Decloak'
+CONN_IFACE_MAIL_NOTIFICATION = CONN + '.Interface.MailNotification'
+CONN_IFACE_CONTACT_LIST = CONN + '.Interface.ContactList'
+CONN_IFACE_CONTACT_GROUPS = CONN + '.Interface.ContactGroups'
+CONN_IFACE_CLIENT_TYPES = CONN + '.Interface.ClientTypes'
+CONN_IFACE_POWER_SAVING = CONN + '.Interface.PowerSaving'
+
+ATTR_CONTACT_CAPABILITIES = CONN_IFACE_CONTACT_CAPS + '/capabilities'
+
+STREAM_HANDLER = 'org.freedesktop.Telepathy.Media.StreamHandler'
+
+ERROR = 'org.freedesktop.Telepathy.Error'
+INVALID_ARGUMENT = ERROR + '.InvalidArgument'
+NOT_IMPLEMENTED = ERROR + '.NotImplemented'
+NOT_AVAILABLE = ERROR + '.NotAvailable'
+PERMISSION_DENIED = ERROR + '.PermissionDenied'
+OFFLINE = ERROR + '.Offline'
+NOT_CAPABLE = ERROR + '.NotCapable'
+CONNECTION_REFUSED = ERROR + '.ConnectionRefused'
+CONNECTION_FAILED = ERROR + '.ConnectionFailed'
+CONNECTION_LOST = ERROR + '.ConnectionLost'
+CANCELLED = ERROR + '.Cancelled'
+DISCONNECTED = ERROR + '.Disconnected'
+REGISTRATION_EXISTS = ERROR + '.RegistrationExists'
+AUTHENTICATION_FAILED = ERROR + '.AuthenticationFailed'
+CONNECTION_REPLACED = ERROR + '.ConnectionReplaced'
+ALREADY_CONNECTED = ERROR + '.AlreadyConnected'
+NETWORK_ERROR = ERROR + '.NetworkError'
+NOT_YET = ERROR + '.NotYet'
+INVALID_HANDLE = ERROR + '.InvalidHandle'
+CERT_UNTRUSTED = ERROR + '.Cert.Untrusted'
+SERVICE_BUSY = ERROR + '.ServiceBusy'
+SERVICE_CONFUSED = ERROR + '.ServiceConfused'
+
+UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
+
+TUBE_PARAMETERS = CHANNEL_IFACE_TUBE + '.Parameters'
+TUBE_STATE = CHANNEL_IFACE_TUBE + '.State'
+STREAM_TUBE_SERVICE = CHANNEL_TYPE_STREAM_TUBE + '.Service'
+DBUS_TUBE_SERVICE_NAME = CHANNEL_TYPE_DBUS_TUBE + '.ServiceName'
+DBUS_TUBE_DBUS_NAMES = CHANNEL_TYPE_DBUS_TUBE + '.DBusNames'
+DBUS_TUBE_SUPPORTED_ACCESS_CONTROLS = CHANNEL_TYPE_DBUS_TUBE + '.SupportedAccessControls'
+STREAM_TUBE_SUPPORTED_SOCKET_TYPES = CHANNEL_TYPE_STREAM_TUBE + '.SupportedSocketTypes'
+
+CONFERENCE_INITIAL_CHANNELS = CHANNEL_IFACE_CONFERENCE + '.InitialChannels'
+CONFERENCE_INITIAL_INVITEE_HANDLES = CHANNEL_IFACE_CONFERENCE + '.InitialInviteeHandles'
+CONFERENCE_INITIAL_INVITEE_IDS = CHANNEL_IFACE_CONFERENCE + '.InitialInviteeIDs'
+
+CONTACT_SEARCH_ASK = CHANNEL_TYPE_CONTACT_SEARCH + '.AvailableSearchKeys'
+CONTACT_SEARCH_SERVER = CHANNEL_TYPE_CONTACT_SEARCH + '.Server'
+CONTACT_SEARCH_STATE = CHANNEL_TYPE_CONTACT_SEARCH + '.SearchState'
+
+SEARCH_NOT_STARTED = 0
+SEARCH_IN_PROGRESS = 1
+SEARCH_MORE_AVAILABLE = 2
+SEARCH_COMPLETED = 3
+SEARCH_FAILED = 4
+
+TUBE_CHANNEL_STATE_LOCAL_PENDING = 0
+TUBE_CHANNEL_STATE_REMOTE_PENDING = 1
+TUBE_CHANNEL_STATE_OPEN = 2
+TUBE_CHANNEL_STATE_NOT_OFFERED = 3
+
+MEDIA_STREAM_TYPE_AUDIO = 0
+MEDIA_STREAM_TYPE_VIDEO = 1
+
+SOCKET_ADDRESS_TYPE_UNIX = 0
+SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX = 1
+SOCKET_ADDRESS_TYPE_IPV4 = 2
+SOCKET_ADDRESS_TYPE_IPV6 = 3
+
+SOCKET_ACCESS_CONTROL_LOCALHOST = 0
+SOCKET_ACCESS_CONTROL_PORT = 1
+SOCKET_ACCESS_CONTROL_NETMASK = 2
+SOCKET_ACCESS_CONTROL_CREDENTIALS = 3
+
+TUBE_STATE_LOCAL_PENDING = 0
+TUBE_STATE_REMOTE_PENDING = 1
+TUBE_STATE_OPEN = 2
+TUBE_STATE_NOT_OFFERED = 3
+
+TUBE_TYPE_DBUS = 0
+TUBE_TYPE_STREAM = 1
+
+MEDIA_STREAM_DIRECTION_NONE = 0
+MEDIA_STREAM_DIRECTION_SEND = 1
+MEDIA_STREAM_DIRECTION_RECEIVE = 2
+MEDIA_STREAM_DIRECTION_BIDIRECTIONAL = 3
+
+MEDIA_STREAM_PENDING_LOCAL_SEND = 1
+MEDIA_STREAM_PENDING_REMOTE_SEND = 2
+
+MEDIA_STREAM_TYPE_AUDIO = 0
+MEDIA_STREAM_TYPE_VIDEO = 1
+
+MEDIA_STREAM_STATE_DISCONNECTED = 0
+MEDIA_STREAM_STATE_CONNECTING = 1
+MEDIA_STREAM_STATE_CONNECTED = 2
+
+MEDIA_STREAM_DIRECTION_NONE = 0
+MEDIA_STREAM_DIRECTION_SEND = 1
+MEDIA_STREAM_DIRECTION_RECEIVE = 2
+MEDIA_STREAM_DIRECTION_BIDIRECTIONAL = 3
+
+FT_STATE_NONE = 0
+FT_STATE_PENDING = 1
+FT_STATE_ACCEPTED = 2
+FT_STATE_OPEN = 3
+FT_STATE_COMPLETED = 4
+FT_STATE_CANCELLED = 5
+
+FT_STATE_CHANGE_REASON_NONE = 0
+FT_STATE_CHANGE_REASON_REQUESTED = 1
+FT_STATE_CHANGE_REASON_LOCAL_STOPPED = 2
+FT_STATE_CHANGE_REASON_REMOTE_STOPPED = 3
+FT_STATE_CHANGE_REASON_LOCAL_ERROR = 4
+FT_STATE_CHANGE_REASON_REMOTE_ERROR = 5
+
+FILE_HASH_TYPE_NONE = 0
+FILE_HASH_TYPE_MD5 = 1
+FILE_HASH_TYPE_SHA1 = 2
+FILE_HASH_TYPE_SHA256 = 3
+
+FT_STATE = CHANNEL_TYPE_FILE_TRANSFER + '.State'
+FT_CONTENT_TYPE = CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'
+FT_FILENAME = CHANNEL_TYPE_FILE_TRANSFER + '.Filename'
+FT_SIZE = CHANNEL_TYPE_FILE_TRANSFER + '.Size'
+FT_CONTENT_HASH_TYPE = CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'
+FT_CONTENT_HASH = CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'
+FT_DESCRIPTION = CHANNEL_TYPE_FILE_TRANSFER + '.Description'
+FT_DATE = CHANNEL_TYPE_FILE_TRANSFER + '.Date'
+FT_AVAILABLE_SOCKET_TYPES = CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'
+FT_TRANSFERRED_BYTES = CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'
+FT_INITIAL_OFFSET = CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'
+FT_FILE_COLLECTION = CHANNEL_TYPE_FILE_TRANSFER + '.FUTURE.FileCollection'
+FT_URI = CHANNEL_TYPE_FILE_TRANSFER + '.URI'
+FT_SERVICE_NAME = CHANNEL_IFACE_FILE_TRANSFER_METADATA + '.ServiceName'
+FT_METADATA = CHANNEL_IFACE_FILE_TRANSFER_METADATA + '.Metadata'
+
+GF_CAN_ADD = 1
+GF_CAN_REMOVE = 2
+GF_CAN_RESCIND = 4
+GF_MESSAGE_ADD = 8
+GF_MESSAGE_REMOVE = 16
+GF_MESSAGE_ACCEPT = 32
+GF_MESSAGE_REJECT = 64
+GF_MESSAGE_RESCIND = 128
+GF_CHANNEL_SPECIFIC_HANDLES = 256
+GF_ONLY_ONE_GROUP = 512
+GF_HANDLE_OWNERS_NOT_AVAILABLE = 1024
+GF_PROPERTIES = 2048
+GF_MEMBERS_CHANGED_DETAILED = 4096
+
+GC_REASON_NONE = 0
+GC_REASON_OFFLINE = 1
+GC_REASON_KICKED = 2
+GC_REASON_BUSY = 3
+GC_REASON_INVITED = 4
+GC_REASON_BANNED = 5
+GC_REASON_ERROR = 6
+GC_REASON_INVALID_CONTACT = 7
+GC_REASON_NO_ANSWER = 8
+GC_REASON_RENAMED = 9
+GC_REASON_PERMISSION_DENIED = 10
+GC_REASON_SEPARATED = 11
+
+HS_UNHELD = 0
+HS_HELD = 1
+HS_PENDING_HOLD = 2
+HS_PENDING_UNHOLD = 3
+
+HSR_NONE = 0
+HSR_REQUESTED = 1
+HSR_RESOURCE_NOT_AVAILABLE = 2
+
+CALL_STATE_RINGING = 1
+CALL_STATE_QUEUED = 2
+CALL_STATE_HELD = 4
+CALL_STATE_FORWARDED = 8
+
+CONN_STATUS_CONNECTED = 0
+CONN_STATUS_CONNECTING = 1
+CONN_STATUS_DISCONNECTED = 2
+
+CSR_NONE_SPECIFIED = 0
+CSR_REQUESTED = 1
+CSR_NETWORK_ERROR = 2
+CSR_AUTHENTICATION_FAILED = 3
+CSR_ENCRYPTION_ERROR = 4
+CSR_NAME_IN_USE = 5
+CSR_CERT_NOT_PROVIDED = 6
+CSR_CERT_UNTRUSTED = 7
+CSR_CERT_EXPIRED = 8
+CSR_CERT_NOT_ACTIVATED = 9
+CSR_CERT_HOSTNAME_MISMATCH = 10
+CSR_CERT_FINGERPRINT_MISMATCH = 11
+CSR_CERT_SELF_SIGNED = 12
+CSR_CERT_OTHER_ERROR = 13
+
+BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
+
+CHAT_STATE_GONE = 0
+CHAT_STATE_INACTIVE = 1
+CHAT_STATE_ACTIVE = 2
+CHAT_STATE_PAUSED = 3
+CHAT_STATE_COMPOSING = 4
+
+# Channel_Media_Capabilities
+MEDIA_CAP_AUDIO = 1
+MEDIA_CAP_VIDEO = 2
+MEDIA_CAP_STUN = 4
+MEDIA_CAP_GTALKP2P = 8
+MEDIA_CAP_ICEUDP = 16
+MEDIA_CAP_IMMUTABLE_STREAMS = 32
+
+CLIENT = 'org.freedesktop.Telepathy.Client'
+
+PRESENCE_OFFLINE = 1
+PRESENCE_AVAILABLE = 2
+PRESENCE_AWAY = 3
+PRESENCE_EXTENDED_AWAY = 4
+PRESENCE_HIDDEN = 5
+PRESENCE_BUSY = 6
+PRESENCE_UNKNOWN = 7
+PRESENCE_ERROR = 8
+
+CONTACT_INFO_FLAG_CAN_SET = 1
+CONTACT_INFO_FLAG_PUSH = 2
+CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT = 1
+CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME = 2
+
+# Channel_Interface_SaslAuthentication
+SASL_STATUS_NOT_STARTED = 0
+SASL_STATUS_IN_PROGRESS = 1
+SASL_STATUS_SERVER_SUCCEEDED = 2
+SASL_STATUS_CLIENT_ACCEPTED = 3
+SASL_STATUS_SUCCEEDED = 4
+SASL_STATUS_SERVER_FAILED = 5
+SASL_STATUS_CLIENT_FAILED = 6
+
+SASL_ABORT_REASON_INVALID_CHALLENGE = 0
+SASL_ABORT_REASON_USER_ABORT = 1
+
+AUTH_METHOD = CHANNEL_TYPE_SERVER_AUTHENTICATION + ".AuthenticationMethod"
+SASL_AVAILABLE_MECHANISMS = CHANNEL_IFACE_SASL_AUTH + ".AvailableMechanisms"
+SASL_STATUS = CHANNEL_IFACE_SASL_AUTH + ".SASLStatus"
+SASL_ERROR = CHANNEL_IFACE_SASL_AUTH + ".SASLError"
+SASL_ERROR_DETAILS = CHANNEL_IFACE_SASL_AUTH + ".SASLErrorDetails"
+SASL_CONTEXT = CHANNEL_IFACE_SASL_AUTH + ".SASLContext"
+SASL_AUTHORIZATION_IDENTITY = CHANNEL_IFACE_SASL_AUTH + ".AuthorizationIdentity"
+SASL_DEFAULT_REALM = CHANNEL_IFACE_SASL_AUTH + ".DefaultRealm"
+SASL_DEFAULT_USERNAME = CHANNEL_IFACE_SASL_AUTH + ".DefaultUsername"
+
+# Channel_Type_ServerTLSConnection
+TLS_CERT_PATH = CHANNEL_TYPE_SERVER_TLS_CONNECTION + ".ServerCertificate"
+TLS_HOSTNAME = CHANNEL_TYPE_SERVER_TLS_CONNECTION + ".Hostname"
+
+# Connection.Interface.Location
+
+LOCATION_FEATURE_CAN_SET = 1
+
+# Channel.Type.Text
+
+MT_NORMAL = 0
+MT_ACTION = 1
+MT_NOTICE = 2
+MT_AUTO_REPLY = 3
+MT_DELIVERY_REPORT = 4
+
+PROTOCOL = 'org.freedesktop.Telepathy.Protocol'
+PROTOCOL_IFACE_PRESENCES = PROTOCOL + '.Interface.Presence'
+PARAM_REQUIRED = 1
+PARAM_REGISTER = 2
+PARAM_HAS_DEFAULT = 4
+PARAM_SECRET = 8
+PARAM_DBUS_PROPERTY = 16
+
+AUTHENTICATION = 'org.freedesktop.Telepathy.Authentication'
+AUTH_TLS_CERT = AUTHENTICATION + ".TLSCertificate"
+
+TLS_CERT_STATE_PENDING = 0
+TLS_CERT_STATE_ACCEPTED = 1
+TLS_CERT_STATE_REJECTED = 2
+
+TLS_REJECT_REASON_UNKNOWN = 0
+TLS_REJECT_REASON_UNTRUSTED = 1
+
+# Channel.Interface.Messages
+
+MESSAGE_PART_SUPPORT_FLAGS = CHANNEL_IFACE_MESSAGES + '.MessagePartSupportFlags'
+DELIVERY_REPORTING_SUPPORT = CHANNEL_IFACE_MESSAGES + '.DeliveryReportingSupport'
+SUPPORTED_CONTENT_TYPES = CHANNEL_IFACE_MESSAGES + '.SupportedContentTypes'
+
+MSG_SENDING_FLAGS_REPORT_DELIVERY = 1
+MSG_SENDING_FLAGS_REPORT_READ = 2
+MSG_SENDING_FLAGS_REPORT_DELETED = 4
+
+DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_FAILURES = 1
+DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_SUCCESSES = 2
+DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_READ = 4
+DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_DELETED = 8
+
+MEDIA_STREAM_ERROR_UNKNOWN = 0
+MEDIA_STREAM_ERROR_EOS = 1
+MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED = 2
+MEDIA_STREAM_ERROR_CONNECTION_FAILED = 3
+MEDIA_STREAM_ERROR_NETWORK_ERROR = 4
+MEDIA_STREAM_ERROR_NO_CODECS = 5
+MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR = 6
+MEDIA_STREAM_ERROR_MEDIA_ERROR = 7
+
+PASSWORD_FLAG_PROVIDE = 8
diff --git a/salut/tests/twisted/ipv6.py b/salut/tests/twisted/ipv6.py
new file mode 100644
index 000000000..d910fecc5
--- /dev/null
+++ b/salut/tests/twisted/ipv6.py
@@ -0,0 +1,181 @@
+import socket
+from twisted.internet import tcp
+
+class Ipv6Server(tcp.Server):
+ def getHost(self):
+ return IPv6Address('TCP', *(self.socket.getsockname()))
+
+ def getPeer(self):
+ return IPv6Address('TCP', *(self.client))
+
+class Ipv6Port(tcp.Port):
+ addressFamily = socket.AF_INET6
+
+ transport = Ipv6Server
+
+ def _buildAddr(self, (host, port, flowinfo, scopeid)):
+ """
+ Build and return an IPv6Address from the passed sockaddr-in6 tuple.
+ """
+ return IPv6Address('TCP', host, port, flowinfo, scopeid)
+
+ def getHost(self):
+ return IPv6Address('TCP', *(self.socket.getsockname()))
+
+def listenTCP6(port, factory, backlog=5, interface='::', reactor=None):
+ if reactor is None:
+ from twisted.internet import reactor
+ p = Ipv6Port(port, factory, backlog, interface, reactor=reactor)
+ p.startListening()
+ return p
+
+# stolen from http://twistedmatrix.com/trac/attachment/ticket/3014/ipv6.2.patch
+from zope.interface import implements
+from twisted.internet.interfaces import IAddress
+from twisted.internet import base, address, error
+from twisted.internet.tcp import Client
+from twisted.python.util import unsignedID
+import types
+
+class IPv6Address(object):
+ """
+ Object representing an IPv6 socket endpoint.
+
+ @ivar type: A string describing the type of transport, either 'TCP' or 'UDP'.
+ @ivar host: A string containing the coloned-oct IP address.
+ @ivar port: An integer representing the port number.
+ @ivar flowinfo: An integer representing the sockaddr-in6 flowinfo
+ @ivar scopeid: An integer representing the sockaddr-in6 scopeid
+ """
+
+ implements(IAddress)
+
+ def __init__(self, type, host, port, flowinfo=0, scopeid=0):
+ if type not in ('TCP', 'UDP'):
+ raise ValueError, "illegal transport type"
+ self.type = type
+ self.host = host
+ self.port = port
+ self.flowinfo = flowinfo
+ self.scopeid = scopeid
+
+ def __eq__(self, other):
+ if isinstance(other, tuple):
+ return tuple(self) == other
+ elif isinstance(other, IPv6Address):
+ a = (self.type, self.host, self.port, self.flowinfo, self.scopeid)
+ b = (other.type, other.host, other.port, other.flowinfo, other.scopeid)
+ return a == b
+ return False
+
+ def __str__(self):
+ return 'IPv6Address(%s, %r, %d, %s, %s, %s)' % (self.type, self.host,
+ self.port, self.flowinfo, self.scopeid)
+
+class Client6(Client):
+ """
+ A TCP6 client.
+ """
+ addressFamily = socket.AF_INET6
+
+ def __init__(self, host, port, bindAddress, connector, reactor=None,
+ flowinfo=0, scopeid=0):
+ Client.__init__(self, host, port, bindAddress, connector, reactor)
+ self.addr = (host, port, flowinfo, scopeid)
+
+ def resolveAddress(self):
+ """
+ Lookup the IPv6 address for self.addr[0] if necessary, then set
+ self.realAddress to that IPv6 address.
+ """
+ if isIPv6Address(self.addr[0]):
+ self._setRealAddress(self.addr[0])
+ else:
+ d = self.reactor.resolve(self.addr[0])
+ d.addCallbacks(self._setRealAddress, self.failIfNotConnected)
+
+ def _setRealAddress(self, address):
+ """
+ Set self.realAddress[0] to address. Set the remaining parts of
+ self.realAddress to the corresponding parts of self.addr.
+ """
+ self.realAddress = (address, self.addr[1], self.addr[2], self.addr[3])
+ self.doConnect()
+
+ def getHost(self):
+ """
+ Returns an IPv6Address.
+
+ This indicates the address from which I am connecting.
+ """
+ return address.IPv6Address('TCP', *(self.socket.getsockname()))
+
+ def getPeer(self):
+ """
+ Returns an IPv6Address.
+
+ This indicates the address that I am connected to.
+ """
+ return IPv6Address('TCP', *(self.addr))
+
+ def __repr__(self):
+ s = '<%s to %s at %x>' % (self.__class__, self.addr, unsignedID(self))
+ return s
+
+class Connector6(base.BaseConnector):
+ """
+ IPv6 implementation of connector
+
+ @ivar flowinfo An integer representing the sockaddr-in6 flowinfo
+ @ivar scopeid An integer representing the sockaddr-in6 scopeid
+ """
+
+ def __init__(self, host, port, factory, timeout, bindAddress,
+ reactor=None, flowinfo=0, scopeid=0):
+ self.host = host
+ if isinstance(port, types.StringTypes):
+ try:
+ port = socket.getservbyname(port, 'tcp')
+ except socket.error, e:
+ raise error.ServiceNameUnknownError(string="%s (%r)" % (e, port))
+ self.port = port
+ self.bindAddress = bindAddress
+ self.flowinfo = flowinfo
+ self.scopeid = scopeid
+ base.BaseConnector.__init__(self, factory, timeout, reactor)
+
+ def _makeTransport(self):
+ """
+ Build and return a TCP6 client for the connector's transport.
+ """
+ return Client6(self.host, self.port, self.bindAddress, self,
+ self.reactor, self.flowinfo, self.scopeid)
+
+ def getDestination(self):
+ """
+ @see twisted.internet.interfaces.IConnector.getDestination
+ """
+ return address.IPv6Address('TCP', self.host, self.port, self.flowinfo,
+ self.scopeid)
+
+def connectTCP6(reactor, host, port, factory, timeout=30, bindAddress=None,
+ flowinfo=0, scopeid=0):
+ """
+ @see: twisted.internet.interfaces.IReactorTCP.connectTCP6
+ """
+ c = Connector6(host, port, factory, timeout, bindAddress, reactor,
+ flowinfo, scopeid)
+ c.connect()
+ return c
+
+def isIPv6Address(ip):
+ """
+ Return True iff ip is a valid bare IPv6 address.
+
+ Return False for 'enhanced' IPv6 addresses like '::1%lo' and '::1/128'
+ """
+ try:
+ socket.inet_pton(socket.AF_INET6, ip)
+ except (ValueError, socket.error):
+ return False
+ return True
diff --git a/salut/tests/twisted/ns.py b/salut/tests/twisted/ns.py
new file mode 100644
index 000000000..b538fc4fb
--- /dev/null
+++ b/salut/tests/twisted/ns.py
@@ -0,0 +1,80 @@
+AMP = "http://jabber.org/protocol/amp"
+BYTESTREAMS = 'http://jabber.org/protocol/bytestreams'
+CHAT_STATES = 'http://jabber.org/protocol/chatstates'
+CAPS = "http://jabber.org/protocol/caps"
+DISCO_INFO = "http://jabber.org/protocol/disco#info"
+DISCO_ITEMS = "http://jabber.org/protocol/disco#items"
+FEATURE_NEG = 'http://jabber.org/protocol/feature-neg'
+FILE_TRANSFER = 'http://jabber.org/protocol/si/profile/file-transfer'
+GEOLOC = 'http://jabber.org/protocol/geoloc'
+GOOGLE_FEAT_SESSION = 'http://www.google.com/xmpp/protocol/session'
+GOOGLE_FEAT_SHARE = 'http://google.com/xmpp/protocol/share/v1'
+GOOGLE_FEAT_VOICE = 'http://www.google.com/xmpp/protocol/voice/v1'
+GOOGLE_FEAT_VIDEO = 'http://www.google.com/xmpp/protocol/video/v1'
+GOOGLE_JINGLE_INFO = 'google:jingleinfo'
+GOOGLE_P2P = "http://www.google.com/transport/p2p"
+GOOGLE_QUEUE = 'google:queue'
+GOOGLE_ROSTER = 'google:roster'
+GOOGLE_SESSION = "http://www.google.com/session"
+GOOGLE_SESSION_SHARE = "http://www.google.com/session/share"
+GOOGLE_SESSION_PHONE = "http://www.google.com/session/phone"
+GOOGLE_SESSION_VIDEO = "http://www.google.com/session/video"
+GOOGLE_MAIL_NOTIFY = "google:mail:notify"
+IBB = 'http://jabber.org/protocol/ibb'
+JINGLE_015 = "http://jabber.org/protocol/jingle"
+JINGLE_015_AUDIO = "http://jabber.org/protocol/jingle/description/audio"
+JINGLE_015_VIDEO = "http://jabber.org/protocol/jingle/description/video"
+JINGLE = "urn:xmpp:jingle:1"
+JINGLE_RTP = "urn:xmpp:jingle:apps:rtp:1"
+JINGLE_RTP_AUDIO = "urn:xmpp:jingle:apps:rtp:audio"
+JINGLE_RTP_VIDEO = "urn:xmpp:jingle:apps:rtp:video"
+JINGLE_RTP_ERRORS = "urn:xmpp:jingle:apps:rtp:errors:1"
+JINGLE_RTP_INFO_1 = "urn:xmpp:jingle:apps:rtp:info:1"
+JINGLE_TRANSPORT_ICEUDP = "urn:xmpp:jingle:transports:ice-udp:1"
+JINGLE_TRANSPORT_RAWUDP = "urn:xmpp:jingle:transports:raw-udp:1"
+MUC = 'http://jabber.org/protocol/muc'
+MUC_BYTESTREAM = 'http://telepathy.freedesktop.org/xmpp/protocol/muc-bytestream'
+MUC_OWNER = '%s#owner' % MUC
+MUC_USER = '%s#user' % MUC
+NICK = "http://jabber.org/protocol/nick"
+NS_XMPP_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
+NS_XMPP_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'
+NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
+NS_XMPP_SESSION = 'urn:ietf:params:xml:ns:xmpp-session'
+OLPC_ACTIVITIES = "http://laptop.org/xmpp/activities"
+OLPC_ACTIVITIES_NOTIFY = "%s+notify" % OLPC_ACTIVITIES
+OLPC_ACTIVITY = "http://laptop.org/xmpp/activity"
+OLPC_ACTIVITY_PROPS = "http://laptop.org/xmpp/activity-properties"
+OLPC_ACTIVITY_PROPS_NOTIFY = "%s+notify" % OLPC_ACTIVITY_PROPS
+OLPC_BUDDY = "http://laptop.org/xmpp/buddy"
+OLPC_BUDDY_PROPS = "http://laptop.org/xmpp/buddy-properties"
+OLPC_BUDDY_PROPS_NOTIFY = "%s+notify" % OLPC_BUDDY_PROPS
+OLPC_CURRENT_ACTIVITY = "http://laptop.org/xmpp/current-activity"
+OLPC_CURRENT_ACTIVITY_NOTIFY = "%s+notify" % OLPC_CURRENT_ACTIVITY
+PUBSUB = "http://jabber.org/protocol/pubsub"
+PUBSUB_EVENT = "%s#event" % PUBSUB
+REGISTER = "jabber:iq:register"
+ROSTER = "jabber:iq:roster"
+SEARCH = 'jabber:iq:search'
+SI = 'http://jabber.org/protocol/si'
+SI_MULTIPLE = 'http://telepathy.freedesktop.org/xmpp/si-multiple'
+STANZA = "urn:ietf:params:xml:ns:xmpp-stanzas"
+STREAMS = "urn:ietf:params:xml:ns:xmpp-streams"
+TEMPPRES = "urn:xmpp:temppres:0"
+TUBES = 'http://telepathy.freedesktop.org/xmpp/tubes'
+MUJI = 'http://telepathy.freedesktop.org/xmpp/muji'
+VCARD_TEMP = 'vcard-temp'
+VCARD_TEMP_UPDATE = 'vcard-temp:x:update'
+X_DATA = 'jabber:x:data'
+X_DELAY = 'jabber:x:delay'
+XML = 'http://www.w3.org/XML/1998/namespace'
+X_OOB = 'jabber:x:oob'
+IQ_OOB = 'jabber:iq:oob'
+GABBLE_CAPS="http://telepathy.freedesktop.org/caps"
+PRESENCE_INVISIBLE = 'presence-invisible'
+PRIVACY = 'jabber:iq:privacy'
+INVISIBLE = 'urn:xmpp:invisible:0'
+GOOGLE_SHARED_STATUS = 'google:shared-status'
+VERSION = 'jabber:iq:version'
+TP_FT_METADATA_SERVICE = 'http://telepathy.freedesktop.org/xmpp/file-transfer-service'
+TP_FT_METADATA = 'http://telepathy.freedesktop.org/xmpp/file-transfer-metadata'
diff --git a/salut/tests/twisted/saluttest.py b/salut/tests/twisted/saluttest.py
new file mode 100644
index 000000000..3df971f21
--- /dev/null
+++ b/salut/tests/twisted/saluttest.py
@@ -0,0 +1,332 @@
+
+"""
+Infrastructure code for testing Salut
+"""
+
+import os
+import sys
+import time
+import re
+from subprocess import Popen
+
+import servicetest
+from servicetest import call_async, EventPattern, Event, unwrap
+from twisted.internet import reactor
+import constants as cs
+from twisted.words.protocols.jabber.client import IQ
+from twisted.words.xish import domish, xpath
+import ns
+
+import dbus
+import glib
+
+# keep sync with src/capabilities.c:self_advertised_features
+fixed_features = [ns.SI, ns.TUBES, ns.IQ_OOB, ns.X_OOB, ns.TP_FT_METADATA]
+
+def make_result_iq(iq):
+ result = IQ(None, "result")
+ result["id"] = iq["id"]
+ query = iq.firstChildElement()
+
+ if query:
+ result.addElement((query.uri, query.name))
+
+ return result
+
+def sync_stream(q, xmpp_connection):
+ """Used to ensure that Salut has processed all stanzas sent to it on this
+ xmpp_connection."""
+
+ iq = IQ(None, "get")
+ iq.addElement(('http://jabber.org/protocol/disco#info', 'query'))
+ xmpp_connection.send(iq)
+ q.expect('stream-iq', query_ns='http://jabber.org/protocol/disco#info')
+
+def make_connection(bus, event_func, params=None):
+ default_params = {
+ 'published-name': 'testsuite',
+ 'first-name': 'test',
+ 'last-name': 'suite',
+ 'nickname': re.sub('(.*tests/twisted/|\./)', '', sys.argv[0]),
+ }
+
+ if params:
+ default_params.update(params)
+
+ return servicetest.make_connection(bus, event_func, 'salut',
+ 'local-xmpp', default_params)
+
+def ensure_avahi_is_running():
+ bus = dbus.SystemBus()
+ bus_obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
+ if bus_obj.NameHasOwner('org.freedesktop.Avahi',
+ dbus_interface='org.freedesktop.DBus'):
+ return
+
+ loop = glib.MainLoop()
+ def name_owner_changed_cb(name, old_owner, new_owner):
+ loop.quit()
+
+ noc = bus.add_signal_receiver(name_owner_changed_cb,
+ signal_name='NameOwnerChanged',
+ dbus_interface='org.freedesktop.DBus',
+ arg0='org.freedesktop.Avahi')
+
+ # Cannot use D-Bus activation because we have no way to pass to activated
+ # clients the address of the system bus and we cannot host the service in
+ # this process because we are going to make blocking calls and we would
+ # deadlock.
+ tests_dir = os.path.dirname(__file__)
+ avahimock_path = os.path.join(tests_dir, 'avahimock.py')
+ Popen([avahimock_path])
+
+ loop.run()
+
+ noc.remove()
+
+def exec_test_deferred (fun, params, protocol=None, timeout=None,
+ make_conn=True):
+ colourer = None
+
+ if 'SALUT_TEST_REAL_AVAHI' not in os.environ:
+ ensure_avahi_is_running()
+
+ if sys.stdout.isatty() or 'CHECK_FORCE_COLOR' in os.environ:
+ colourer = servicetest.install_colourer()
+
+ bus = dbus.SessionBus()
+
+ queue = servicetest.IteratingEventQueue(timeout)
+ queue.verbose = (
+ os.environ.get('CHECK_TWISTED_VERBOSE', '') != ''
+ or '-v' in sys.argv)
+
+ if make_conn:
+ try:
+ conn = make_connection(bus, queue.append, params)
+ except Exception, e:
+ # This is normally because the connection's still kicking around
+ # on the bus from a previous test. Let's bail out unceremoniously.
+ print e
+ os._exit(1)
+ else:
+ conn = None
+
+ def signal_receiver(*args, **kw):
+ queue.append(Event('dbus-signal',
+ path=unwrap(kw['path']),
+ signal=kw['member'], args=map(unwrap, args),
+ interface=kw['interface']))
+
+ bus.add_signal_receiver(
+ signal_receiver,
+ None, # signal name
+ None, # interface
+ None,
+ path_keyword='path',
+ member_keyword='member',
+ interface_keyword='interface',
+ byte_arrays=True
+ )
+
+ error = None
+
+ try:
+ fun(queue, bus, conn)
+ except Exception, e:
+ import traceback
+ traceback.print_exc()
+ error = e
+ queue.verbose = False
+
+ if colourer:
+ sys.stdout = colourer.fh
+
+ if bus.name_has_owner(conn.object.bus_name):
+ # Connection hasn't already been disconnected and destroyed
+ try:
+ if conn.GetStatus() == cs.CONN_STATUS_CONNECTED:
+ # Connection is connected, properly disconnect it
+ call_async(queue, conn, 'Disconnect')
+ queue.expect_many(EventPattern('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_DISCONNECTED, cs.CSR_REQUESTED]),
+ EventPattern('dbus-return', method='Disconnect'))
+ else:
+ # Connection is not connected, call Disconnect() to destroy it
+ conn.Disconnect()
+ except dbus.DBusException, e:
+ pass
+
+ try:
+ conn.Disconnect()
+ raise AssertionError("Connection didn't disappear; "
+ "all subsequent tests will probably fail")
+ except dbus.DBusException, e:
+ pass
+ except Exception, e:
+ traceback.print_exc()
+ error = e
+
+ if error is None:
+ reactor.callLater(0, reactor.crash)
+ else:
+ # please ignore the POSIX behind the curtain
+ os._exit(1)
+
+ if 'SALUT_TEST_REFDBG' in os.environ:
+ # we have to wait that Salut timeouts so the process is properly
+ # exited and refdbg can generates its report
+ time.sleep(5.5)
+
+def exec_test(fun, params=None, protocol=None, timeout=None,
+ make_conn=True):
+ reactor.callWhenRunning (exec_test_deferred, fun, params, protocol, timeout,
+ make_conn)
+ reactor.run()
+
+def wait_for_contact_list(q, conn):
+ """Request contact list channels and wait for their NewChannel signals.
+ This is useful to avoid these signals to interfere with your test."""
+
+ #FIXME: this maybe racy if there are other contacts connected
+ requestotron = dbus.Interface(conn, cs.CONN_IFACE_REQUESTS)
+
+ # publish
+ requestotron.EnsureChannel({
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
+ cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
+ cs.TARGET_ID: 'publish'})
+ q.expect('dbus-signal', signal='NewChannel')
+ # subscribe
+ requestotron.EnsureChannel({
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
+ cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
+ cs.TARGET_ID: 'subscribe'})
+ q.expect('dbus-signal', signal='NewChannel')
+
+def wait_for_contact_in_publish(q, bus, conn, contact_name):
+ publish_handle = conn.RequestHandles(cs.HT_LIST, ["publish"])[0]
+ publish = conn.RequestChannel(cs.CHANNEL_TYPE_CONTACT_LIST,
+ cs.HT_LIST, publish_handle, False)
+
+ handle = 0
+ # Wait until the record shows up in publish
+ while handle == 0:
+ e = q.expect('dbus-signal', signal='MembersChangedDetailed',
+ path=publish)
+ # Versions of telepathy-glib prior to 0.14.6 incorrectly used the name
+ # 'member-ids'.
+ try:
+ ids = e.args[4]['contact-ids']
+ except KeyError:
+ ids = e.args[4]['member-ids']
+
+ for h in e.args[0]:
+ name = ids[h]
+ if name == contact_name:
+ handle = h
+
+ return handle
+
+def _elem_add(elem, *children):
+ for child in children:
+ if isinstance(child, domish.Element):
+ elem.addChild(child)
+ elif isinstance(child, unicode):
+ elem.addContent(child)
+ else:
+ raise ValueError(
+ 'invalid child object %r (must be element or unicode)', child)
+
+def elem(a, b=None, attrs={}, **kw):
+ r"""
+ >>> elem('foo')().toXml()
+ u'<foo/>'
+ >>> elem('foo', x='1')().toXml()
+ u"<foo x='1'/>"
+ >>> elem('foo', x='1')(u'hello').toXml()
+ u"<foo x='1'>hello</foo>"
+ >>> elem('foo', x='1')(u'hello',
+ ... elem('http://foo.org', 'bar', y='2')(u'bye')).toXml()
+ u"<foo x='1'>hello<bar xmlns='http://foo.org' y='2'>bye</bar></foo>"
+ >>> elem('foo', attrs={'xmlns:bar': 'urn:bar', 'bar:cake': 'yum'})(
+ ... elem('bar:e')(u'i')
+ ... ).toXml()
+ u"<foo xmlns:bar='urn:bar' bar:cake='yum'><bar:e>i</bar:e></foo>"
+ """
+
+ class _elem(domish.Element):
+ def __call__(self, *children):
+ _elem_add(self, *children)
+ return self
+
+ if b is not None:
+ elem = _elem((a, b))
+ else:
+ elem = _elem((None, a))
+
+ # Can't just update kw into attrs, because that *modifies the parameter's
+ # default*. Thanks python.
+ allattrs = {}
+ allattrs.update(kw)
+ allattrs.update(attrs)
+
+ # First, let's pull namespaces out
+ realattrs = {}
+ for k, v in allattrs.iteritems():
+ if k.startswith('xmlns:'):
+ abbr = k[len('xmlns:'):]
+ elem.localPrefixes[abbr] = v
+ else:
+ realattrs[k] = v
+
+ for k, v in realattrs.iteritems():
+ if k == 'from_':
+ elem['from'] = v
+ else:
+ elem[k] = v
+
+ return elem
+
+def elem_iq(server, type, **kw):
+ class _iq(IQ):
+ def __call__(self, *children):
+ _elem_add(self, *children)
+ return self
+
+ iq = _iq(server, type)
+
+ for k, v in kw.iteritems():
+ if k == 'from_':
+ iq['from'] = v
+ else:
+ iq[k] = v
+
+ return iq
+
+def make_presence(_from, to, type=None, show=None,
+ status=None, caps=None, photo=None):
+ presence = domish.Element((None, 'presence'))
+ presence['from'] = _from
+ presence['to'] = to
+
+ if type is not None:
+ presence['type'] = type
+
+ if show is not None:
+ presence.addElement('show', content=show)
+
+ if status is not None:
+ presence.addElement('status', content=status)
+
+ if caps is not None:
+ cel = presence.addElement(('http://jabber.org/protocol/caps', 'c'))
+ for key,value in caps.items():
+ cel[key] = value
+
+ # <x xmlns="vcard-temp:x:update"><photo>4a1...</photo></x>
+ if photo is not None:
+ x = presence.addElement((ns.VCARD_TEMP_UPDATE, 'x'))
+ x.addElement('photo').addContent(photo)
+
+ return presence
diff --git a/salut/tests/twisted/servicetest.py b/salut/tests/twisted/servicetest.py
new file mode 100644
index 000000000..1771ddaf4
--- /dev/null
+++ b/salut/tests/twisted/servicetest.py
@@ -0,0 +1,631 @@
+
+"""
+Infrastructure code for testing connection managers.
+"""
+
+from twisted.internet import glib2reactor
+from twisted.internet.protocol import Protocol, Factory, ClientFactory
+glib2reactor.install()
+import sys
+import time
+
+import pprint
+import unittest
+
+import dbus.glib
+
+from twisted.internet import reactor
+
+import constants as cs
+
+tp_name_prefix = 'org.freedesktop.Telepathy'
+tp_path_prefix = '/org/freedesktop/Telepathy'
+
+class DictionarySupersetOf (object):
+ """Utility class for expecting "a dictionary with at least these keys"."""
+ def __init__(self, dictionary):
+ self._dictionary = dictionary
+ def __repr__(self):
+ return "DictionarySupersetOf(%s)" % self._dictionary
+ def __eq__(self, other):
+ """would like to just do:
+ return set(other.items()).issuperset(self._dictionary.items())
+ but it turns out that this doesn't work if you have another dict
+ nested in the values of your dicts"""
+ try:
+ for k,v in self._dictionary.items():
+ if k not in other or other[k] != v:
+ return False
+ return True
+ except TypeError: # other is not iterable
+ return False
+
+class Event:
+ def __init__(self, type, **kw):
+ self.__dict__.update(kw)
+ self.type = type
+ (self.subqueue, self.subtype) = type.split ("-", 1)
+
+def format_event(event):
+ ret = ['- type %s' % event.type]
+
+ for key in dir(event):
+ if key != 'type' and not key.startswith('_'):
+ ret.append('- %s: %s' % (
+ key, pprint.pformat(getattr(event, key))))
+
+ if key == 'error':
+ ret.append('%s' % getattr(event, key))
+
+ return ret
+
+class EventPattern:
+ def __init__(self, type, **properties):
+ self.type = type
+ self.predicate = None
+ if 'predicate' in properties:
+ self.predicate = properties['predicate']
+ del properties['predicate']
+ self.properties = properties
+ (self.subqueue, self.subtype) = type.split ("-", 1)
+
+ def __repr__(self):
+ properties = dict(self.properties)
+
+ if self.predicate is not None:
+ properties['predicate'] = self.predicate
+
+ return '%s(%r, **%r)' % (
+ self.__class__.__name__, self.type, properties)
+
+ def match(self, event):
+ if event.type != self.type:
+ return False
+
+ for key, value in self.properties.iteritems():
+ try:
+ if getattr(event, key) != value:
+ return False
+ except AttributeError:
+ return False
+
+ if self.predicate is None or self.predicate(event):
+ return True
+
+ return False
+
+
+class TimeoutError(Exception):
+ pass
+
+class ForbiddenEventOccurred(Exception):
+ def __init__(self, event):
+ Exception.__init__(self)
+ self.event = event
+
+ def __str__(self):
+ return '\n' + '\n'.join(format_event(self.event))
+
+class BaseEventQueue:
+ """Abstract event queue base class.
+
+ Implement the wait() method to have something that works.
+ """
+
+ def __init__(self, timeout=None):
+ self.verbose = False
+ self.forbidden_events = set()
+ self.event_queues = {}
+
+ if timeout is None:
+ self.timeout = 5
+ else:
+ self.timeout = timeout
+
+ def log(self, s):
+ if self.verbose:
+ print s
+
+ def log_queues(self, queues):
+ self.log ("Waiting for event on: %s" % ", ".join(queues))
+
+ def log_event(self, event):
+ self.log('got event:')
+
+ if self.verbose:
+ map(self.log, format_event(event))
+
+ def forbid_events(self, patterns):
+ """
+ Add patterns (an iterable of EventPattern) to the set of forbidden
+ events. If a forbidden event occurs during an expect or expect_many,
+ the test will fail.
+ """
+ self.forbidden_events.update(set(patterns))
+
+ def unforbid_events(self, patterns):
+ """
+ Remove 'patterns' (an iterable of EventPattern) from the set of
+ forbidden events. These must be the same EventPattern pointers that
+ were passed to forbid_events.
+ """
+ self.forbidden_events.difference_update(set(patterns))
+
+ def _check_forbidden(self, event):
+ for e in self.forbidden_events:
+ if e.match(event):
+ raise ForbiddenEventOccurred(event)
+
+ def expect(self, type, **kw):
+ """
+ Waits for an event matching the supplied pattern to occur, and returns
+ it. For example, to await a D-Bus signal with particular arguments:
+
+ e = q.expect('dbus-signal', signal='Badgers', args=["foo", 42])
+ """
+ pattern = EventPattern(type, **kw)
+ t = time.time()
+
+ while True:
+ event = self.wait([pattern.subqueue])
+ self._check_forbidden(event)
+
+ if pattern.match(event):
+ self.log('handled, took %0.3f ms'
+ % ((time.time() - t) * 1000.0) )
+ self.log('')
+ return event
+
+ self.log('not handled')
+ self.log('')
+
+ def expect_many(self, *patterns):
+ """
+ Waits for events matching all of the supplied EventPattern instances to
+ return, and returns a list of events in the same order as the patterns
+ they matched. After a pattern is successfully matched, it is not
+ considered for future events; if more than one unsatisfied pattern
+ matches an event, the first "wins".
+
+ Note that the expected events may occur in any order. If you're
+ expecting a series of events in a particular order, use repeated calls
+ to expect() instead.
+
+ This method is useful when you're awaiting a number of events which may
+ happen in any order. For instance, in telepathy-gabble, calling a D-Bus
+ method often causes a value to be returned immediately, as well as a
+ query to be sent to the server. Since these events may reach the test
+ in either order, the following is incorrect and will fail if the IQ
+ happens to reach the test first:
+
+ ret = q.expect('dbus-return', method='Foo')
+ query = q.expect('stream-iq', query_ns=ns.FOO)
+
+ The following would be correct:
+
+ ret, query = q.expect_many(
+ EventPattern('dbus-return', method='Foo'),
+ EventPattern('stream-iq', query_ns=ns.FOO),
+ )
+ """
+ ret = [None] * len(patterns)
+ t = time.time()
+
+ while None in ret:
+ try:
+ queues = set()
+ for i, pattern in enumerate(patterns):
+ if ret[i] is None:
+ queues.add(pattern.subqueue)
+ event = self.wait(queues)
+ except TimeoutError:
+ self.log('timeout')
+ self.log('still expecting:')
+ for i, pattern in enumerate(patterns):
+ if ret[i] is None:
+ self.log(' - %r' % pattern)
+ raise
+ self._check_forbidden(event)
+
+ for i, pattern in enumerate(patterns):
+ if ret[i] is None and pattern.match(event):
+ self.log('handled, took %0.3f ms'
+ % ((time.time() - t) * 1000.0) )
+ self.log('')
+ ret[i] = event
+ break
+ else:
+ self.log('not handled')
+ self.log('')
+
+ return ret
+
+ def demand(self, type, **kw):
+ pattern = EventPattern(type, **kw)
+
+ event = self.wait([pattern.subqueue])
+
+ if pattern.match(event):
+ self.log('handled')
+ self.log('')
+ return event
+
+ self.log('not handled')
+ raise RuntimeError('expected %r, got %r' % (pattern, event))
+
+ def queues_available(self, queues):
+ if queues == None:
+ return self.event_queues.keys()
+ else:
+ available = self.event_queues.keys()
+ return filter(lambda x: x in available, queues)
+
+
+ def pop_next(self, queue):
+ events = self.event_queues[queue]
+ e = events.pop(0)
+ if not events:
+ self.event_queues.pop (queue)
+ return e
+
+ def append(self, event):
+ self.log ("Adding to queue")
+ self.log_event (event)
+ self.event_queues[event.subqueue] = \
+ self.event_queues.get(event.subqueue, []) + [event]
+
+class IteratingEventQueue(BaseEventQueue):
+ """Event queue that works by iterating the Twisted reactor."""
+
+ def __init__(self, timeout=None):
+ BaseEventQueue.__init__(self, timeout)
+
+ def wait(self, queues=None):
+ stop = [False]
+
+ def later():
+ stop[0] = True
+
+ delayed_call = reactor.callLater(self.timeout, later)
+
+ self.log_queues(queues)
+
+ qa = self.queues_available(queues)
+ while not qa and (not stop[0]):
+ reactor.iterate(0.01)
+ qa = self.queues_available(queues)
+
+ if qa:
+ delayed_call.cancel()
+ e = self.pop_next (qa[0])
+ self.log_event (e)
+ return e
+ else:
+ raise TimeoutError
+
+class TestEventQueue(BaseEventQueue):
+ def __init__(self, events):
+ BaseEventQueue.__init__(self)
+ for e in events:
+ self.append (e)
+
+ def wait(self, queues = None):
+ qa = self.queues_available(queues)
+
+ if qa:
+ return self.pop_next (qa[0])
+ else:
+ raise TimeoutError
+
+class EventQueueTest(unittest.TestCase):
+ def test_expect(self):
+ queue = TestEventQueue([Event('test-foo'), Event('test-bar')])
+ assert queue.expect('test-foo').type == 'test-foo'
+ assert queue.expect('test-bar').type == 'test-bar'
+
+ def test_expect_many(self):
+ queue = TestEventQueue([Event('test-foo'),
+ Event('test-bar')])
+ bar, foo = queue.expect_many(
+ EventPattern('test-bar'),
+ EventPattern('test-foo'))
+ assert bar.type == 'test-bar'
+ assert foo.type == 'test-foo'
+
+ def test_expect_many2(self):
+ # Test that events are only matched against patterns that haven't yet
+ # been matched. This tests a regression.
+ queue = TestEventQueue([Event('test-foo', x=1), Event('test-foo', x=2)])
+ foo1, foo2 = queue.expect_many(
+ EventPattern('test-foo'),
+ EventPattern('test-foo'))
+ assert foo1.type == 'test-foo' and foo1.x == 1
+ assert foo2.type == 'test-foo' and foo2.x == 2
+
+ def test_expect_queueing(self):
+ queue = TestEventQueue([Event('foo-test', x=1),
+ Event('foo-test', x=2)])
+
+ queue.append(Event('bar-test', x=1))
+ queue.append(Event('bar-test', x=2))
+
+ queue.append(Event('baz-test', x=1))
+ queue.append(Event('baz-test', x=2))
+
+ for x in xrange(1,2):
+ e = queue.expect ('baz-test')
+ assertEquals (x, e.x)
+
+ e = queue.expect ('bar-test')
+ assertEquals (x, e.x)
+
+ e = queue.expect ('foo-test')
+ assertEquals (x, e.x)
+
+ def test_timeout(self):
+ queue = TestEventQueue([])
+ self.assertRaises(TimeoutError, queue.expect, 'test-foo')
+
+ def test_demand(self):
+ queue = TestEventQueue([Event('test-foo'), Event('test-bar')])
+ foo = queue.demand('test-foo')
+ assert foo.type == 'test-foo'
+
+ def test_demand_fail(self):
+ queue = TestEventQueue([Event('test-foo'), Event('test-bar')])
+ self.assertRaises(RuntimeError, queue.demand, 'test-bar')
+
+def unwrap(x):
+ """Hack to unwrap D-Bus values, so that they're easier to read when
+ printed."""
+
+ if isinstance(x, list):
+ return map(unwrap, x)
+
+ if isinstance(x, tuple):
+ return tuple(map(unwrap, x))
+
+ if isinstance(x, dict):
+ return dict([(unwrap(k), unwrap(v)) for k, v in x.iteritems()])
+
+ if isinstance(x, dbus.Boolean):
+ return bool(x)
+
+ for t in [unicode, str, long, int, float]:
+ if isinstance(x, t):
+ return t(x)
+
+ return x
+
+def call_async(test, proxy, method, *args, **kw):
+ """Call a D-Bus method asynchronously and generate an event for the
+ resulting method return/error."""
+
+ def reply_func(*ret):
+ test.append(Event('dbus-return', method=method,
+ value=unwrap(ret)))
+
+ def error_func(err):
+ test.append(Event('dbus-error', method=method, error=err,
+ name=err.get_dbus_name(), message=str(err)))
+
+ method_proxy = getattr(proxy, method)
+ kw.update({'reply_handler': reply_func, 'error_handler': error_func})
+ method_proxy(*args, **kw)
+
+def sync_dbus(bus, q, conn):
+ # Dummy D-Bus method call
+ # This won't do the right thing unless the proxy has a unique name.
+ assert conn.object.bus_name.startswith(':')
+ root_object = bus.get_object(conn.object.bus_name, '/')
+ call_async(
+ q, dbus.Interface(root_object, 'org.freedesktop.Telepathy.Tests'), 'DummySyncDBus')
+ q.expect('dbus-error', method='DummySyncDBus')
+
+class ProxyWrapper:
+ def __init__(self, object, default, others):
+ self.object = object
+ self.default_interface = dbus.Interface(object, default)
+ self.Properties = dbus.Interface(object, dbus.PROPERTIES_IFACE)
+ self.TpProperties = \
+ dbus.Interface(object, tp_name_prefix + '.Properties')
+ self.interfaces = dict([
+ (name, dbus.Interface(object, iface))
+ for name, iface in others.iteritems()])
+
+ def __getattr__(self, name):
+ if name in self.interfaces:
+ return self.interfaces[name]
+
+ if name in self.object.__dict__:
+ return getattr(self.object, name)
+
+ return getattr(self.default_interface, name)
+
+def wrap_connection(conn):
+ return ProxyWrapper(conn, tp_name_prefix + '.Connection',
+ dict([
+ (name, tp_name_prefix + '.Connection.Interface.' + name)
+ for name in ['Aliasing', 'Avatars', 'Capabilities', 'Contacts',
+ 'Presence', 'SimplePresence', 'Requests']] +
+ [('Peer', 'org.freedesktop.DBus.Peer'),
+ ('ContactCapabilities', cs.CONN_IFACE_CONTACT_CAPS),
+ ('ContactInfo', cs.CONN_IFACE_CONTACT_INFO),
+ ('Location', cs.CONN_IFACE_LOCATION),
+ ('Future', tp_name_prefix + '.Connection.FUTURE'),
+ ('MailNotification', cs.CONN_IFACE_MAIL_NOTIFICATION),
+ ('ContactList', cs.CONN_IFACE_CONTACT_LIST),
+ ('ContactGroups', cs.CONN_IFACE_CONTACT_GROUPS),
+ ('PowerSaving', cs.CONN_IFACE_POWER_SAVING),
+ ]))
+
+def wrap_channel(chan, type_, extra=None):
+ interfaces = {
+ type_: tp_name_prefix + '.Channel.Type.' + type_,
+ 'Group': tp_name_prefix + '.Channel.Interface.Group',
+ }
+
+ if extra:
+ interfaces.update(dict([
+ (name, tp_name_prefix + '.Channel.Interface.' + name)
+ for name in extra]))
+
+ return ProxyWrapper(chan, tp_name_prefix + '.Channel', interfaces)
+
+def make_connection(bus, event_func, name, proto, params):
+ cm = bus.get_object(
+ tp_name_prefix + '.ConnectionManager.%s' % name,
+ tp_path_prefix + '/ConnectionManager/%s' % name)
+ cm_iface = dbus.Interface(cm, tp_name_prefix + '.ConnectionManager')
+
+ connection_name, connection_path = cm_iface.RequestConnection(
+ proto, params)
+ conn = wrap_connection(bus.get_object(connection_name, connection_path))
+
+ return conn
+
+def make_channel_proxy(conn, path, iface):
+ bus = dbus.SessionBus()
+ chan = bus.get_object(conn.object.bus_name, path)
+ chan = dbus.Interface(chan, tp_name_prefix + '.' + iface)
+ return chan
+
+# block_reading can be used if the test want to choose when we start to read
+# data from the socket.
+class EventProtocol(Protocol):
+ def __init__(self, queue=None, block_reading=False):
+ self.queue = queue
+ self.block_reading = block_reading
+
+ def dataReceived(self, data):
+ if self.queue is not None:
+ self.queue.append(Event('socket-data', protocol=self,
+ data=data))
+
+ def sendData(self, data):
+ self.transport.write(data)
+
+ def connectionMade(self):
+ if self.block_reading:
+ self.transport.stopReading()
+
+ def connectionLost(self, reason=None):
+ if self.queue is not None:
+ self.queue.append(Event('socket-disconnected', protocol=self))
+
+class EventProtocolFactory(Factory):
+ def __init__(self, queue, block_reading=False):
+ self.queue = queue
+ self.block_reading = block_reading
+
+ def _create_protocol(self):
+ return EventProtocol(self.queue, self.block_reading)
+
+ def buildProtocol(self, addr):
+ proto = self._create_protocol()
+ self.queue.append(Event('socket-connected', protocol=proto))
+ return proto
+
+class EventProtocolClientFactory(EventProtocolFactory, ClientFactory):
+ pass
+
+def watch_tube_signals(q, tube):
+ def got_signal_cb(*args, **kwargs):
+ q.append(Event('tube-signal',
+ path=kwargs['path'],
+ signal=kwargs['member'],
+ args=map(unwrap, args),
+ tube=tube))
+
+ tube.add_signal_receiver(got_signal_cb,
+ path_keyword='path', member_keyword='member',
+ byte_arrays=True)
+
+def pretty(x):
+ return pprint.pformat(unwrap(x))
+
+def assertEquals(expected, value):
+ if expected != value:
+ raise AssertionError(
+ "expected:\n%s\ngot:\n%s" % (pretty(expected), pretty(value)))
+
+def assertSameSets(expected, value):
+ exp_set = set(expected)
+ val_set = set(value)
+
+ if exp_set != val_set:
+ raise AssertionError(
+ "expected contents:\n%s\ngot:\n%s" % (
+ pretty(exp_set), pretty(val_set)))
+
+def assertNotEquals(expected, value):
+ if expected == value:
+ raise AssertionError(
+ "expected something other than:\n%s" % pretty(value))
+
+def assertContains(element, value):
+ if element not in value:
+ raise AssertionError(
+ "expected:\n%s\nin:\n%s" % (pretty(element), pretty(value)))
+
+def assertDoesNotContain(element, value):
+ if element in value:
+ raise AssertionError(
+ "expected:\n%s\nnot in:\n%s" % (pretty(element), pretty(value)))
+
+def assertLength(length, value):
+ if len(value) != length:
+ raise AssertionError("expected: length %d, got length %d:\n%s" % (
+ length, len(value), pretty(value)))
+
+def assertFlagsSet(flags, value):
+ masked = value & flags
+ if masked != flags:
+ raise AssertionError(
+ "expected flags %u, of which only %u are set in %u" % (
+ flags, masked, value))
+
+def assertFlagsUnset(flags, value):
+ masked = value & flags
+ if masked != 0:
+ raise AssertionError(
+ "expected none of flags %u, but %u are set in %u" % (
+ flags, masked, value))
+
+def assertDBusError(name, error):
+ if error.get_dbus_name() != name:
+ raise AssertionError(
+ "expected DBus error named:\n %s\ngot:\n %s\n(with message: %s)"
+ % (name, error.get_dbus_name(), error.message))
+
+def install_colourer():
+ def red(s):
+ return '\x1b[31m%s\x1b[0m' % s
+
+ def green(s):
+ return '\x1b[32m%s\x1b[0m' % s
+
+ patterns = {
+ 'handled': green,
+ 'not handled': red,
+ }
+
+ class Colourer:
+ def __init__(self, fh, patterns):
+ self.fh = fh
+ self.patterns = patterns
+
+ def write(self, s):
+ for p, f in self.patterns.items():
+ if s.startswith(p):
+ self.fh.write(f(p) + s[len(p):])
+ return
+
+ self.fh.write(s)
+
+ sys.stdout = Colourer(sys.stdout, patterns)
+ return sys.stdout
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/salut/tests/twisted/sidecars.py b/salut/tests/twisted/sidecars.py
new file mode 100644
index 000000000..26a7b67c8
--- /dev/null
+++ b/salut/tests/twisted/sidecars.py
@@ -0,0 +1,61 @@
+"""
+Test Salut's implementation of sidecars, using the test plugin.
+"""
+
+from servicetest import (
+ call_async, EventPattern, assertEquals
+ )
+from saluttest import exec_test
+import constants as cs
+from config import PLUGINS_ENABLED
+
+TEST_PLUGIN_IFACE = "org.freedesktop.Telepathy.Salut.Plugin.Test"
+
+if not PLUGINS_ENABLED:
+ print "NOTE: built without --enable-plugins, not testing plugins"
+ print " (but still testing failing calls to EnsureSidecar)"
+
+def test(q, bus, conn):
+ # Request a sidecar thate we support before we're connected; it should just
+ # wait around until we're connected.
+ call_async(q, conn.Future, 'EnsureSidecar', TEST_PLUGIN_IFACE)
+
+ conn.Connect()
+
+ if PLUGINS_ENABLED:
+ # Now we're connected, the call we made earlier should return.
+ path, props = q.expect('dbus-return', method='EnsureSidecar').value
+ # This sidecar doesn't even implement get_immutable_properties; it
+ # should just get the empty dict filled in for it.
+ assertEquals({}, props)
+
+ # We should get the same sidecar if we request it again
+ path2, props2 = conn.Future.EnsureSidecar(TEST_PLUGIN_IFACE)
+ assertEquals((path, props), (path2, props2))
+ else:
+ # Only now does it fail.
+ q.expect('dbus-error', method='EnsureSidecar')
+
+ # This is not a valid interface name
+ call_async(q, conn.Future, 'EnsureSidecar', 'not an interface')
+ q.expect('dbus-error', name=cs.INVALID_ARGUMENT)
+
+ # The test plugin makes no reference to this interface.
+ call_async(q, conn.Future, 'EnsureSidecar', 'unsupported.sidecar')
+ q.expect('dbus-error', name=cs.NOT_IMPLEMENTED)
+
+ call_async(q, conn, 'Disconnect')
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_DISCONNECTED, cs.CSR_REQUESTED]),
+ )
+
+ call_async(q, conn.Future, 'EnsureSidecar', 'zomg.what')
+ # With older telepathy-glib this would be DISCONNECTED;
+ # with newer telepathy-glib the Connection disappears from the bus
+ # sooner, and you get UnknownMethod or something from dbus-glib.
+ q.expect('dbus-error')
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/salut/tests/twisted/tools/Makefile.am b/salut/tests/twisted/tools/Makefile.am
new file mode 100644
index 000000000..aaca6af39
--- /dev/null
+++ b/salut/tests/twisted/tools/Makefile.am
@@ -0,0 +1,33 @@
+exec-with-log.sh: exec-with-log.sh.in
+ $(AM_V_GEN)sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
+ @chmod +x $@
+
+%.conf: %.conf.in
+ $(AM_V_GEN)sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
+
+# We don't use the full filename for the .in because > 99 character filenames
+# in tarballs are non-portable (and automake 1.8 doesn't let us build
+# non-archaic tarballs)
+org.freedesktop.Telepathy.ConnectionManager.%.service: %.service.in
+ $(AM_V_GEN)sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
+
+# D-Bus service file for testing
+service_in_files = salut.service.in
+service_files = org.freedesktop.Telepathy.ConnectionManager.salut.service
+
+# D-Bus config file for testing
+conf_in_files = tmp-session-bus.conf.in
+conf_files = $(conf_in_files:.conf.in=.conf)
+
+BUILT_SOURCES = $(service_files) $(conf_files) exec-with-log.sh
+
+EXTRA_DIST = \
+ $(service_in_files) \
+ $(conf_in_files) \
+ exec-with-log.sh.in \
+ with-session-bus.sh \
+ run_and_bt.gdb
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ salut-testing.log
diff --git a/salut/tests/twisted/tools/exec-with-log.sh.in b/salut/tests/twisted/tools/exec-with-log.sh.in
new file mode 100644
index 000000000..87bef0706
--- /dev/null
+++ b/salut/tests/twisted/tools/exec-with-log.sh.in
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+cd "@abs_top_builddir@/tests/twisted/tools"
+
+export SALUT_DEBUG=all GIBBER_DEBUG=all WOCKY_DEBUG=all
+export SALUT_PLUGIN_DIR="@abs_top_builddir@/plugins/.libs"
+export G_SLICE=debug-blocks
+ulimit -c unlimited
+exec >> salut-testing.log 2>&1
+
+if test -n "$SALUT_TEST_VALGRIND"; then
+ export G_DEBUG=gc-friendly
+ export G_SLICE=always-malloc
+ SALUT_WRAPPER="valgrind --leak-check=full --num-callers=20"
+elif test -n "$SALUT_TEST_REFDBG"; then
+ if test -z "$REFDBG_OPTIONS" ; then
+ export REFDBG_OPTIONS="btnum=10"
+ fi
+ if test -z "$SALUT_WRAPPER" ; then
+ SALUT_WRAPPER="refdbg"
+ fi
+elif test -n "$SALUT_TEST_BACKTRACE"; then
+ SALUT_WRAPPER="gdb -q -x run_and_bt.gdb"
+fi
+
+if ! test -n "$SALUT_TEST_REAL_AVAHI"; then
+ # The bus-daemon that is activating us doesn't know it's also the system bus
+ export DBUS_SYSTEM_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS"
+fi
+
+export G_DEBUG=fatal-warnings" ${G_DEBUG}"
+exec @abs_top_builddir@/libtool --mode=execute $SALUT_WRAPPER @abs_top_builddir@/src/telepathy-salut
diff --git a/salut/tests/twisted/tools/run_and_bt.gdb b/salut/tests/twisted/tools/run_and_bt.gdb
new file mode 100644
index 000000000..527fee3e1
--- /dev/null
+++ b/salut/tests/twisted/tools/run_and_bt.gdb
@@ -0,0 +1,3 @@
+run
+bt full
+quit
diff --git a/salut/tests/twisted/tools/salut.service.in b/salut/tests/twisted/tools/salut.service.in
new file mode 100644
index 000000000..93adb71de
--- /dev/null
+++ b/salut/tests/twisted/tools/salut.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.ConnectionManager.salut
+Exec=@abs_top_builddir@/tests/twisted/tools/exec-with-log.sh
diff --git a/salut/tests/twisted/tools/tmp-session-bus.conf.in b/salut/tests/twisted/tools/tmp-session-bus.conf.in
new file mode 100644
index 000000000..84d8d656b
--- /dev/null
+++ b/salut/tests/twisted/tools/tmp-session-bus.conf.in
@@ -0,0 +1,30 @@
+<!-- This configuration file controls the per-user-login-session message bus.
+ Add a session-local.conf and edit that rather than changing this
+ file directly. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <!-- Our well-known bus type, don't change this -->
+ <type>session</type>
+
+ <listen>unix:tmpdir=/tmp</listen>
+
+ <servicedir>@abs_top_builddir@/tests/twisted/tools</servicedir>
+
+ <policy context="default">
+ <!-- Allow everything to be sent -->
+ <allow send_destination="*" eavesdrop="true"/>
+ <!-- Allow everything to be received -->
+ <allow eavesdrop="true"/>
+ <!-- Allow anyone to own anything -->
+ <allow own="*"/>
+ </policy>
+
+ <!-- This is included last so local configuration can override what's
+ in this standard file -->
+
+
+
+
+</busconfig>
diff --git a/salut/tests/twisted/tools/with-session-bus.sh b/salut/tests/twisted/tools/with-session-bus.sh
new file mode 100644
index 000000000..1b8990e61
--- /dev/null
+++ b/salut/tests/twisted/tools/with-session-bus.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+# with-session-bus.sh - run a program with a temporary D-Bus session daemon
+#
+# The canonical location of this program is the telepathy-glib tools/
+# directory, please synchronize any changes with that copy.
+#
+# Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+set -e
+
+me=with-session-bus
+
+dbus_daemon_args="--print-address=5 --print-pid=6 --fork"
+sleep=0
+
+usage ()
+{
+ echo "usage: $me [options] -- program [program_options]" >&2
+ echo "Requires write access to the current directory." >&2
+ echo "" >&2
+ echo "If \$WITH_SESSION_BUS_FORK_DBUS_MONITOR is set, fork dbus-monitor" >&2
+ echo "with the arguments in \$WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT." >&2
+ echo "The output of dbus-monitor is saved in $me-<pid>.dbus-monitor-logs" >&2
+ exit 2
+}
+
+while test "z$1" != "z--"; do
+ case "$1" in
+ --sleep=*)
+ sleep="$1"
+ sleep="${sleep#--sleep=}"
+ shift
+ ;;
+ --session)
+ dbus_daemon_args="$dbus_daemon_args --session"
+ shift
+ ;;
+ --config-file=*)
+ # FIXME: assumes config file doesn't contain any special characters
+ dbus_daemon_args="$dbus_daemon_args $1"
+ shift
+ ;;
+ --also-for-system)
+ with_system_bus=1
+ shift
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+shift
+if test "z$1" = "z"; then usage; fi
+
+exec 5> $me-$$.address
+exec 6> $me-$$.pid
+
+cleanup ()
+{
+ pid=`head -n1 $me-$$.pid`
+ if test -n "$pid" ; then
+ if [ -n "$CHECK_TWISTED_VERBOSE" ] || [ -n "$VERBOSE_TESTS" ]; then
+ echo "Killing temporary bus daemon: $pid" >&2
+ fi
+ kill -INT "$pid"
+ fi
+ rm -f $me-$$.address
+ rm -f $me-$$.pid
+}
+
+trap cleanup INT HUP TERM
+dbus-daemon $dbus_daemon_args
+
+if [ -n "$CHECK_TWISTED_VERBOSE" ] || [ -n "$VERBOSE_TESTS" ]; then
+ { echo -n "Temporary bus daemon is "; cat $me-$$.address; } >&2
+ { echo -n "Temporary bus daemon PID is "; head -n1 $me-$$.pid; } >&2
+fi
+
+e=0
+DBUS_SESSION_BUS_ADDRESS="`cat $me-$$.address`"
+export DBUS_SESSION_BUS_ADDRESS
+
+if [ -n "$with_system_bus" ] ; then
+ DBUS_SYSTEM_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS"
+ export DBUS_SYSTEM_BUS_ADDRESS
+fi
+
+if [ -n "$WITH_SESSION_BUS_FORK_DBUS_MONITOR" ] ; then
+ echo "Forking dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT" >&2
+ dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT \
+ > $me-$$.dbus-monitor-logs 2>&1 &
+fi
+
+if [ -n "$GABBLE_TEST_BUSTLE" ]; then
+ echo "Forking bustle-dbus-monitor" >&2
+ bustle-dbus-monitor > tools/$me-$$.bustle-logs 2>&1 &
+fi
+
+"$@" || e=$?
+
+if test $sleep != 0; then
+ sleep $sleep
+fi
+
+trap - INT HUP TERM
+cleanup
+
+exit $e
diff --git a/salut/tests/twisted/trivialstream.py b/salut/tests/twisted/trivialstream.py
new file mode 100644
index 000000000..ca80b755a
--- /dev/null
+++ b/salut/tests/twisted/trivialstream.py
@@ -0,0 +1,70 @@
+import dbus.glib
+import gobject
+import sys
+import time
+import os
+import socket
+import tempfile
+import random
+import string
+
+class TrivialStream:
+ def __init__(self, socket_address=None):
+ self.socket_address = socket_address
+
+ def read_socket(self, s):
+ try:
+ data = s.recv(1024)
+ if len(data) > 0:
+ print "received:", data
+ except socket.error, e:
+ pass
+ return True
+
+ def write_socket(self, s, msg):
+ print "send:", msg
+ try:
+ s = s.send(msg)
+ except socket.error, e:
+ pass
+ return True
+
+class TrivialStreamServer(TrivialStream):
+ def __init__(self):
+ TrivialStream.__init__(self)
+
+ def run(self):
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.setblocking(1)
+ s.settimeout(0.1)
+ s.bind(("127.0.0.1", 0))
+
+ self.socket_address = s.getsockname()
+ print "Trivial Server lauched on socket", self.socket_address
+ s.listen(1)
+
+ gobject.timeout_add(1000, self.accept_client, s)
+
+ def accept_client(self, s):
+ try:
+ s2, addr = s.accept()
+ s2.setblocking(1)
+ s2.setblocking(0.1)
+ self.handle_client(s2)
+ return True
+ except socket.timeout:
+ return True
+
+ def handle_client(self, s):
+ gobject.timeout_add(5000, self.write_socket, s, "hi !")
+
+class TrivialStreamClient(TrivialStream):
+ def __init__(self, socket_address):
+ TrivialStream.__init__(self, socket_address)
+
+ def connect(self):
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(self.socket_address)
+ print "Trivial client connected to", self.socket_address
+ gobject.timeout_add(1000, self.read_socket, s)
+
diff --git a/salut/tests/twisted/xmppstream.py b/salut/tests/twisted/xmppstream.py
new file mode 100644
index 000000000..63c55bcb5
--- /dev/null
+++ b/salut/tests/twisted/xmppstream.py
@@ -0,0 +1,257 @@
+
+"""
+Infrastructure code for testing. Implements incoming and outgoing xml/xmpp
+streams
+"""
+
+import servicetest
+from servicetest import Event, EventPattern
+import twisted
+from twisted.words.xish import domish, xpath, xmlstream
+from twisted.internet.protocol import Factory, ClientFactory
+from twisted.internet import reactor
+
+from ipv6 import listenTCP6, connectTCP6
+
+NS_STREAMS = 'http://etherx.jabber.org/streams'
+
+def make_stream_event(type, stanza):
+ event = servicetest.Event(type, stanza=stanza)
+ if stanza.hasAttribute("to"):
+ event.to = stanza.getAttribute("to")
+ else:
+ event.to = None
+
+ if stanza.hasAttribute("from"):
+ event.from_ = stanza.getAttribute("from")
+ else:
+ event.from_ = None
+
+ event.name = event.to
+ event.remote_name = event.from_
+
+ return event
+
+def make_iq_event(iq):
+ event = make_stream_event('stream-iq', iq)
+ event.iq_type = iq.getAttribute("type")
+ event.iq_id = iq.getAttribute("id")
+ query = iq.firstChildElement()
+
+ if query:
+ event.query = query
+ event.query_ns = query.uri
+ event.query_name = query.name
+
+ if query.getAttribute("node"):
+ event.query_node = query.getAttribute("node")
+ else:
+ event.query = None
+
+ return event
+
+def make_presence_event(stanza):
+ event = make_stream_event('stream-presence', stanza)
+ event.presence_type = stanza.getAttribute('type')
+
+ statuses = xpath.queryForNodes('/presence/status', stanza)
+
+ if statuses:
+ event.presence_status = str(statuses[0])
+
+ return event
+
+def make_message_event(stanza):
+ event = make_stream_event('stream-message', stanza)
+ event.message_type = stanza.getAttribute('type')
+ return event
+
+class BaseXmlStream(xmlstream.XmlStream):
+ prefixes = { NS_STREAMS: 'stream' }
+ version = "1.0"
+ namespace = 'jabber:client'
+
+ def __init__(self, event_function, name = None, remote_name = None):
+ xmlstream.XmlStream.__init__(self)
+
+ self.name = name
+ self.remote_name = remote_name
+ self.event_func = event_function
+
+ self.event_function = event_function
+ self.addObserver(xmlstream.STREAM_START_EVENT,
+ lambda *args: self.event(Event('stream-opened')))
+ self.addObserver('//features', lambda x: self.event(
+ make_stream_event('stream-features', x)))
+ self.addObserver('//iq', lambda x: self.event(
+ make_iq_event(x)))
+ self.addObserver('//message', lambda x: self.event(
+ make_message_event(x)))
+ self.addObserver('//presence', lambda x: self.event(
+ make_presence_event(x)))
+
+ def send_header(self):
+ root = domish.Element((NS_STREAMS, 'stream'), 'jabber:client')
+ if self.name is not None:
+ root['from'] = self.name
+ if self.remote_name is not None:
+ root['to'] = self.remote_name
+ root['version'] = self.version
+ self.send(root.toXml(closeElement = 0, prefixes=self.prefixes))
+
+ def event(self, e):
+ e.connection = self
+ self.event_function(e)
+
+ def send(self, obj):
+ if domish.IElement.providedBy(obj):
+ if self.name != None:
+ obj["from"] = self.name
+ if self.remote_name != None:
+ obj["to"] = self.remote_name
+ obj = obj.toXml(prefixes=self.prefixes)
+
+ xmlstream.XmlStream.send(self, obj)
+
+
+class IncomingXmppStream(BaseXmlStream):
+ def __init__(self, event_func, name):
+ BaseXmlStream.__init__(self, event_func, name, None)
+
+ def onDocumentStart(self, rootElement):
+ # Use the fact that it's always salut that connects, so it sends a
+ # proper opening
+ assert rootElement.name == "stream"
+ assert rootElement.uri == NS_STREAMS
+
+ assert rootElement.hasAttribute("from")
+ assert rootElement.hasAttribute("to")
+ if self.name is not None:
+ assert rootElement["to"] == self.name, self.name
+
+ assert rootElement.hasAttribute("version")
+ assert rootElement["version"] == "1.0"
+
+ self.remote_name = rootElement["from"]
+ self.send_header()
+ self.send_features()
+ BaseXmlStream.onDocumentStart(self, rootElement)
+
+ def send_features(self):
+ features = domish.Element((NS_STREAMS, 'features'))
+ self.send(features)
+
+class IncomingXmppFactory(Factory):
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.factory = self
+ e = Event('incoming-connection', listener = self)
+ p.event(e)
+ return p
+
+def setup_stream_listener(queue, name, port = 0, protocol = None):
+ if protocol == None:
+ protocol = IncomingXmppStream
+
+ factory = IncomingXmppFactory()
+ factory.protocol = lambda *args: protocol(queue.append, name)
+ port = reactor.listenTCP(port, factory)
+
+ return (factory, port.getHost().port)
+
+def setup_stream_listener6(queue, name, port = 0, protocol = None):
+ if protocol == None:
+ protocol = IncomingXmppStream
+
+ factory = IncomingXmppFactory()
+ factory.protocol = lambda *args: protocol(queue.append, name)
+ port = listenTCP6(port, factory)
+
+ return (factory, port.getHost().port)
+
+class OutgoingXmppStream(BaseXmlStream):
+ def __init__(self, event_function, name, remote_name):
+ BaseXmlStream.__init__(self, event_function, name, remote_name)
+ self.addObserver(xmlstream.STREAM_CONNECTED_EVENT, self.connected)
+
+ def connected (self, stream):
+ e = Event('connection-result', succeeded = True)
+ self.event(e)
+
+ self.send_header()
+
+class OutgoingXmppiChatStream(OutgoingXmppStream):
+ def __init__(self, event_function, name, remote_name):
+ # set name and remote_name as None as iChat doesn't send 'to' and
+ # 'from' attributes.
+ OutgoingXmppStream.__init__(self, event_function, None, None)
+
+class IncomingXmppiChatStream(IncomingXmppStream):
+ def __init__(self, event_func, name):
+ # set name to None as iChat doesn't send 'from' attribute.
+ IncomingXmppStream.__init__(self, event_func, None)
+
+class OutgoingXmppFactory(ClientFactory):
+ def __init__(self, event_function):
+ self.event_func = event_function
+
+ def clientConnectionFailed(self, connector, reason):
+ ClientFactory.clientConnectionFailed(self, connector, reason)
+ e = Event('connection-result', succeeded = False, reason = reason)
+ self.event_func(e)
+
+def connect_to_stream(queue, name, remote_name, host, port, protocol = None):
+ if protocol == None:
+ protocol = OutgoingXmppStream
+
+ p = protocol(queue.append, name, remote_name)
+
+ factory = OutgoingXmppFactory(queue.append)
+ factory.protocol = lambda *args: p
+ reactor.connectTCP(host, port, factory)
+
+ return p
+
+def connect_to_stream6(queue, name, remote_name, host, port, protocol = None):
+ if protocol == None:
+ protocol = OutgoingXmppStream
+
+ p = protocol(queue.append, name, remote_name)
+
+ factory = OutgoingXmppFactory(queue.append)
+ factory.protocol = lambda *args: p
+ connectTCP6(reactor, host, port, factory)
+
+ return p
+
+if __name__ == '__main__':
+ def run_test():
+ q = servicetest.IteratingEventQueue()
+ # Set verboseness if needed for debugging
+ #q.verbose = True
+
+ (listener, port) = setup_stream_listener(q, "incoming")
+ outbound = connect_to_stream(q, "outgoing",
+ "incoming", "localhost", port)
+
+ inbound = q.expect('incoming-connection',
+ listener = listener).connection
+
+ # inbound stream is opened first, then outbounds stream is opened and
+ # receive features
+ q.expect('stream-opened', connection = inbound)
+ q.expect('stream-opened', connection = outbound)
+ q.expect('stream-features', connection = outbound)
+
+
+ message = domish.Element(('','message'))
+ message.addElement('body', content="test123")
+ outbound.send(message)
+
+ e = q.expect('stream-message', connection=inbound)
+
+ # twisting twisted
+ reactor.stop()
+
+ reactor.callLater(0.1, run_test)
+ reactor.run()
diff --git a/salut/tests/valgrind.supp b/salut/tests/valgrind.supp
new file mode 100644
index 000000000..29bb04547
--- /dev/null
+++ b/salut/tests/valgrind.supp
@@ -0,0 +1,711 @@
+### this file contains suppressions for valgrind when running
+### the gibber/telepathy-salut unit tests based on the gstreamer one
+
+### syscall suppressions
+
+{
+ <clone on Wim's Debian>
+ Memcheck:Param
+ clone(parent_tidptr)
+ fun:clone
+ fun:clone
+}
+
+{
+ <clone on Wim's Debian>
+ Memcheck:Param
+ clone(child_tidptr)
+ fun:clone
+ fun:clone
+}
+
+{
+ <clone on Wim's Debian>
+ Memcheck:Param
+ clone(tlsinfo)
+ fun:clone
+ fun:clone
+}
+
+### glibc suppressions
+
+{
+ <conditional jump on wim's debian 2/2/06>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.*.so
+ fun:dl_open_worker
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ fun:dlopen_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+# glibc does not deallocate thread-local storage
+
+{
+ <tls>
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_allocate_tls
+ fun:pthread_create@@*
+}
+
+# I get an extra stack entry on x86/dapper
+{
+ <tls>
+ Memcheck:Leak
+ fun:calloc
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_allocate_tls
+ fun:pthread_create@@*
+}
+
+
+{
+ <pthread strstr>
+ Memcheck:Cond
+ fun:strstr
+ fun:__pthread_initialize_minimal
+ obj:/lib/libpthread-*.so
+ obj:/lib/libpthread-*.so
+ fun:call_init
+ fun:_dl_init
+ obj:/lib/ld-*.so
+}
+
+# a thread-related free problem in glibc from Edgard
+{
+ __libc_freeres_rw_acess
+ Memcheck:Addr4
+ obj:*
+ obj:*
+ obj:*
+ obj:*
+ obj:*
+ fun:__libc_freeres
+}
+
+{
+ <a conditional jump on wim's debian>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+}
+
+# g_module_open-related problems
+{
+ <started showing up on fc4-quick>
+ Memcheck:Addr2
+ fun:memcpy
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <started showing up on fc4-quick>
+ Memcheck:Addr4
+ fun:memcpy
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <g_module_open on wim's debian>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:do_sym
+ fun:_dl_sym
+ fun:dlsym_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+ fun:dlsym
+ fun:g_module_symbol
+ fun:g_module_open
+}
+
+{
+ <g_module_open on wim's debian>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:dl_open_worker
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ fun:dlopen_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+{
+ <g_module_open on wim's debian>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.*.so
+ fun:dl_open_worker
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ fun:dlopen_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <leak on wim's debian in g_module_open>
+ Memcheck:Leak
+ fun:malloc
+ obj:/lib/ld-2.3.*.so
+ fun:dl_open_worker
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ fun:dlopen_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <invalid read on wim's debian>
+ Memcheck:Addr4
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:dl_open_worker
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ fun:dlopen_doit
+ obj:/lib/ld-2.3.*.so
+}
+
+{
+ <invalid read on wim's debian>
+ Memcheck:Addr4
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:dl_open_worker
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ fun:dlopen_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+}
+
+{
+ <invalid read on wim's debian - 2006-02-02>
+ Memcheck:Addr4
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:dl_open_worker
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ fun:dlopen_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <invalid read on wim's debian - 2006-02-02>
+ Memcheck:Addr4
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:dl_open_worker
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ fun:dlopen_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <invalid read on wim's debian - 2006-02-02>
+ Memcheck:Addr4
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:do_sym
+ fun:_dl_sym
+ fun:dlsym_doit
+ obj:/lib/ld-2.3.*.so
+ fun:_dlerror_run
+ fun:dlsym
+ fun:g_module_symbol
+ fun:g_module_open
+}
+
+{
+ <futex on Andy's 64-bit ubuntu>
+ Memcheck:Param
+ futex(uaddr2)
+ fun:pthread_once
+ obj:/lib/libc-2.3.*.so
+ obj:/lib/libc-2.3.*.so
+ fun:mbsnrtowcs
+ fun:vfprintf
+ fun:vsprintf
+ fun:sprintf
+ obj:/lib/libc-2.3.*.so
+}
+
+# valgrind doesn't allow me to specify a suppression for Addr1, Addr2, Addr4
+# as Addr*, so 3 copies for that; and then 2 of each for that pesky memcpy
+{
+ <Invalid read of size 1, 2, 4 on thomas's FC4>
+ Memcheck:Addr1
+ fun:_dl_signal_error
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <Invalid read of size 1, 2, 4 on thomas's FC4>
+ Memcheck:Addr2
+ fun:_dl_signal_error
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+{
+ <Invalid read of size 1, 2, 4 on thomas's FC4>
+ Memcheck:Addr4
+ fun:_dl_signal_error
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <Invalid read of size 1, 2, 4 on thomas's FC4>
+ Memcheck:Addr1
+ fun:memcpy
+ fun:_dl_signal_error
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <Invalid read of size 1, 2, 4 on thomas's FC4>
+ Memcheck:Addr2
+ fun:memcpy
+ fun:_dl_signal_error
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+{
+ <Invalid read of size 1, 2, 4 on thomas's FC4>
+ Memcheck:Addr4
+ fun:memcpy
+ fun:_dl_signal_error
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+}
+
+{
+ <Addr8 on Andy's AMD64 ubuntu in dl_open>
+ Memcheck:Addr8
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/libc-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ obj:/lib/libdl-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+}
+
+{
+ <Conditional jump on Andy's AMD64 ubuntu>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/libc-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ fun:_dl_open
+ obj:/lib/libdl-2.3.*.so
+ obj:/lib/ld-2.3.*.so
+ obj:/lib/libdl-2.3.*.so
+ fun:dlopen
+ fun:g_module_open
+}
+
+{
+ <Mike's x86 dapper>
+ Memcheck:Addr4
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/tls/i686/cmov/libc-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ fun:_dl_open
+ obj:/lib/tls/i686/cmov/libdl-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/tls/i686/cmov/libdl-2.3.6.so
+ fun:dlopen
+}
+
+{
+ <Mike's x86 dapper>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/tls/i686/cmov/libc-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ fun:_dl_open
+ obj:/lib/tls/i686/cmov/libdl-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/tls/i686/cmov/libdl-2.3.6.so
+ fun:dlopen
+}
+
+{
+ <Another dapper one>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/tls/i686/cmov/libc-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ fun:_dl_open
+ obj:/lib/tls/i686/cmov/libdl-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/tls/i686/cmov/libdl-2.3.6.so
+ fun:dlopen
+}
+
+### glib suppressions
+{
+ <g_parse_debug_string>
+ Memcheck:Cond
+ fun:g_parse_debug_string
+ obj:/usr/lib*/libglib-2.0.so.*
+ fun:g_slice_alloc
+ fun:g_slice_alloc0
+}
+
+{
+ <g_type_init malloc>
+ Memcheck:Leak
+ fun:malloc
+ fun:g_malloc
+ fun:g_strdup
+ fun:g_quark_from_string
+ obj:*
+ obj:*
+ fun:g_type_register_fundamental
+ obj:*
+ fun:g_type_init_with_debug_flags
+ fun:g_type_init
+}
+
+{
+ <g_type_init calloc>
+ Memcheck:Leak
+ fun:calloc
+ fun:g_malloc0
+ obj:*
+ obj:*
+ fun:g_type_register_fundamental
+}
+
+{
+ <g_type_init calloc 2>
+ Memcheck:Leak
+ fun:calloc
+ fun:g_malloc0
+ obj:*
+ obj:*
+ fun:g_type_init_with_debug_flags
+}
+
+{
+ <g_type_init calloc 3, GSlice version>
+ Memcheck:Leak
+ fun:calloc
+ fun:g_malloc0
+ fun:g_slice_alloc
+ obj:*
+ obj:*
+ fun:g_type_init_with_debug_flags
+}
+
+#pthread memleaks
+
+{
+ Thread creation leak
+ Memcheck:Leak
+ fun:calloc
+ fun:allocate_dtv
+ fun:_dl_allocate*
+ fun:_dl_allocate*
+ fun:__pthread_initialize_minimal
+}
+
+{
+ Thread management leak
+ Memcheck:Leak
+ fun:calloc
+ fun:allocate_dtv
+ fun:_dl_allocate*
+ fun:_dl_allocate*
+ fun:__pthread_*
+}
+
+{
+ Thread management leak 2
+ Memcheck:Leak
+ fun:memalign
+ fun:_dl_allocate*
+ fun:_dl_allocate*
+ fun:__pthread_*
+}
+
+{
+ pthread_create Syscall param write(buf) points to uninitialised byte(s)
+ Memcheck:Param
+ write(buf)
+ fun:pthread_create@@GLIBC_2.2.5
+ fun:g_thread_create*
+
+}
+
+# nss_parse_* memleak (used by g_option_context_parse)
+{
+ nss_parse_* memleak
+ Memcheck:Leak
+ fun:malloc
+ fun:nss_parse_service_list
+ fun:__nss_database_lookup
+}
+
+{
+ <annoying read error inside dlopen stuff on Ubuntu Dapper x86_64>
+ Memcheck:Addr8
+ obj:/lib/ld-2.3.6.so
+}
+
+{
+ <Ubuntu Dapper x86_64>
+ Memcheck:Param
+ futex(uaddr2)
+ fun:pthread_once
+ obj:/lib/libc-2.3.6.so
+ obj:/lib/libc-2.3.6.so
+ fun:setlocale
+ fun:init_pre
+ fun:g_option_context_parse
+}
+
+{
+ <Ubuntu Dapper x86_64 dlopen stuff again>
+ Memcheck:Cond
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ fun:_dl_open
+ obj:/lib/libdl-2.3.6.so
+ obj:/lib/ld-2.3.6.so
+ obj:/lib/libdl-2.3.6.so
+ fun:dlopen
+ fun:g_module_open
+}
+# this exists in a bunch of different variations, hence the short tail/trace
+{
+ <dlopen invalid read of size 4 suppression on tpm's Ubuntu edgy/x86>
+ Memcheck:Addr4
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+}
+{
+ <and the same for 64bit systems>
+ Memcheck:Addr8
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+}
+
+# More edgy suppressions (Mike)
+{
+ <dlopen Condition jump suppressions for Ubuntu Edgy/x86>
+ Memcheck:Cond
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ fun:dlopen_doit
+ obj:/lib/ld-2.4.so
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+}
+
+{
+ <dlopen Condition jump suppressions for Ubuntu Edgy/x86>
+ Memcheck:Cond
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ fun:dlopen_doit
+ obj:/lib/ld-2.4.so
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+}
+
+{
+ <dlopen Condition jump suppressions for Ubuntu Edgy/x86>
+ Memcheck:Cond
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ fun:do_sym
+ fun:_dl_sym
+}
+
+# This one's overly general, but there's zero other information in the stack
+# trace - just these five lines!
+{
+ <dlopen Condition jump suppressions for Ubuntu Edgy/x86>
+ Memcheck:Cond
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+}
+
+{
+ <tls leaks on Edgy/x86>
+ Memcheck:Leak
+ fun:calloc
+ obj:/lib/ld-2.4.so
+ fun:_dl_allocate_tls
+ fun:pthread_create@@GLIBC_2.1
+}
+
+{
+ <libcdio 0.76 leak>
+ Memcheck:Leak
+ fun:calloc
+ obj:/usr/lib/libcdio.so.6.0.1
+ fun:cdio_open_am_linux
+ obj:/usr/lib/libcdio.so.6.0.1
+ fun:cdio_open_am
+}
+
+# TLS leaks for feisty/x86
+{
+ <tls leaks on Feisty/x86>
+ Memcheck:Leak
+ fun:calloc
+ fun:allocate_dtv
+ fun:_dl_allocate_tls
+ fun:pthread_create@@GLIBC_2.1
+}
+
+{
+ <Addr8 on Jan's AMD64 ubuntu Feisty in dl_open>
+ Memcheck:Addr8
+ obj:/lib/ld-2.5.so
+}
+
+{
+ <GLib caching the home dir>
+ Memcheck:Leak
+ fun:malloc
+ obj:/lib/libc-*.so
+ fun:__nss_database_lookup
+ obj:*
+ obj:*
+ fun:getpwnam_r
+ fun:g_get_any_init_do
+ fun:g_get_home_dir
+}
+{
+ <GLib caching the user name>
+ Memcheck:Leak
+ fun:malloc
+ obj:/lib/libc-*.so
+ fun:__nss_database_lookup
+ obj:*
+ obj:*
+ fun:getpwnam_r
+ fun:g_get_any_init_do
+ fun:g_get_user_name
+}
diff --git a/salut/tools/Makefile.am b/salut/tools/Makefile.am
new file mode 100644
index 000000000..6292143b7
--- /dev/null
+++ b/salut/tools/Makefile.am
@@ -0,0 +1,51 @@
+EXTRA_DIST = \
+ c-constants-generator.xsl \
+ c-interfaces-generator.xsl \
+ check-coding-style.mk \
+ check-c-style.sh \
+ check-misc.sh \
+ check-whitespace.sh \
+ doc-generator.xsl \
+ glib-client-marshaller-gen.py \
+ glib-errors-enum-body.xsl \
+ glib-errors-enum-header.xsl \
+ glib-interfaces-generator.xsl \
+ glib-interfaces-body-generator.xsl \
+ glib-ginterface-gen.py \
+ glib-gtypes-generator.py \
+ glib-signals-marshal-gen.py \
+ identity.xsl \
+ libglibcodegen.py \
+ make-release-mail.py \
+ telepathy.am \
+ xep.xsl
+
+glib-client-marshaller-gen.py: libglibcodegen.py
+ @touch $@
+glib-ginterface-gen.py: libglibcodegen.py
+ @touch $@
+glib-gtypes-generator.py: libglibcodegen.py
+ @touch $@
+glib-signals-marshal-gen.py: libglibcodegen.py
+ @touch $@
+
+glib-interfaces-generator.xsl: c-interfaces-generator.xsl
+ @touch $@
+glib-interfaces-body-generator.xsl: c-interfaces-generator.xsl
+ @touch $@
+
+maintainer-update-from-xmpp.org:
+ set -e; \
+ uri=svn://svn.xmpp.org:7938/xmpp/trunk/extensions/xep.xsl; \
+ svn info $$uri; \
+ svn cat $$uri > xep.xsl.tmp
+ mv xep.xsl.tmp xep.xsl
+
+TELEPATHY_GLIB_SRCDIR = $(top_srcdir)/../telepathy-glib
+maintainer-update-from-telepathy-glib:
+ set -e && cd $(srcdir) && \
+ for x in $(EXTRA_DIST); do \
+ if test -f $(TELEPATHY_GLIB_SRCDIR)/tools/$$x; then \
+ cp $(TELEPATHY_GLIB_SRCDIR)/tools/$$x $$x; \
+ fi; \
+ done
diff --git a/salut/tools/c-constants-generator.xsl b/salut/tools/c-constants-generator.xsl
new file mode 100644
index 000000000..18b2e495d
--- /dev/null
+++ b/salut/tools/c-constants-generator.xsl
@@ -0,0 +1,299 @@
+<!-- Stylesheet to extract C enumerations from the Telepathy spec.
+The master copy of this stylesheet is in telepathy-glib - please make any
+changes there.
+
+Copyright (C) 2006, 2007 Collabora Limited
+
+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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ exclude-result-prefixes="tp">
+
+ <xsl:output method="text" indent="no" encoding="ascii"/>
+
+ <xsl:param name="mixed-case-prefix" select="''"/>
+
+ <xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
+ <xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
+
+ <xsl:variable name="upper-case-prefix" select="concat(translate($mixed-case-prefix, $lower, $upper), '_')"/>
+ <xsl:variable name="lower-case-prefix" select="concat(translate($mixed-case-prefix, $upper, $lower), '_')"/>
+
+
+ <xsl:template match="tp:flags">
+ <xsl:variable name="name">
+ <xsl:choose>
+ <xsl:when test="@plural">
+ <xsl:value-of select="@plural"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="value-prefix">
+ <xsl:choose>
+ <xsl:when test="@singular">
+ <xsl:value-of select="@singular"/>
+ </xsl:when>
+ <xsl:when test="@value-prefix">
+ <xsl:value-of select="@value-prefix"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:text>/**&#10;</xsl:text>
+ <xsl:text> *&#10;</xsl:text>
+ <xsl:value-of select="translate(concat($mixed-case-prefix, $name), '_', '')"/>
+ <xsl:text>:&#10;</xsl:text>
+ <xsl:apply-templates mode="flag-or-enumvalue-gtkdoc">
+ <xsl:with-param name="value-prefix" select="$value-prefix"/>
+ </xsl:apply-templates>
+ <xsl:text> *&#10;</xsl:text>
+ <xsl:if test="tp:docstring">
+ <xsl:text> * &lt;![CDATA[</xsl:text>
+ <xsl:value-of select="translate(string (tp:docstring), '&#13;&#10;', ' ')"/>
+ <xsl:text>]]&gt;&#10;</xsl:text>
+ <xsl:text> *&#10;</xsl:text>
+ </xsl:if>
+ <xsl:text> * Bitfield/set of flags generated from the Telepathy specification.&#10;</xsl:text>
+ <xsl:text> */&#10;</xsl:text>
+ <xsl:text>typedef enum {&#10;</xsl:text>
+ <xsl:apply-templates>
+ <xsl:with-param name="value-prefix" select="$value-prefix"/>
+ </xsl:apply-templates>
+ <xsl:text>} </xsl:text>
+ <xsl:value-of select="translate(concat($mixed-case-prefix, $name), '_', '')"/>
+ <xsl:text>;&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="text()" mode="flag-or-enumvalue-gtkdoc"/>
+
+ <xsl:template match="tp:enumvalue" mode="flag-or-enumvalue-gtkdoc">
+ <xsl:param name="value-prefix"/>
+ <xsl:text> * @</xsl:text>
+ <xsl:value-of select="translate(concat($upper-case-prefix, $value-prefix, '_', @suffix), $lower, $upper)"/>
+ <xsl:text>: &lt;![CDATA[</xsl:text>
+ <xsl:value-of select="translate(string(tp:docstring), '&#13;&#10;', ' ')"/>
+ <xsl:text>]]&gt;&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="tp:flag" mode="flag-or-enumvalue-gtkdoc">
+ <xsl:param name="value-prefix"/>
+ <xsl:text> * @</xsl:text>
+ <xsl:value-of select="translate(concat($upper-case-prefix, $value-prefix, '_', @suffix), $lower, $upper)"/>
+ <xsl:text>: &lt;![CDATA[</xsl:text>
+ <xsl:value-of select="translate(string(tp:docstring), '&#13;&#10;', ' ')"/>
+ <xsl:text>]]&gt;&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="tp:enum">
+ <xsl:variable name="name">
+ <xsl:choose>
+ <xsl:when test="@singular">
+ <xsl:value-of select="@singular"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="value-prefix">
+ <xsl:choose>
+ <xsl:when test="@singular">
+ <xsl:value-of select="@singular"/>
+ </xsl:when>
+ <xsl:when test="@value-prefix">
+ <xsl:value-of select="@value-prefix"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="name-plural">
+ <xsl:choose>
+ <xsl:when test="@plural">
+ <xsl:value-of select="@plural"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/><xsl:text>s</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:text>/**&#10;</xsl:text>
+ <xsl:text> *&#10;</xsl:text>
+ <xsl:value-of select="translate(concat($mixed-case-prefix, $name), '_', '')"/>
+ <xsl:text>:&#10;</xsl:text>
+ <xsl:apply-templates mode="flag-or-enumvalue-gtkdoc">
+ <xsl:with-param name="value-prefix" select="$value-prefix"/>
+ </xsl:apply-templates>
+ <xsl:text> *&#10;</xsl:text>
+ <xsl:if test="tp:docstring">
+ <xsl:text> * &lt;![CDATA[</xsl:text>
+ <xsl:value-of select="translate(string (tp:docstring), '&#13;&#10;', ' ')"/>
+ <xsl:text>]]&gt;&#10;</xsl:text>
+ <xsl:text> *&#10;</xsl:text>
+ </xsl:if>
+ <xsl:text> * Bitfield/set of flags generated from the Telepathy specification.&#10;</xsl:text>
+ <xsl:text> */&#10;</xsl:text>
+ <xsl:text>typedef enum {&#10;</xsl:text>
+ <xsl:apply-templates>
+ <xsl:with-param name="value-prefix" select="$value-prefix"/>
+ </xsl:apply-templates>
+ <xsl:text>} </xsl:text>
+ <xsl:value-of select="translate(concat($mixed-case-prefix, $name), '_', '')"/>
+ <xsl:text>;&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:text>/**&#10;</xsl:text>
+ <xsl:text> * NUM_</xsl:text>
+ <xsl:value-of select="translate(concat($upper-case-prefix, $name-plural), $lower, $upper)"/>
+ <xsl:text>:&#10;</xsl:text>
+ <xsl:text> *&#10;</xsl:text>
+ <xsl:text> * 1 higher than the highest valid value of #</xsl:text>
+ <xsl:value-of select="translate(concat($mixed-case-prefix, $name), '_', '')"/>
+ <xsl:text>.&#10;</xsl:text>
+ <xsl:text> */&#10;</xsl:text>
+ <xsl:text>#define NUM_</xsl:text>
+ <xsl:value-of select="translate(concat($upper-case-prefix, $name-plural), $lower, $upper)"/>
+ <xsl:text> (</xsl:text>
+ <xsl:value-of select="tp:enumvalue[position() = last()]/@value"/>
+ <xsl:text>+1)&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="tp:flags/tp:flag">
+ <xsl:param name="value-prefix"/>
+ <xsl:variable name="suffix">
+ <xsl:choose>
+ <xsl:when test="@suffix">
+ <xsl:value-of select="@suffix"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="name" select="translate(concat($upper-case-prefix, $value-prefix, '_', $suffix), $lower, $upper)"/>
+
+ <xsl:if test="@name and @suffix and @name != @suffix">
+ <xsl:message terminate="yes">
+ <xsl:text>Flag name </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> != suffix </xsl:text>
+ <xsl:value-of select="@suffix"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> = </xsl:text>
+ <xsl:value-of select="@value"/>
+ <xsl:text>,&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="tp:enum/tp:enumvalue">
+ <xsl:param name="value-prefix"/>
+ <xsl:variable name="suffix">
+ <xsl:choose>
+ <xsl:when test="@suffix">
+ <xsl:value-of select="@suffix"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="name" select="translate(concat($upper-case-prefix, $value-prefix, '_', $suffix), $lower, $upper)"/>
+
+ <xsl:if test="@name and @suffix and @name != @suffix">
+ <xsl:message terminate="yes">
+ <xsl:text>Enumvalue name </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> != suffix </xsl:text>
+ <xsl:value-of select="@suffix"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:if test="preceding-sibling::tp:enumvalue and number(preceding-sibling::tp:enumvalue[1]/@value) > number(@value)">
+ <xsl:message terminate="yes">
+ <xsl:text>Enum values must be in ascending numeric order, but </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> is less than the previous value</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> = </xsl:text>
+ <xsl:value-of select="@value"/>
+ <xsl:text>,&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="tp:flag">
+ <xsl:message terminate="yes">tp:flag found outside tp:flags&#10;</xsl:message>
+ </xsl:template>
+
+ <xsl:template match="tp:enumvalue">
+ <xsl:message terminate="yes">tp:enumvalue found outside tp:enum&#10;</xsl:message>
+ </xsl:template>
+
+ <xsl:template match="text()"/>
+
+ <xsl:template match="/tp:spec">
+ <xsl:if test="$mixed-case-prefix = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>mixed-case-prefix param must be set&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:text>/* Generated from </xsl:text>
+ <xsl:value-of select="tp:title"/>
+ <xsl:if test="tp:version">
+ <xsl:text>, version </xsl:text>
+ <xsl:value-of select="tp:version"/>
+ </xsl:if>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:for-each select="tp:copyright">
+ <xsl:value-of select="."/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:for-each>
+ <xsl:value-of select="tp:license"/>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:value-of select="tp:docstring"/>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:text> */&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:text>#ifdef __cplusplus&#10;</xsl:text>
+ <xsl:text>extern "C" {&#10;</xsl:text>
+ <xsl:text>#endif&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:text>#ifdef __cplusplus&#10;</xsl:text>
+ <xsl:text>}&#10;</xsl:text>
+ <xsl:text>#endif&#10;</xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et noai noci: -->
diff --git a/salut/tools/c-interfaces-generator.xsl b/salut/tools/c-interfaces-generator.xsl
new file mode 100644
index 000000000..f965a7051
--- /dev/null
+++ b/salut/tools/c-interfaces-generator.xsl
@@ -0,0 +1,84 @@
+<!-- Stylesheet to extract C enumerations from the Telepathy spec.
+The master copy of this stylesheet is in telepathy-glib - please make any
+changes there.
+
+Copyright (C) 2006, 2007 Collabora Limited
+
+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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ exclude-result-prefixes="tp">
+
+ <xsl:param name="mixed-case-prefix" select="''"/>
+
+ <xsl:variable name="PREFIX"
+ select="translate($mixed-case-prefix, $lower, $upper)"/>
+ <xsl:variable name="Prefix" select="$mixed-case-prefix"/>
+ <xsl:variable name="prefix"
+ select="translate($mixed-case-prefix, $upper, $lower)"/>
+
+ <xsl:output method="text" indent="no" encoding="ascii"/>
+
+ <xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
+ <xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
+
+ <xsl:template match="interface">
+ <xsl:text>/**&#10; * </xsl:text>
+ <xsl:value-of select="$PREFIX"/>
+ <xsl:text>_IFACE_</xsl:text>
+ <xsl:value-of select="translate(../@name, concat($lower, '/'), $upper)"/>
+ <xsl:text>:&#10; * &#10; * The interface name "</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>"&#10; */&#10;#define </xsl:text>
+ <xsl:value-of select="$PREFIX"/>
+ <xsl:text>_IFACE_</xsl:text>
+ <xsl:value-of select="translate(../@name, concat($lower, '/'), $upper)"/>
+ <xsl:text> \&#10;"</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>"&#10;&#10;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="text()"/>
+
+ <xsl:template match="/tp:spec">
+ <xsl:if test="$mixed-case-prefix = ''">
+ <xsl:message terminate="yes">
+ <xsl:text>mixed-case-prefix param must be set&#10;</xsl:text>
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:text>/* Generated from: </xsl:text>
+ <xsl:value-of select="tp:title"/>
+ <xsl:if test="tp:version">
+ <xsl:text> version </xsl:text>
+ <xsl:value-of select="tp:version"/>
+ </xsl:if>
+ <xsl:text>&#10;&#10;</xsl:text>
+ <xsl:for-each select="tp:copyright">
+ <xsl:value-of select="."/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:for-each>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:value-of select="tp:license"/>
+ <xsl:value-of select="tp:docstring"/>
+ <xsl:text>&#10; */&#10;&#10;</xsl:text>
+ <xsl:apply-templates/>
+ </xsl:template>
+
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et noai noci: -->
diff --git a/salut/tools/check-c-style.sh b/salut/tools/check-c-style.sh
new file mode 100644
index 000000000..4330b1479
--- /dev/null
+++ b/salut/tools/check-c-style.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+fail=0
+
+( . "${tools_dir}"/check-misc.sh ) || fail=$?
+
+if grep -n '^ *GError *\*[[:alpha:]_][[:alnum:]_]* *;' "$@"
+then
+ echo "^^^ The above files contain uninitialized GError*s - they should be"
+ echo " initialized to NULL"
+ fail=1
+fi
+
+# The first regex finds function calls like foo() (as opposed to foo ()).
+# It attempts to ignore string constants (may cause false negatives).
+# The second and third ignore block comments (gtkdoc uses foo() as markup).
+# The fourth ignores cpp so you can
+# #define foo(bar) (_real_foo (__FUNC__, bar)) (cpp insists on foo() style).
+if grep -n '^[^"]*[[:lower:]](' "$@" \
+ | grep -v '^[-[:alnum:]_./]*:[[:digit:]]*: *\*' \
+ | grep -v '^[-[:alnum:]_./]*:[[:digit:]]*: */\*' \
+ | grep -v '^[-[:alnum:]_./]*:[[:digit:]]*: *#'
+then
+ echo "^^^ Our coding style is to use function calls like foo (), not foo()"
+ fail=1
+fi
+
+if grep -En '[(][[:alnum:]_]+ ?\*[)][(]?[[:alpha:]_]' "$@"; then
+ echo "^^^ Our coding style is to have a space between a cast and the "
+ echo " thing being cast"
+ fail=1
+fi
+
+# this only spots casts
+if grep -En '[(][[:alnum:]_]+\*+[)]' "$@"; then
+ echo "^^^ Our coding style is to have a space before the * of pointer types"
+ echo " (regex 1)"
+ fail=1
+fi
+# ... and this only spots variable declarations and function return types
+if grep -En '^ *(static |const |)* *[[:alnum:]_]+\*+([[:alnum:]_]|;|$)' \
+ "$@"; then
+ echo "^^^ Our coding style is to have a space before the * of pointer types"
+ echo " (regex 2)"
+ fail=1
+fi
+
+if grep -n 'g_hash_table_destroy' "$@"; then
+ echo "^^^ Our coding style is to use g_hash_table_unref"
+ fail=1
+fi
+
+for p in "" "ptr_" "byte_"; do
+ if grep -En "g_${p}array_free \(([^ ,]+), TRUE\)" "$@"; then
+ echo "^^^ Our coding style is to use g_${p}array_unref in the case "
+ echo " the underlying C array is not used"
+ fail=1
+ fi
+done
+
+if test -n "$CHECK_FOR_LONG_LINES"
+then
+ if egrep -n '.{80,}' "$@"
+ then
+ echo "^^^ The above files contain long lines"
+ fail=1
+ fi
+fi
+
+exit $fail
diff --git a/salut/tools/check-coding-style.mk b/salut/tools/check-coding-style.mk
new file mode 100644
index 000000000..1c0a60f66
--- /dev/null
+++ b/salut/tools/check-coding-style.mk
@@ -0,0 +1,17 @@
+check-coding-style:
+ @fail=0; \
+ if test -n "$(check_misc_sources)"; then \
+ tools_dir=$(top_srcdir)/tools \
+ sh $(top_srcdir)/tools/check-misc.sh \
+ $(addprefix $(srcdir)/,$(check_misc_sources)) || fail=1; \
+ fi; \
+ if test -n "$(check_c_sources)"; then \
+ tools_dir=$(top_srcdir)/tools \
+ sh $(top_srcdir)/tools/check-c-style.sh \
+ $(addprefix $(srcdir)/,$(check_c_sources)) || fail=1; \
+ fi;\
+ if test yes = "$(ENABLE_CODING_STYLE_CHECKS)"; then \
+ exit "$$fail";\
+ else \
+ exit 0;\
+ fi
diff --git a/salut/tools/check-misc.sh b/salut/tools/check-misc.sh
new file mode 100644
index 000000000..89e8e871a
--- /dev/null
+++ b/salut/tools/check-misc.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+fail=0
+
+( . "${tools_dir}"/check-whitespace.sh ) || fail=$?
+
+if egrep '(Free\s*Software\s*Foundation.*02139|02111-1307)' "$@"
+then
+ echo "^^^ The above files contain the FSF's old address in GPL headers"
+ fail=1
+fi
+
+exit $fail
diff --git a/salut/tools/check-whitespace.sh b/salut/tools/check-whitespace.sh
new file mode 100644
index 000000000..534833126
--- /dev/null
+++ b/salut/tools/check-whitespace.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+fail=0
+
+if grep -n ' $' "$@"
+then
+ echo "^^^ The above files contain unwanted trailing spaces"
+ fail=1
+fi
+
+if grep -n ' ' "$@"
+then
+ echo "^^^ The above files contain tabs"
+ fail=1
+fi
+
+exit $fail
diff --git a/salut/tools/doc-generator.xsl b/salut/tools/doc-generator.xsl
new file mode 100644
index 000000000..a820e46d8
--- /dev/null
+++ b/salut/tools/doc-generator.xsl
@@ -0,0 +1,758 @@
+<!-- Generate HTML documentation from the Telepathy specification.
+The master copy of this stylesheet is in the Telepathy spec repository -
+please make any changes there.
+
+Copyright (C) 2006-2008 Collabora Limited
+
+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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ exclude-result-prefixes="tp html">
+ <!--Don't move the declaration of the HTML namespace up here - XMLNSs
+ don't work ideally in the presence of two things that want to use the
+ absence of a prefix, sadly. -->
+
+ <xsl:template match="html:*" mode="html">
+ <xsl:copy>
+ <xsl:apply-templates mode="html"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="*" mode="identity">
+ <xsl:copy>
+ <xsl:apply-templates mode="identity"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="tp:docstring">
+ <xsl:apply-templates select="text() | html:* | tp:rationale" mode="html"/>
+ </xsl:template>
+
+ <xsl:template match="tp:rationale" mode="html">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="rationale">
+ <xsl:apply-templates select="node()" mode="html"/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:errors">
+ <h1 xmlns="http://www.w3.org/1999/xhtml">Errors</h1>
+ <xsl:apply-templates/>
+ </xsl:template>
+
+ <xsl:template match="tp:generic-types">
+ <h1 xmlns="http://www.w3.org/1999/xhtml">Generic types</h1>
+ <xsl:call-template name="do-types"/>
+ </xsl:template>
+
+ <xsl:template name="do-types">
+ <xsl:if test="tp:simple-type">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Simple types</h2>
+ <xsl:apply-templates select="tp:simple-type"/>
+ </xsl:if>
+
+ <xsl:if test="tp:enum">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Enumerated types:</h2>
+ <xsl:apply-templates select="tp:enum"/>
+ </xsl:if>
+
+ <xsl:if test="tp:flags">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Sets of flags:</h2>
+ <xsl:apply-templates select="tp:flags"/>
+ </xsl:if>
+
+ <xsl:if test="tp:struct">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Structure types</h2>
+ <xsl:apply-templates select="tp:struct"/>
+ </xsl:if>
+
+ <xsl:if test="tp:mapping">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Mapping types</h2>
+ <xsl:apply-templates select="tp:mapping"/>
+ </xsl:if>
+
+ <xsl:if test="tp:external-type">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Types defined elsewhere</h2>
+ <dl><xsl:apply-templates select="tp:external-type"/></dl>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="tp:error">
+ <h2 xmlns="http://www.w3.org/1999/xhtml"><a name="{concat(../@namespace, '.', translate(@name, ' ', ''))}"></a><xsl:value-of select="concat(../@namespace, '.', translate(@name, ' ', ''))"/></h2>
+ <xsl:apply-templates select="tp:docstring"/>
+ </xsl:template>
+
+ <xsl:template match="/tp:spec/tp:copyright">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates/>
+ </div>
+ </xsl:template>
+ <xsl:template match="/tp:spec/tp:license">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="license">
+ <xsl:apply-templates mode="html"/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:copyright"/>
+ <xsl:template match="tp:license"/>
+
+ <xsl:template match="interface">
+ <h1 xmlns="http://www.w3.org/1999/xhtml"><a name="{@name}"></a><xsl:value-of select="@name"/></h1>
+
+ <xsl:if test="@tp:causes-havoc">
+ <p xmlns="http://www.w3.org/1999/xhtml" class="causes-havoc">
+ This interface is <xsl:value-of select="@tp:causes-havoc"/>
+ and is likely to cause havoc to your API/ABI if bindings are generated.
+ Don't include it in libraries that care about compatibility.
+ </p>
+ </xsl:if>
+
+ <xsl:if test="tp:requires">
+ <p>Implementations of this interface must also implement:</p>
+ <ul xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:for-each select="tp:requires">
+ <li><code><a href="#{@interface}"><xsl:value-of select="@interface"/></a></code></li>
+ </xsl:for-each>
+ </ul>
+ </xsl:if>
+
+ <xsl:apply-templates select="tp:docstring" />
+
+ <xsl:choose>
+ <xsl:when test="method">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Methods:</h2>
+ <xsl:apply-templates select="method"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <p xmlns="http://www.w3.org/1999/xhtml">Interface has no methods.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="signal">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Signals:</h2>
+ <xsl:apply-templates select="signal"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <p xmlns="http://www.w3.org/1999/xhtml">Interface has no signals.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="tp:property">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">Telepathy Properties:</h2>
+ <p xmlns="http://www.w3.org/1999/xhtml">Accessed using the
+ <a href="#org.freedesktop.Telepathy.Properties">Telepathy
+ Properties</a> interface.</p>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:property"/>
+ </dl>
+ </xsl:when>
+ <xsl:otherwise>
+ <p xmlns="http://www.w3.org/1999/xhtml">Interface has no Telepathy
+ properties.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:choose>
+ <xsl:when test="property">
+ <h2 xmlns="http://www.w3.org/1999/xhtml">D-Bus core Properties:</h2>
+ <p xmlns="http://www.w3.org/1999/xhtml">Accessed using the
+ org.freedesktop.DBus.Properties interface.</p>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="property"/>
+ </dl>
+ </xsl:when>
+ <xsl:otherwise>
+ <p xmlns="http://www.w3.org/1999/xhtml">Interface has no D-Bus core
+ properties.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:call-template name="do-types"/>
+
+ </xsl:template>
+
+ <xsl:template match="tp:flags">
+ <h3>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a>
+ </h3>
+ <xsl:apply-templates select="tp:docstring" />
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:variable name="value-prefix">
+ <xsl:choose>
+ <xsl:when test="@value-prefix">
+ <xsl:value-of select="@value-prefix"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:for-each select="tp:flag">
+ <dt xmlns="http://www.w3.org/1999/xhtml"><code><xsl:value-of select="concat($value-prefix, '_', @suffix)"/> = <xsl:value-of select="@value"/></code></dt>
+ <xsl:choose>
+ <xsl:when test="tp:docstring">
+ <dd xmlns="http://www.w3.org/1999/xhtml"><xsl:apply-templates select="tp:docstring" /></dd>
+ </xsl:when>
+ <xsl:otherwise>
+ <dd xmlns="http://www.w3.org/1999/xhtml">(Undocumented)</dd>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </dl>
+ </xsl:template>
+
+ <xsl:template match="tp:enum">
+ <h3 xmlns="http://www.w3.org/1999/xhtml">
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a>
+ </h3>
+ <xsl:apply-templates select="tp:docstring" />
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:variable name="value-prefix">
+ <xsl:choose>
+ <xsl:when test="@value-prefix">
+ <xsl:value-of select="@value-prefix"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:for-each select="tp:enumvalue">
+ <dt xmlns="http://www.w3.org/1999/xhtml"><code><xsl:value-of select="concat($value-prefix, '_', @suffix)"/> = <xsl:value-of select="@value"/></code></dt>
+ <xsl:choose>
+ <xsl:when test="tp:docstring">
+ <dd xmlns="http://www.w3.org/1999/xhtml"><xsl:apply-templates select="tp:docstring" /></dd>
+ </xsl:when>
+ <xsl:otherwise>
+ <dd xmlns="http://www.w3.org/1999/xhtml">(Undocumented)</dd>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </dl>
+ </xsl:template>
+
+ <xsl:template match="property">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <a name="{concat(../@name, '.', @name)}">
+ <code><xsl:value-of select="@name"/></code>
+ </a>
+ <xsl:text> - </xsl:text>
+ <code><xsl:value-of select="@type"/></code>
+ <xsl:call-template name="parenthesized-tp-type"/>
+ <xsl:text>, </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@access = 'read'">
+ <xsl:text>read-only</xsl:text>
+ </xsl:when>
+ <xsl:when test="@access = 'write'">
+ <xsl:text>write-only</xsl:text>
+ </xsl:when>
+ <xsl:when test="@access = 'readwrite'">
+ <xsl:text>read/write</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>access: </xsl:text>
+ <code><xsl:value-of select="@access"/></code>
+ </xsl:otherwise>
+ </xsl:choose>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring"/>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="tp:property">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:if test="@name">
+ <code><xsl:value-of select="@name"/></code> -
+ </xsl:if>
+ <code><xsl:value-of select="@type"/></code>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring"/>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="tp:mapping">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="struct">
+ <h3>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a> - a{
+ <xsl:for-each select="tp:member">
+ <xsl:value-of select="@type"/>
+ <xsl:text>: </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:if test="position() != last()"> &#x2192; </xsl:if>
+ </xsl:for-each>
+ }
+ </h3>
+ <div class="docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ <xsl:if test="string(@array-name) != ''">
+ <p>In bindings that need a separate name, arrays of
+ <xsl:value-of select="@name"/> should be called
+ <xsl:value-of select="@array-name"/>.</p>
+ </xsl:if>
+ </div>
+ <div>
+ <h4>Members</h4>
+ <dl>
+ <xsl:apply-templates select="tp:member" mode="members-in-docstring"/>
+ </dl>
+ </div>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:docstring" mode="in-index"/>
+
+ <xsl:template match="tp:simple-type | tp:enum | tp:flags | tp:external-type"
+ mode="in-index">
+ - <xsl:value-of select="@type"/>
+ </xsl:template>
+
+ <xsl:template match="tp:simple-type">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="simple-type">
+ <h3>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a> - <xsl:value-of select="@type"/>
+ </h3>
+ <div class="docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ </div>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:external-type">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="external-type">
+ <dt>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a> - <xsl:value-of select="@type"/>
+ </dt>
+ <dd>Defined by: <xsl:value-of select="@from"/></dd>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="tp:struct" mode="in-index">
+ - ( <xsl:for-each select="tp:member">
+ <xsl:value-of select="@type"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each> )
+ </xsl:template>
+
+ <xsl:template match="tp:mapping" mode="in-index">
+ - a{ <xsl:for-each select="tp:member">
+ <xsl:value-of select="@type"/>
+ <xsl:if test="position() != last()"> &#x2192; </xsl:if>
+ </xsl:for-each> }
+ </xsl:template>
+
+ <xsl:template match="tp:struct">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="struct">
+ <h3>
+ <a name="type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a> - (
+ <xsl:for-each select="tp:member">
+ <xsl:value-of select="@type"/>
+ <xsl:text>: </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ )
+ </h3>
+ <div class="docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ </div>
+ <xsl:choose>
+ <xsl:when test="string(@array-name) != ''">
+ <p>In bindings that need a separate name, arrays of
+ <xsl:value-of select="@name"/> should be called
+ <xsl:value-of select="@array-name"/>.</p>
+ </xsl:when>
+ <xsl:otherwise>
+ <p>Arrays of <xsl:value-of select="@name"/> don't generally
+ make sense.</p>
+ </xsl:otherwise>
+ </xsl:choose>
+ <div>
+ <h4>Members</h4>
+ <dl>
+ <xsl:apply-templates select="tp:member" mode="members-in-docstring"/>
+ </dl>
+ </div>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="method">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="method">
+ <h3 xmlns="http://www.w3.org/1999/xhtml">
+ <a name="{concat(../@name, concat('.', @name))}">
+ <xsl:value-of select="@name"/>
+ </a> (
+ <xsl:for-each xmlns="" select="arg[@direction='in']">
+ <xsl:value-of select="@type"/>: <xsl:value-of select="@name"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ ) &#x2192;
+ <xsl:choose>
+ <xsl:when test="arg[@direction='out']">
+ <xsl:for-each xmlns="" select="arg[@direction='out']">
+ <xsl:value-of select="@type"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:when>
+ <xsl:otherwise>nothing</xsl:otherwise>
+ </xsl:choose>
+ </h3>
+ <div xmlns="http://www.w3.org/1999/xhtml" class="docstring">
+ <xsl:apply-templates select="tp:docstring" />
+ </div>
+
+ <xsl:if test="arg[@direction='in']">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h4>Parameters</h4>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="arg[@direction='in']"
+ mode="parameters-in-docstring"/>
+ </dl>
+ </div>
+ </xsl:if>
+
+ <xsl:if test="arg[@direction='out']">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h4>Returns</h4>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="arg[@direction='out']"
+ mode="returns-in-docstring"/>
+ </dl>
+ </div>
+ </xsl:if>
+
+ <xsl:if test="tp:possible-errors">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h4>Possible errors</h4>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:possible-errors/tp:error"/>
+ </dl>
+ </div>
+ </xsl:if>
+
+ </div>
+ </xsl:template>
+
+ <xsl:template name="parenthesized-tp-type">
+ <xsl:if test="@tp:type">
+ <xsl:variable name="tp-type" select="@tp:type"/>
+ <xsl:variable name="single-type">
+ <xsl:choose>
+ <xsl:when test="contains($tp-type, '[]')">
+ <xsl:value-of select="substring-before($tp-type, '[]')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$tp-type"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="//tp:simple-type[@name=$tp-type]" />
+ <xsl:when test="//tp:simple-type[concat(@name, '[]')=$tp-type]" />
+ <xsl:when test="//tp:struct[concat(@name, '[]')=$tp-type][string(@array-name) != '']" />
+ <xsl:when test="//tp:mapping[concat(@name, '[]')=$tp-type][string(@array-name) != '']" />
+ <xsl:when test="//tp:struct[@name=$tp-type]" />
+ <xsl:when test="//tp:enum[@name=$tp-type]" />
+ <xsl:when test="//tp:enum[concat(@name, '[]')=$tp-type]" />
+ <xsl:when test="//tp:flags[@name=$tp-type]" />
+ <xsl:when test="//tp:flags[concat(@name, '[]')=$tp-type]" />
+ <xsl:when test="//tp:mapping[@name=$tp-type]" />
+ <xsl:when test="//tp:external-type[concat(@name, '[]')=$tp-type]" />
+ <xsl:when test="//tp:external-type[@name=$tp-type]" />
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>ERR: Unable to find type '</xsl:text>
+ <xsl:value-of select="$tp-type"/>
+ <xsl:text>'&#10;</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ (<a href="#type-{$single-type}"><xsl:value-of select="$tp-type"/></a>)
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="tp:member" mode="members-in-docstring">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <code><xsl:value-of select="@name"/></code> -
+ <code><xsl:value-of select="@type"/></code>
+ <xsl:call-template name="parenthesized-tp-type"/>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:choose>
+ <xsl:when test="tp:docstring">
+ <xsl:apply-templates select="tp:docstring" />
+ </xsl:when>
+ <xsl:otherwise>
+ <em>(undocumented)</em>
+ </xsl:otherwise>
+ </xsl:choose>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="arg" mode="parameters-in-docstring">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <code><xsl:value-of select="@name"/></code> -
+ <code><xsl:value-of select="@type"/></code>
+ <xsl:call-template name="parenthesized-tp-type"/>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring" />
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="arg" mode="returns-in-docstring">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:if test="@name">
+ <code><xsl:value-of select="@name"/></code> -
+ </xsl:if>
+ <code><xsl:value-of select="@type"/></code>
+ <xsl:call-template name="parenthesized-tp-type"/>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="tp:docstring"/>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="tp:possible-errors/tp:error">
+ <dt xmlns="http://www.w3.org/1999/xhtml">
+ <code><xsl:value-of select="@name"/></code>
+ </dt>
+ <dd xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:choose>
+ <xsl:when test="tp:docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ </xsl:when>
+ <xsl:when test="//tp:errors/tp:error[concat(../@namespace, '.', translate(@name, ' ', ''))=$name]/tp:docstring">
+ <xsl:apply-templates select="//tp:errors/tp:error[concat(../@namespace, '.', translate(@name, ' ', ''))=$name]/tp:docstring"/> <em xmlns="http://www.w3.org/1999/xhtml">(generic description)</em>
+ </xsl:when>
+ <xsl:otherwise>
+ (Undocumented.)
+ </xsl:otherwise>
+ </xsl:choose>
+ </dd>
+ </xsl:template>
+
+ <xsl:template match="signal">
+ <div xmlns="http://www.w3.org/1999/xhtml" class="signal">
+ <h3 xmlns="http://www.w3.org/1999/xhtml">
+ <a name="{concat(../@name, concat('.', @name))}">
+ <xsl:value-of select="@name"/>
+ </a> (
+ <xsl:for-each xmlns="" select="arg">
+ <xsl:value-of select="@type"/>: <xsl:value-of select="@name"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ )</h3>
+ <div xmlns="http://www.w3.org/1999/xhtml" class="docstring">
+ <xsl:apply-templates select="tp:docstring"/>
+ </div>
+
+ <xsl:if test="arg">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h4>Parameters</h4>
+ <dl xmlns="http://www.w3.org/1999/xhtml">
+ <xsl:apply-templates select="arg" mode="parameters-in-docstring"/>
+ </dl>
+ </div>
+ </xsl:if>
+ </div>
+ </xsl:template>
+
+ <xsl:output method="xml" indent="no" encoding="ascii"
+ omit-xml-declaration="yes"
+ doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+ doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" />
+
+ <xsl:template match="/tp:spec">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>
+ <xsl:value-of select="tp:title"/>
+ <xsl:if test="tp:version">
+ <xsl:text> version </xsl:text>
+ <xsl:value-of select="tp:version"/>
+ </xsl:if>
+ </title>
+ <style type="text/css">
+
+ body {
+ font-family: sans-serif;
+ margin: 2em;
+ height: 100%;
+ font-size: 1.2em;
+ }
+ h1 {
+ padding-top: 5px;
+ padding-bottom: 5px;
+ font-size: 1.6em;
+ background: #dadae2;
+ }
+ h2 {
+ font-size: 1.3em;
+ }
+ h3 {
+ font-size: 1.2em;
+ }
+ a:link, a:visited, a:link:hover, a:visited:hover {
+ font-weight: bold;
+ }
+ .topbox {
+ padding-top: 10px;
+ padding-left: 10px;
+ border-bottom: black solid 1px;
+ padding-bottom: 10px;
+ background: #dadae2;
+ font-size: 2em;
+ font-weight: bold;
+ color: #5c5c5c;
+ }
+ .topnavbox {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ background: #abacba;
+ border-bottom: black solid 1px;
+ font-size: 1.2em;
+ }
+ .topnavbox a{
+ color: black;
+ font-weight: normal;
+ }
+ .sidebar {
+ float: left;
+ /* width:9em;
+ border-right:#abacba solid 1px;
+ border-left: #abacba solid 1px;
+ height:100%; */
+ border: #abacba solid 1px;
+ padding-left: 10px;
+ margin-left: 10px;
+ padding-right: 10px;
+ margin-right: 10px;
+ color: #5d5d5d;
+ background: #dadae2;
+ }
+ .sidebar a {
+ text-decoration: none;
+ border-bottom: #e29625 dotted 1px;
+ color: #e29625;
+ font-weight: normal;
+ }
+ .sidebar h1 {
+ font-size: 1.2em;
+ color: black;
+ }
+ .sidebar ul {
+ padding-left: 25px;
+ padding-bottom: 10px;
+ border-bottom: #abacba solid 1px;
+ }
+ .sidebar li {
+ padding-top: 2px;
+ padding-bottom: 2px;
+ }
+ .sidebar h2 {
+ font-style:italic;
+ font-size: 0.81em;
+ padding-left: 5px;
+ padding-right: 5px;
+ font-weight: normal;
+ }
+ .date {
+ font-size: 0.6em;
+ float: right;
+ font-style: italic;
+ }
+ .method, .signal, .property {
+ margin-left: 1em;
+ margin-right: 4em;
+ }
+ .rationale {
+ font-style: italic;
+ border-left: 0.25em solid #808080;
+ padding-left: 0.5em;
+ }
+
+ </style>
+ </head>
+ <body>
+ <h1 class="topbox">
+ <xsl:value-of select="tp:title" />
+ </h1>
+ <xsl:if test="tp:version">
+ <h2>Version <xsl:apply-templates select="tp:version"/></h2>
+ </xsl:if>
+ <xsl:apply-templates select="tp:copyright"/>
+ <xsl:apply-templates select="tp:license"/>
+ <xsl:apply-templates select="tp:docstring"/>
+
+ <h2>Interfaces</h2>
+ <ul>
+ <xsl:for-each select="//node/interface">
+ <li><code><a href="#{@name}"><xsl:value-of select="@name"/></a></code></li>
+ </xsl:for-each>
+ </ul>
+
+ <xsl:apply-templates select="//node"/>
+ <xsl:apply-templates select="tp:generic-types"/>
+ <xsl:apply-templates select="tp:errors"/>
+
+ <h1>Index</h1>
+ <h2>Index of interfaces</h2>
+ <ul>
+ <xsl:for-each select="//node/interface">
+ <li><code><a href="#{@name}"><xsl:value-of select="@name"/></a></code></li>
+ </xsl:for-each>
+ </ul>
+ <h2>Index of types</h2>
+ <ul>
+ <xsl:for-each select="//tp:simple-type | //tp:enum | //tp:flags | //tp:mapping | //tp:struct | //tp:external-type">
+ <xsl:sort select="@name"/>
+ <li>
+ <code>
+ <a href="#type-{@name}">
+ <xsl:value-of select="@name"/>
+ </a>
+ </code>
+ <xsl:apply-templates mode="in-index" select="."/>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </body>
+ </html>
+ </xsl:template>
+
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et: -->
diff --git a/salut/tools/glib-client-marshaller-gen.py b/salut/tools/glib-client-marshaller-gen.py
new file mode 100644
index 000000000..54447255b
--- /dev/null
+++ b/salut/tools/glib-client-marshaller-gen.py
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+
+import sys
+import xml.dom.minidom
+from string import ascii_letters, digits
+
+
+from libglibcodegen import signal_to_marshal_name
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+class Generator(object):
+
+ def __init__(self, dom, prefix):
+ self.dom = dom
+ self.marshallers = {}
+ self.prefix = prefix
+
+ def do_signal(self, signal):
+ marshaller = signal_to_marshal_name(signal, self.prefix)
+
+ assert '__' in marshaller
+ rhs = marshaller.split('__', 1)[1].split('_')
+
+ self.marshallers[marshaller] = rhs
+
+ def __call__(self):
+ signals = self.dom.getElementsByTagName('signal')
+
+ for signal in signals:
+ self.do_signal(signal)
+
+ print 'void'
+ print '%s_register_dbus_glib_marshallers (void)' % self.prefix
+ print '{'
+
+ all = self.marshallers.keys()
+ all.sort()
+ for marshaller in all:
+ rhs = self.marshallers[marshaller]
+
+ print ' dbus_g_object_register_marshaller (%s,' % marshaller
+ print ' G_TYPE_NONE, /* return */'
+ for type in rhs:
+ print ' G_TYPE_%s,' % type.replace('VOID', 'NONE')
+ print ' G_TYPE_INVALID);'
+
+ print '}'
+
+
+def types_to_gtypes(types):
+ return [type_to_gtype(t)[1] for t in types]
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+ dom = xml.dom.minidom.parse(argv[0])
+
+ Generator(dom, argv[1])()
diff --git a/salut/tools/glib-errors-enum-body.xsl b/salut/tools/glib-errors-enum-body.xsl
new file mode 100644
index 000000000..17054b765
--- /dev/null
+++ b/salut/tools/glib-errors-enum-body.xsl
@@ -0,0 +1,72 @@
+<!-- Stylesheet to extract GLib error enumerations from the Telepathy spec.
+The master copy of this stylesheet is in telepathy-glib - please make any
+changes there.
+
+Copyright (C) 2006, 2007 Collabora Limited
+
+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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ exclude-result-prefixes="tp">
+
+ <xsl:output method="text" indent="no" encoding="ascii"/>
+
+ <xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
+ <xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
+
+ <xsl:template match="tp:error" mode="values">
+ <!-- CHANNEL_BANNED -->
+ <xsl:variable name="name" select="translate(@name, concat($lower, '. '),
+ concat($upper, '__'))"/>
+ <!-- Channel.Banned -->
+ <xsl:variable name="nick" select="translate(@name, ' ', '')"/>
+ /* <xsl:value-of select="concat(../@namespace, '.', $name)"/>
+ <xsl:value-of select="tp:docstring"/> */
+ { TP_ERROR_<xsl:value-of select="$name"/>, "TP_ERROR_<xsl:value-of select="$name"/>", "<xsl:value-of select="$nick"/>" },
+</xsl:template>
+
+ <xsl:template match="text()"/>
+
+ <xsl:template match="//tp:errors">/* Generated from the Telepathy spec
+
+<xsl:for-each select="tp:copyright">
+<xsl:value-of select="."/><xsl:text>
+</xsl:text></xsl:for-each><xsl:text>
+</xsl:text><xsl:value-of select="tp:license"/>
+*/
+
+#include &lt;_gen/telepathy-errors.h&gt;
+
+GType
+tp_error_get_type (void)
+{
+ static GType etype = 0;
+ if (G_UNLIKELY (etype == 0))
+ {
+ static const GEnumValue values[] = {
+<xsl:apply-templates select="tp:error" mode="values"/> };
+
+ etype = g_enum_register_static ("TpError", values);
+ }
+ return etype;
+}
+
+</xsl:template>
+
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et noai noci: -->
diff --git a/salut/tools/glib-errors-enum-header.xsl b/salut/tools/glib-errors-enum-header.xsl
new file mode 100644
index 000000000..5275041cd
--- /dev/null
+++ b/salut/tools/glib-errors-enum-header.xsl
@@ -0,0 +1,73 @@
+<!-- Stylesheet to extract GLib error enumerations from the Telepathy spec.
+The master copy of this stylesheet is in telepathy-glib - please make any
+changes there.
+
+Copyright (C) 2006, 2007 Collabora Limited
+
+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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ exclude-result-prefixes="tp">
+
+ <xsl:output method="text" indent="no" encoding="ascii"/>
+
+ <xsl:template match="tp:error" mode="gtkdoc">
+ * @TP_ERROR_<xsl:value-of select="translate(@name, 'abcdefghijklmnopqrstuvwxyz .', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ__')"/>: <xsl:value-of select="concat(../@namespace, '.', translate(@name, ' ', ''))"/>:
+ * <xsl:value-of select="translate(tp:docstring, '&#13;&#10;', '')"/>
+ </xsl:template>
+
+ <xsl:template match="tp:error" mode="enum">
+<xsl:text> TP_ERROR_</xsl:text><xsl:value-of select="translate(@name, 'abcdefghijklmnopqrstuvwxyz .', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ__')"/>,
+</xsl:template>
+
+ <xsl:template match="text()"/>
+
+ <xsl:template match="//tp:errors">/* Generated from the Telepathy spec
+
+<xsl:for-each select="tp:copyright">
+<xsl:value-of select="."/><xsl:text>
+</xsl:text></xsl:for-each><xsl:text>
+</xsl:text><xsl:value-of select="tp:license"/>
+*/
+
+#include &lt;glib-object.h&gt;
+
+G_BEGIN_DECLS
+
+GType tp_error_get_type (void);
+
+/**
+ * TP_TYPE_ERROR:
+ *
+ * The GType of the Telepathy error enumeration.
+ */
+#define TP_TYPE_ERROR (tp_error_get_type())
+
+/**
+ * TpError:<xsl:apply-templates select="tp:error" mode="gtkdoc"/>
+ *
+ * Enumerated type representing the Telepathy D-Bus errors.
+ */
+typedef enum {
+<xsl:apply-templates select="tp:error" mode="enum"/>} TpError;
+
+G_END_DECLS
+</xsl:template>
+
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et noai noci: -->
diff --git a/salut/tools/glib-ginterface-gen.py b/salut/tools/glib-ginterface-gen.py
new file mode 100644
index 000000000..36f3242b0
--- /dev/null
+++ b/salut/tools/glib-ginterface-gen.py
@@ -0,0 +1,711 @@
+#!/usr/bin/python
+
+# glib-ginterface-gen.py: service-side interface generator
+#
+# Generate dbus-glib 0.x service GInterfaces from the Telepathy specification.
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (C) 2006, 2007 Collabora Limited
+#
+# 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import os.path
+import xml.dom.minidom
+
+from libglibcodegen import Signature, type_to_gtype, cmp_by_name, \
+ camelcase_to_lower, NS_TP, dbus_gutils_wincaps_to_uscore, \
+ signal_to_marshal_name, method_to_glue_marshal_name
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+class Generator(object):
+
+ def __init__(self, dom, prefix, basename, signal_marshal_prefix,
+ headers, end_headers, not_implemented_func,
+ allow_havoc):
+ self.dom = dom
+ self.__header = []
+ self.__body = []
+
+ assert prefix.endswith('_')
+ assert not signal_marshal_prefix.endswith('_')
+
+ # The main_prefix, sub_prefix thing is to get:
+ # FOO_ -> (FOO_, _)
+ # FOO_SVC_ -> (FOO_, _SVC_)
+ # but
+ # FOO_BAR/ -> (FOO_BAR_, _)
+ # FOO_BAR/SVC_ -> (FOO_BAR_, _SVC_)
+
+ if '/' in prefix:
+ main_prefix, sub_prefix = prefix.upper().split('/', 1)
+ prefix = prefix.replace('/', '_')
+ else:
+ main_prefix, sub_prefix = prefix.upper().split('_', 1)
+
+ self.MAIN_PREFIX_ = main_prefix + '_'
+ self._SUB_PREFIX_ = '_' + sub_prefix
+
+ self.Prefix_ = prefix
+ self.Prefix = prefix.replace('_', '')
+ self.prefix_ = prefix.lower()
+ self.PREFIX_ = prefix.upper()
+
+ self.signal_marshal_prefix = signal_marshal_prefix
+ self.headers = headers
+ self.end_headers = end_headers
+ self.not_implemented_func = not_implemented_func
+ self.allow_havoc = allow_havoc
+
+ def h(self, s):
+ self.__header.append(s)
+
+ def b(self, s):
+ self.__body.append(s)
+
+ def do_node(self, node):
+ node_name = node.getAttribute('name').replace('/', '')
+ node_name_mixed = self.node_name_mixed = node_name.replace('_', '')
+ node_name_lc = self.node_name_lc = node_name.lower()
+ node_name_uc = self.node_name_uc = node_name.upper()
+
+ interfaces = node.getElementsByTagName('interface')
+ assert len(interfaces) == 1, interfaces
+ interface = interfaces[0]
+ self.iface_name = interface.getAttribute('name')
+
+ tmp = interface.getAttribute('tp:implement-service')
+ if tmp == "no":
+ return
+
+ tmp = interface.getAttribute('tp:causes-havoc')
+ if tmp and not self.allow_havoc:
+ raise AssertionError('%s is %s' % (self.iface_name, tmp))
+
+ self.b('static const DBusGObjectInfo _%s%s_object_info;'
+ % (self.prefix_, node_name_lc))
+ self.b('')
+
+ methods = interface.getElementsByTagName('method')
+ signals = interface.getElementsByTagName('signal')
+ properties = interface.getElementsByTagName('property')
+ # Don't put properties in dbus-glib glue
+ glue_properties = []
+
+ self.b('struct _%s%sClass {' % (self.Prefix, node_name_mixed))
+ self.b(' GTypeInterface parent_class;')
+ for method in methods:
+ self.b(' %s %s;' % self.get_method_impl_names(method))
+ self.b('};')
+ self.b('')
+
+ if signals:
+ self.b('enum {')
+ for signal in signals:
+ self.b(' %s,' % self.get_signal_const_entry(signal))
+ self.b(' N_%s_SIGNALS' % node_name_uc)
+ self.b('};')
+ self.b('static guint %s_signals[N_%s_SIGNALS] = {0};'
+ % (node_name_lc, node_name_uc))
+ self.b('')
+
+ self.b('static void %s%s_base_init (gpointer klass);'
+ % (self.prefix_, node_name_lc))
+ self.b('')
+
+ self.b('GType')
+ self.b('%s%s_get_type (void)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+ self.b(' static GType type = 0;')
+ self.b('')
+ self.b(' if (G_UNLIKELY (type == 0))')
+ self.b(' {')
+ self.b(' static const GTypeInfo info = {')
+ self.b(' sizeof (%s%sClass),' % (self.Prefix, node_name_mixed))
+ self.b(' %s%s_base_init, /* base_init */'
+ % (self.prefix_, node_name_lc))
+ self.b(' NULL, /* base_finalize */')
+ self.b(' NULL, /* class_init */')
+ self.b(' NULL, /* class_finalize */')
+ self.b(' NULL, /* class_data */')
+ self.b(' 0,')
+ self.b(' 0, /* n_preallocs */')
+ self.b(' NULL /* instance_init */')
+ self.b(' };')
+ self.b('')
+ self.b(' type = g_type_register_static (G_TYPE_INTERFACE,')
+ self.b(' "%s%s", &info, 0);' % (self.Prefix, node_name_mixed))
+ self.b(' }')
+ self.b('')
+ self.b(' return type;')
+ self.b('}')
+ self.b('')
+
+ self.h('/**')
+ self.h(' * %s%s:' % (self.Prefix, node_name_mixed))
+ self.h(' *')
+ self.h(' * Dummy typedef representing any implementation of this '
+ 'interface.')
+ self.h(' */')
+ self.h('typedef struct _%s%s %s%s;'
+ % (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed))
+ self.h('')
+ self.h('/**')
+ self.h(' * %s%sClass:' % (self.Prefix, node_name_mixed))
+ self.h(' *')
+ self.h(' * The class of %s%s.' % (self.Prefix, node_name_mixed))
+ self.h(' */')
+ self.h('typedef struct _%s%sClass %s%sClass;'
+ % (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed))
+ self.h('')
+ self.h('GType %s%s_get_type (void);'
+ % (self.prefix_, node_name_lc))
+
+ gtype = self.current_gtype = \
+ self.MAIN_PREFIX_ + 'TYPE' + self._SUB_PREFIX_ + node_name_uc
+ classname = self.Prefix + node_name_mixed
+
+ self.h('#define %s \\\n (%s%s_get_type ())'
+ % (gtype, self.prefix_, node_name_lc))
+ self.h('#define %s%s(obj) \\\n'
+ ' (G_TYPE_CHECK_INSTANCE_CAST((obj), %s, %s))'
+ % (self.PREFIX_, node_name_uc, gtype, classname))
+ self.h('#define %sIS%s%s(obj) \\\n'
+ ' (G_TYPE_CHECK_INSTANCE_TYPE((obj), %s))'
+ % (self.MAIN_PREFIX_, self._SUB_PREFIX_, node_name_uc, gtype))
+ self.h('#define %s%s_GET_CLASS(obj) \\\n'
+ ' (G_TYPE_INSTANCE_GET_INTERFACE((obj), %s, %sClass))'
+ % (self.PREFIX_, node_name_uc, gtype, classname))
+ self.h('')
+ self.h('')
+
+ base_init_code = []
+
+ for method in methods:
+ self.do_method(method)
+
+ for signal in signals:
+ base_init_code.extend(self.do_signal(signal))
+
+ self.b('static inline void')
+ self.b('%s%s_base_init_once (gpointer klass G_GNUC_UNUSED)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+ self.b(' static TpDBusPropertiesMixinPropInfo properties[%d] = {'
+ % (len(properties) + 1))
+
+ for m in properties:
+ access = m.getAttribute('access')
+ assert access in ('read', 'write', 'readwrite')
+
+ if access == 'read':
+ flags = 'TP_DBUS_PROPERTIES_MIXIN_FLAG_READ'
+ elif access == 'write':
+ flags = 'TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE'
+ else:
+ flags = ('TP_DBUS_PROPERTIES_MIXIN_FLAG_READ | '
+ 'TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE')
+
+ self.b(' { 0, %s, "%s", 0, NULL, NULL }, /* %s */'
+ % (flags, m.getAttribute('type'), m.getAttribute('name')))
+
+ self.b(' { 0, 0, NULL, 0, NULL, NULL }')
+ self.b(' };')
+ self.b(' static TpDBusPropertiesMixinIfaceInfo interface =')
+ self.b(' { 0, properties, NULL, NULL };')
+ self.b('')
+ self.b(' interface.dbus_interface = g_quark_from_static_string '
+ '("%s");' % self.iface_name)
+
+ for i, m in enumerate(properties):
+ self.b(' properties[%d].name = g_quark_from_static_string ("%s");'
+ % (i, m.getAttribute('name')))
+ self.b(' properties[%d].type = %s;'
+ % (i, type_to_gtype(m.getAttribute('type'))[1]))
+
+ self.b(' tp_svc_interface_set_dbus_properties_info (%s, &interface);'
+ % self.current_gtype)
+
+ self.b('')
+ for s in base_init_code:
+ self.b(s)
+ self.b(' dbus_g_object_type_install_info (%s%s_get_type (),'
+ % (self.prefix_, node_name_lc))
+ self.b(' &_%s%s_object_info);'
+ % (self.prefix_, node_name_lc))
+ self.b('}')
+
+ self.b('static void')
+ self.b('%s%s_base_init (gpointer klass)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+ self.b(' static gboolean initialized = FALSE;')
+ self.b('')
+ self.b(' if (!initialized)')
+ self.b(' {')
+ self.b(' initialized = TRUE;')
+ self.b(' %s%s_base_init_once (klass);'
+ % (self.prefix_, node_name_lc))
+ self.b(' }')
+ # insert anything we need to do per implementation here
+ self.b('}')
+
+ self.h('')
+
+ self.b('static const DBusGMethodInfo _%s%s_methods[] = {'
+ % (self.prefix_, node_name_lc))
+
+ method_blob, offsets = self.get_method_glue(methods)
+
+ for method, offset in zip(methods, offsets):
+ self.do_method_glue(method, offset)
+
+ self.b('};')
+ self.b('')
+
+ self.b('static const DBusGObjectInfo _%s%s_object_info = {'
+ % (self.prefix_, node_name_lc))
+ self.b(' 0,') # version
+ self.b(' _%s%s_methods,' % (self.prefix_, node_name_lc))
+ self.b(' %d,' % len(methods))
+ self.b('"' + method_blob.replace('\0', '\\0') + '",')
+ self.b('"' + self.get_signal_glue(signals).replace('\0', '\\0') + '",')
+ self.b('"' +
+ self.get_property_glue(glue_properties).replace('\0', '\\0') +
+ '",')
+ self.b('};')
+ self.b('')
+
+ self.node_name_mixed = None
+ self.node_name_lc = None
+ self.node_name_uc = None
+
+ def get_method_glue(self, methods):
+ info = []
+ offsets = []
+
+ for method in methods:
+ offsets.append(len(''.join(info)))
+
+ info.append(self.iface_name + '\0')
+ info.append(method.getAttribute('name') + '\0')
+
+ info.append('A\0') # async
+
+ counter = 0
+ for arg in method.getElementsByTagName('arg'):
+ out = arg.getAttribute('direction') == 'out'
+
+ name = arg.getAttribute('name')
+ if not name:
+ assert out
+ name = 'arg%u' % counter
+ counter += 1
+
+ info.append(name + '\0')
+
+ if out:
+ info.append('O\0')
+ else:
+ info.append('I\0')
+
+ if out:
+ info.append('F\0') # not const
+ info.append('N\0') # not error or return
+ info.append(arg.getAttribute('type') + '\0')
+
+ info.append('\0')
+
+ return ''.join(info) + '\0', offsets
+
+ def do_method_glue(self, method, offset):
+ lc_name = camelcase_to_lower(method.getAttribute('name'))
+
+ marshaller = method_to_glue_marshal_name(method,
+ self.signal_marshal_prefix)
+ wrapper = self.prefix_ + self.node_name_lc + '_' + lc_name
+
+ self.b(" { (GCallback) %s, %s, %d }," % (wrapper, marshaller, offset))
+
+ def get_signal_glue(self, signals):
+ info = []
+
+ for signal in signals:
+ info.append(self.iface_name)
+ info.append(signal.getAttribute('name'))
+
+ return '\0'.join(info) + '\0\0'
+
+ # the implementation can be the same
+ get_property_glue = get_signal_glue
+
+ def get_method_impl_names(self, method):
+ dbus_method_name = method.getAttribute('name')
+ class_member_name = camelcase_to_lower(dbus_method_name)
+ stub_name = (self.prefix_ + self.node_name_lc + '_' +
+ class_member_name)
+ return (stub_name + '_impl', class_member_name)
+
+ def do_method(self, method):
+ assert self.node_name_mixed is not None
+
+ in_class = []
+
+ # Examples refer to Thing.DoStuff (su) -> ii
+
+ # DoStuff
+ dbus_method_name = method.getAttribute('name')
+ # do_stuff
+ class_member_name = camelcase_to_lower(dbus_method_name)
+ # void tp_svc_thing_do_stuff (TpSvcThing *, const char *, guint,
+ # DBusGMethodInvocation *);
+ stub_name = (self.prefix_ + self.node_name_lc + '_' +
+ class_member_name)
+ # typedef void (*tp_svc_thing_do_stuff_impl) (TpSvcThing *,
+ # const char *, guint, DBusGMethodInvocation);
+ impl_name = stub_name + '_impl'
+ # void tp_svc_thing_return_from_do_stuff (DBusGMethodInvocation *,
+ # gint, gint);
+ ret_name = (self.prefix_ + self.node_name_lc + '_return_from_' +
+ class_member_name)
+
+ # Gather arguments
+ in_args = []
+ out_args = []
+ for i in method.getElementsByTagName('arg'):
+ name = i.getAttribute('name')
+ direction = i.getAttribute('direction') or 'in'
+ dtype = i.getAttribute('type')
+
+ assert direction in ('in', 'out')
+
+ if name:
+ name = direction + '_' + name
+ elif direction == 'in':
+ name = direction + str(len(in_args))
+ else:
+ name = direction + str(len(out_args))
+
+ ctype, gtype, marshaller, pointer = type_to_gtype(dtype)
+
+ if pointer:
+ ctype = 'const ' + ctype
+
+ struct = (ctype, name)
+
+ if direction == 'in':
+ in_args.append(struct)
+ else:
+ out_args.append(struct)
+
+ # Implementation type declaration (in header, docs in body)
+ self.b('/**')
+ self.b(' * %s:' % impl_name)
+ self.b(' * @self: The object implementing this interface')
+ for (ctype, name) in in_args:
+ self.b(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.b(' * @context: Used to return values or throw an error')
+ self.b(' *')
+ self.b(' * The signature of an implementation of the D-Bus method')
+ self.b(' * %s on interface %s.' % (dbus_method_name, self.iface_name))
+ self.b(' */')
+ self.h('typedef void (*%s) (%s%s *self,'
+ % (impl_name, self.Prefix, self.node_name_mixed))
+ for (ctype, name) in in_args:
+ self.h(' %s%s,' % (ctype, name))
+ self.h(' DBusGMethodInvocation *context);')
+
+ # Class member (in class definition)
+ in_class.append(' %s %s;' % (impl_name, class_member_name))
+
+ # Stub definition (in body only - it's static)
+ self.b('static void')
+ self.b('%s (%s%s *self,'
+ % (stub_name, self.Prefix, self.node_name_mixed))
+ for (ctype, name) in in_args:
+ self.b(' %s%s,' % (ctype, name))
+ self.b(' DBusGMethodInvocation *context)')
+ self.b('{')
+ self.b(' %s impl = (%s%s_GET_CLASS (self)->%s);'
+ % (impl_name, self.PREFIX_, self.node_name_uc, class_member_name))
+ self.b('')
+ self.b(' if (impl != NULL)')
+ tmp = ['self'] + [name for (ctype, name) in in_args] + ['context']
+ self.b(' {')
+ self.b(' (impl) (%s);' % ',\n '.join(tmp))
+ self.b(' }')
+ self.b(' else')
+ self.b(' {')
+ if self.not_implemented_func:
+ self.b(' %s (context);' % self.not_implemented_func)
+ else:
+ self.b(' GError e = { DBUS_GERROR, ')
+ self.b(' DBUS_GERROR_UNKNOWN_METHOD,')
+ self.b(' "Method not implemented" };')
+ self.b('')
+ self.b(' dbus_g_method_return_error (context, &e);')
+ self.b(' }')
+ self.b('}')
+ self.b('')
+
+ # Implementation registration (in both header and body)
+ self.h('void %s%s_implement_%s (%s%sClass *klass, %s impl);'
+ % (self.prefix_, self.node_name_lc, class_member_name,
+ self.Prefix, self.node_name_mixed, impl_name))
+
+ self.b('/**')
+ self.b(' * %s%s_implement_%s:'
+ % (self.prefix_, self.node_name_lc, class_member_name))
+ self.b(' * @klass: A class whose instances implement this interface')
+ self.b(' * @impl: A callback used to implement the %s D-Bus method'
+ % dbus_method_name)
+ self.b(' *')
+ self.b(' * Register an implementation for the %s method in the vtable'
+ % dbus_method_name)
+ self.b(' * of an implementation of this interface. To be called from')
+ self.b(' * the interface init function.')
+ self.b(' */')
+ self.b('void')
+ self.b('%s%s_implement_%s (%s%sClass *klass, %s impl)'
+ % (self.prefix_, self.node_name_lc, class_member_name,
+ self.Prefix, self.node_name_mixed, impl_name))
+ self.b('{')
+ self.b(' klass->%s = impl;' % class_member_name)
+ self.b('}')
+ self.b('')
+
+ # Return convenience function (static inline, in header)
+ self.h('/**')
+ self.h(' * %s:' % ret_name)
+ self.h(' * @context: The D-Bus method invocation context')
+ for (ctype, name) in out_args:
+ self.h(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.h(' *')
+ self.h(' * Return successfully by calling dbus_g_method_return().')
+ self.h(' * This inline function exists only to provide type-safety.')
+ self.h(' */')
+ tmp = (['DBusGMethodInvocation *context'] +
+ [ctype + name for (ctype, name) in out_args])
+ self.h('static inline')
+ self.h('/* this comment is to stop gtkdoc realising this is static */')
+ self.h(('void %s (' % ret_name) + (',\n '.join(tmp)) + ');')
+ self.h('static inline void')
+ self.h(('%s (' % ret_name) + (',\n '.join(tmp)) + ')')
+ self.h('{')
+ tmp = ['context'] + [name for (ctype, name) in out_args]
+ self.h(' dbus_g_method_return (' + ',\n '.join(tmp) + ');')
+ self.h('}')
+ self.h('')
+
+ return in_class
+
+ def get_signal_const_entry(self, signal):
+ assert self.node_name_uc is not None
+ return ('SIGNAL_%s_%s'
+ % (self.node_name_uc, signal.getAttribute('name')))
+
+ def do_signal(self, signal):
+ assert self.node_name_mixed is not None
+
+ in_base_init = []
+
+ # for signal: Thing::StuffHappened (s, u)
+ # we want to emit:
+ # void tp_svc_thing_emit_stuff_happened (gpointer instance,
+ # const char *arg0, guint arg1);
+
+ dbus_name = signal.getAttribute('name')
+ stub_name = (self.prefix_ + self.node_name_lc + '_emit_' +
+ camelcase_to_lower(dbus_name))
+ const_name = self.get_signal_const_entry(signal)
+
+ # Gather arguments
+ args = []
+ for i in signal.getElementsByTagName('arg'):
+ name = i.getAttribute('name')
+ dtype = i.getAttribute('type')
+ tp_type = i.getAttribute('tp:type')
+
+ if name:
+ name = 'arg_' + name
+ else:
+ name = 'arg' + str(len(args))
+
+ ctype, gtype, marshaller, pointer = type_to_gtype(dtype)
+
+ if pointer:
+ ctype = 'const ' + ctype
+
+ struct = (ctype, name, gtype)
+ args.append(struct)
+
+ tmp = (['gpointer instance'] +
+ [ctype + name for (ctype, name, gtype) in args])
+
+ self.h(('void %s (' % stub_name) + (',\n '.join(tmp)) + ');')
+
+ # FIXME: emit docs
+
+ self.b('/**')
+ self.b(' * %s:' % stub_name)
+ self.b(' * @instance: The object implementing this interface')
+ for (ctype, name, gtype) in args:
+ self.b(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.b(' *')
+ self.b(' * Type-safe wrapper around g_signal_emit to emit the')
+ self.b(' * %s signal on interface %s.'
+ % (dbus_name, self.iface_name))
+ self.b(' */')
+
+ self.b('void')
+ self.b(('%s (' % stub_name) + (',\n '.join(tmp)) + ')')
+ self.b('{')
+ self.b(' g_assert (instance != NULL);')
+ self.b(' g_assert (G_TYPE_CHECK_INSTANCE_TYPE (instance, %s));'
+ % (self.current_gtype))
+ tmp = (['instance', '%s_signals[%s]' % (self.node_name_lc, const_name),
+ '0'] + [name for (ctype, name, gtype) in args])
+ self.b(' g_signal_emit (' + ',\n '.join(tmp) + ');')
+ self.b('}')
+ self.b('')
+
+ in_base_init.append(' %s_signals[%s] ='
+ % (self.node_name_lc, const_name))
+ in_base_init.append(' g_signal_new ("%s",'
+ % (dbus_gutils_wincaps_to_uscore(dbus_name).replace('_', '-')))
+ in_base_init.append(' G_OBJECT_CLASS_TYPE (klass),')
+ in_base_init.append(' G_SIGNAL_RUN_LAST|G_SIGNAL_DETAILED,')
+ in_base_init.append(' 0,')
+ in_base_init.append(' NULL, NULL,')
+ in_base_init.append(' %s,'
+ % signal_to_marshal_name(signal, self.signal_marshal_prefix))
+ in_base_init.append(' G_TYPE_NONE,')
+ tmp = ['%d' % len(args)] + [gtype for (ctype, name, gtype) in args]
+ in_base_init.append(' %s);' % ',\n '.join(tmp))
+ in_base_init.append('')
+
+ return in_base_init
+
+ def __call__(self):
+ self.h('#include <glib-object.h>')
+ self.h('#include <dbus/dbus-glib.h>')
+ self.h('#include <telepathy-glib/dbus-properties-mixin.h>')
+ self.h('')
+ self.h('G_BEGIN_DECLS')
+ self.h('')
+
+ self.b('#include "%s.h"' % basename)
+ self.b('')
+ for header in self.headers:
+ self.b('#include %s' % header)
+ self.b('')
+
+ nodes = self.dom.getElementsByTagName('node')
+ nodes.sort(cmp_by_name)
+
+ for node in nodes:
+ self.do_node(node)
+
+ self.h('')
+ self.h('G_END_DECLS')
+
+ self.b('')
+ for header in self.end_headers:
+ self.b('#include %s' % header)
+
+ self.h('')
+ self.b('')
+ open(basename + '.h', 'w').write('\n'.join(self.__header))
+ open(basename + '.c', 'w').write('\n'.join(self.__body))
+
+
+def cmdline_error():
+ print """\
+usage:
+ gen-ginterface [OPTIONS] xmlfile Prefix_
+options:
+ --include='<header.h>' (may be repeated)
+ --include='"header.h"' (ditto)
+ --include-end='"header.h"' (ditto)
+ Include extra headers in the generated .c file
+ --signal-marshal-prefix='prefix'
+ Use the given prefix on generated signal marshallers (default is
+ prefix.lower()).
+ --filename='BASENAME'
+ Set the basename for the output files (default is prefix.lower()
+ + 'ginterfaces')
+ --not-implemented-func='symbol'
+ Set action when methods not implemented in the interface vtable are
+ called. symbol must have signature
+ void symbol (DBusGMethodInvocation *context)
+ and return some sort of "not implemented" error via
+ dbus_g_method_return_error (context, ...)
+"""
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ from getopt import gnu_getopt
+
+ options, argv = gnu_getopt(sys.argv[1:], '',
+ ['filename=', 'signal-marshal-prefix=',
+ 'include=', 'include-end=',
+ 'allow-unstable',
+ 'not-implemented-func='])
+
+ try:
+ prefix = argv[1]
+ except IndexError:
+ cmdline_error()
+
+ basename = prefix.lower() + 'ginterfaces'
+ signal_marshal_prefix = prefix.lower().rstrip('_')
+ headers = []
+ end_headers = []
+ not_implemented_func = ''
+ allow_havoc = False
+
+ for option, value in options:
+ if option == '--filename':
+ basename = value
+ elif option == '--signal-marshal-prefix':
+ signal_marshal_prefix = value
+ elif option == '--include':
+ if value[0] not in '<"':
+ value = '"%s"' % value
+ headers.append(value)
+ elif option == '--include-end':
+ if value[0] not in '<"':
+ value = '"%s"' % value
+ end_headers.append(value)
+ elif option == '--not-implemented-func':
+ not_implemented_func = value
+ elif option == '--allow-unstable':
+ allow_havoc = True
+
+ try:
+ dom = xml.dom.minidom.parse(argv[0])
+ except IndexError:
+ cmdline_error()
+
+ Generator(dom, prefix, basename, signal_marshal_prefix, headers,
+ end_headers, not_implemented_func, allow_havoc)()
diff --git a/salut/tools/glib-gtypes-generator.py b/salut/tools/glib-gtypes-generator.py
new file mode 100644
index 000000000..fcb46e841
--- /dev/null
+++ b/salut/tools/glib-gtypes-generator.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+
+# Generate GLib GInterfaces from the Telepathy specification.
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (C) 2006, 2007 Collabora Limited
+#
+# 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import xml.dom.minidom
+
+from libglibcodegen import escape_as_identifier, \
+ get_docstring, \
+ NS_TP, \
+ Signature, \
+ type_to_gtype, \
+ xml_escape
+
+
+def types_to_gtypes(types):
+ return [type_to_gtype(t)[1] for t in types]
+
+
+class GTypesGenerator(object):
+ def __init__(self, dom, output, mixed_case_prefix):
+ self.dom = dom
+ self.Prefix = mixed_case_prefix
+ self.PREFIX_ = self.Prefix.upper() + '_'
+ self.prefix_ = self.Prefix.lower() + '_'
+
+ self.header = open(output + '.h', 'w')
+ self.body = open(output + '-body.h', 'w')
+
+ for f in (self.header, self.body):
+ f.write('/* Auto-generated, do not edit.\n *\n'
+ ' * This file may be distributed under the same terms\n'
+ ' * as the specification from which it was generated.\n'
+ ' */\n\n')
+
+ self.need_mappings = {}
+ self.need_structs = {}
+ self.need_arrays = {}
+
+ def do_mapping_header(self, mapping):
+ members = mapping.getElementsByTagNameNS(NS_TP, 'member')
+ assert len(members) == 2
+
+ impl_sig = ''.join([elt.getAttribute('type')
+ for elt in members])
+
+ esc_impl_sig = escape_as_identifier(impl_sig)
+
+ name = (self.PREFIX_ + 'HASH_TYPE_' +
+ mapping.getAttribute('name').upper())
+ impl = self.prefix_ + 'type_dbus_hash_' + esc_impl_sig
+
+ docstring = get_docstring(mapping) or '(Undocumented)'
+
+ self.header.write('/**\n * %s:\n *\n' % name)
+ self.header.write(' * %s\n' % xml_escape(docstring))
+ self.header.write(' *\n')
+ self.header.write(' * This macro expands to a call to a function\n')
+ self.header.write(' * that returns the #GType of a #GHashTable\n')
+ self.header.write(' * appropriate for representing a D-Bus\n')
+ self.header.write(' * dictionary of signature\n')
+ self.header.write(' * <literal>a{%s}</literal>.\n' % impl_sig)
+ self.header.write(' *\n')
+
+ key, value = members
+
+ self.header.write(' * Keys (D-Bus type <literal>%s</literal>,\n'
+ % key.getAttribute('type'))
+ tp_type = key.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.header.write(' * type <literal>%s</literal>,\n' % tp_type)
+ self.header.write(' * named <literal>%s</literal>):\n'
+ % key.getAttribute('name'))
+ docstring = get_docstring(key) or '(Undocumented)'
+ self.header.write(' * %s\n' % xml_escape(docstring))
+ self.header.write(' *\n')
+
+ self.header.write(' * Values (D-Bus type <literal>%s</literal>,\n'
+ % value.getAttribute('type'))
+ tp_type = value.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.header.write(' * type <literal>%s</literal>,\n' % tp_type)
+ self.header.write(' * named <literal>%s</literal>):\n'
+ % value.getAttribute('name'))
+ docstring = get_docstring(value) or '(Undocumented)'
+ self.header.write(' * %s\n' % xml_escape(docstring))
+ self.header.write(' *\n')
+
+ self.header.write(' */\n')
+
+ self.header.write('#define %s (%s ())\n\n' % (name, impl))
+ self.need_mappings[impl_sig] = esc_impl_sig
+
+ def do_struct_header(self, struct):
+ members = struct.getElementsByTagNameNS(NS_TP, 'member')
+ impl_sig = ''.join([elt.getAttribute('type') for elt in members])
+ esc_impl_sig = escape_as_identifier(impl_sig)
+
+ name = (self.PREFIX_ + 'STRUCT_TYPE_' +
+ struct.getAttribute('name').upper())
+ impl = self.prefix_ + 'type_dbus_struct_' + esc_impl_sig
+ docstring = struct.getElementsByTagNameNS(NS_TP, 'docstring')
+ if docstring:
+ docstring = docstring[0].toprettyxml()
+ if docstring.startswith('<tp:docstring>'):
+ docstring = docstring[14:]
+ if docstring.endswith('</tp:docstring>\n'):
+ docstring = docstring[:-16]
+ if docstring.strip() in ('<tp:docstring/>', ''):
+ docstring = '(Undocumented)'
+ else:
+ docstring = '(Undocumented)'
+ self.header.write('/**\n * %s:\n\n' % name)
+ self.header.write(' * %s\n' % xml_escape(docstring))
+ self.header.write(' *\n')
+ self.header.write(' * This macro expands to a call to a function\n')
+ self.header.write(' * that returns the #GType of a #GValueArray\n')
+ self.header.write(' * appropriate for representing a D-Bus struct\n')
+ self.header.write(' * with signature <literal>(%s)</literal>.\n'
+ % impl_sig)
+ self.header.write(' *\n')
+
+ for i, member in enumerate(members):
+ self.header.write(' * Member %d (D-Bus type '
+ '<literal>%s</literal>,\n'
+ % (i, member.getAttribute('type')))
+ tp_type = member.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.header.write(' * type <literal>%s</literal>,\n' % tp_type)
+ self.header.write(' * named <literal>%s</literal>):\n'
+ % member.getAttribute('name'))
+ docstring = get_docstring(member) or '(Undocumented)'
+ self.header.write(' * %s\n' % xml_escape(docstring))
+ self.header.write(' *\n')
+
+ self.header.write(' */\n')
+ self.header.write('#define %s (%s ())\n\n' % (name, impl))
+
+ array_name = struct.getAttribute('array-name')
+ if array_name != '':
+ array_name = (self.PREFIX_ + 'ARRAY_TYPE_' + array_name.upper())
+ impl = self.prefix_ + 'type_dbus_array_' + esc_impl_sig
+ self.header.write('/**\n * %s:\n\n' % array_name)
+ self.header.write(' * Expands to a call to a function\n')
+ self.header.write(' * that returns the #GType of a #GPtrArray\n')
+ self.header.write(' * of #%s.\n' % name)
+ self.header.write(' */\n')
+ self.header.write('#define %s (%s ())\n\n' % (array_name, impl))
+ self.need_arrays[impl_sig] = esc_impl_sig
+
+ self.need_structs[impl_sig] = esc_impl_sig
+
+ def __call__(self):
+ mappings = self.dom.getElementsByTagNameNS(NS_TP, 'mapping')
+ structs = self.dom.getElementsByTagNameNS(NS_TP, 'struct')
+
+ for mapping in mappings:
+ self.do_mapping_header(mapping)
+
+ for sig in self.need_mappings:
+ self.header.write('GType %stype_dbus_hash_%s (void);\n\n' %
+ (self.prefix_, self.need_mappings[sig]))
+ self.body.write('GType\n%stype_dbus_hash_%s (void)\n{\n' %
+ (self.prefix_, self.need_mappings[sig]))
+ self.body.write(' static GType t = 0;\n\n')
+ self.body.write(' if (G_UNLIKELY (t == 0))\n')
+ # FIXME: translate sig into two GTypes
+ items = tuple(Signature(sig))
+ gtypes = types_to_gtypes(items)
+ self.body.write(' t = dbus_g_type_get_map ("GHashTable", '
+ '%s, %s);\n' % (gtypes[0], gtypes[1]))
+ self.body.write(' return t;\n')
+ self.body.write('}\n\n')
+
+ for struct in structs:
+ self.do_struct_header(struct)
+
+ for sig in self.need_structs:
+ self.header.write('GType %stype_dbus_struct_%s (void);\n\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.body.write('GType\n%stype_dbus_struct_%s (void)\n{\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.body.write(' static GType t = 0;\n\n')
+ self.body.write(' if (G_UNLIKELY (t == 0))\n')
+ self.body.write(' t = dbus_g_type_get_struct ("GValueArray",\n')
+ items = tuple(Signature(sig))
+ gtypes = types_to_gtypes(items)
+ for gtype in gtypes:
+ self.body.write(' %s,\n' % gtype)
+ self.body.write(' G_TYPE_INVALID);\n')
+ self.body.write(' return t;\n')
+ self.body.write('}\n\n')
+
+ for sig in self.need_arrays:
+ self.header.write('GType %stype_dbus_array_%s (void);\n\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.body.write('GType\n%stype_dbus_array_%s (void)\n{\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.body.write(' static GType t = 0;\n\n')
+ self.body.write(' if (G_UNLIKELY (t == 0))\n')
+ self.body.write(' t = dbus_g_type_get_collection ("GPtrArray", '
+ '%stype_dbus_struct_%s ());\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.body.write(' return t;\n')
+ self.body.write('}\n\n')
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+
+ dom = xml.dom.minidom.parse(argv[0])
+
+ GTypesGenerator(dom, argv[1], argv[2])()
diff --git a/salut/tools/glib-interfaces-body-generator.xsl b/salut/tools/glib-interfaces-body-generator.xsl
new file mode 100644
index 000000000..caff8917a
--- /dev/null
+++ b/salut/tools/glib-interfaces-body-generator.xsl
@@ -0,0 +1,47 @@
+<!-- Stylesheet to extract C interface names from the Telepathy spec.
+The master copy of this stylesheet is in telepathy-glib - please make any
+changes there.
+
+Copyright (C) 2006, 2007 Collabora Limited
+
+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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ exclude-result-prefixes="tp">
+
+ <xsl:import href="c-interfaces-generator.xsl"/>
+
+ <xsl:template match="interface">
+ <xsl:text>GQuark&#10;</xsl:text>
+ <xsl:value-of select="$prefix"/>
+ <xsl:text>_iface_quark_</xsl:text>
+ <xsl:value-of select="translate(../@name, concat($upper, '/'), $lower)"/>
+ <xsl:text> (void)&#10;{&#10;</xsl:text>
+ <xsl:text> static GQuark quark = 0;&#10;&#10;</xsl:text>
+ <xsl:text> if (G_UNLIKELY (quark == 0))&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> quark = g_quark_from_static_string ("</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>");&#10;</xsl:text>
+ <xsl:text> }&#10;&#10;</xsl:text>
+ <xsl:text> return quark;&#10;</xsl:text>
+ <xsl:text>}&#10;&#10;</xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et noai noci: -->
diff --git a/salut/tools/glib-interfaces-generator.xsl b/salut/tools/glib-interfaces-generator.xsl
new file mode 100644
index 000000000..e703c407e
--- /dev/null
+++ b/salut/tools/glib-interfaces-generator.xsl
@@ -0,0 +1,55 @@
+<!-- Stylesheet to extract C interface names from the Telepathy spec.
+The master copy of this stylesheet is in telepathy-glib - please make any
+changes there.
+
+Copyright (C) 2006, 2007 Collabora Limited
+
+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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ exclude-result-prefixes="tp">
+
+ <xsl:import href="c-interfaces-generator.xsl"/>
+
+ <xsl:template match="interface">
+ <xsl:apply-imports/>
+
+ <xsl:text>/**&#10; * </xsl:text>
+ <xsl:value-of select="$PREFIX"/>
+ <xsl:text>_IFACE_QUARK_</xsl:text>
+ <xsl:value-of select="translate(../@name, concat($lower, '/'), $upper)"/>
+ <xsl:text>:&#10; * &#10; * Expands to a call to a function that </xsl:text>
+ <xsl:text>returns a quark for the interface name "</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>"&#10; */&#10;#define </xsl:text>
+ <xsl:value-of select="$PREFIX"/>
+ <xsl:text>_IFACE_QUARK_</xsl:text>
+ <xsl:value-of select="translate(../@name, concat($lower, '/'), $upper)"/>
+ <xsl:text> \&#10; (</xsl:text>
+ <xsl:value-of select="$prefix"/>
+ <xsl:text>_iface_quark_</xsl:text>
+ <xsl:value-of select="translate(../@name, concat($upper, '/'), $lower)"/>
+ <xsl:text> ())&#10;&#10;GQuark </xsl:text>
+ <xsl:value-of select="$prefix"/>
+ <xsl:text>_iface_quark_</xsl:text>
+ <xsl:value-of select="translate(../@name, concat($upper, '/'), $lower)"/>
+ <xsl:text> (void);&#10;&#10;</xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et noai noci: -->
diff --git a/salut/tools/glib-signals-marshal-gen.py b/salut/tools/glib-signals-marshal-gen.py
new file mode 100644
index 000000000..0d02c1341
--- /dev/null
+++ b/salut/tools/glib-signals-marshal-gen.py
@@ -0,0 +1,55 @@
+#!/usr/bin/python
+
+import sys
+import xml.dom.minidom
+from string import ascii_letters, digits
+
+
+from libglibcodegen import signal_to_marshal_name, method_to_glue_marshal_name
+
+
+class Generator(object):
+
+ def __init__(self, dom):
+ self.dom = dom
+ self.marshallers = {}
+
+ def do_method(self, method):
+ marshaller = method_to_glue_marshal_name(method, 'PREFIX')
+
+ assert '__' in marshaller
+ rhs = marshaller.split('__', 1)[1].split('_')
+
+ self.marshallers[marshaller] = rhs
+
+ def do_signal(self, signal):
+ marshaller = signal_to_marshal_name(signal, 'PREFIX')
+
+ assert '__' in marshaller
+ rhs = marshaller.split('__', 1)[1].split('_')
+
+ self.marshallers[marshaller] = rhs
+
+ def __call__(self):
+ methods = self.dom.getElementsByTagName('method')
+
+ for method in methods:
+ self.do_method(method)
+
+ signals = self.dom.getElementsByTagName('signal')
+
+ for signal in signals:
+ self.do_signal(signal)
+
+ all = self.marshallers.keys()
+ all.sort()
+ for marshaller in all:
+ rhs = self.marshallers[marshaller]
+ if not marshaller.startswith('g_cclosure'):
+ print 'VOID:' + ','.join(rhs)
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+ dom = xml.dom.minidom.parse(argv[0])
+
+ Generator(dom)()
diff --git a/salut/tools/identity.xsl b/salut/tools/identity.xsl
new file mode 100644
index 000000000..6630f84de
--- /dev/null
+++ b/salut/tools/identity.xsl
@@ -0,0 +1,7 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/salut/tools/libglibcodegen.py b/salut/tools/libglibcodegen.py
new file mode 100644
index 000000000..d465b7406
--- /dev/null
+++ b/salut/tools/libglibcodegen.py
@@ -0,0 +1,320 @@
+"""Library code for GLib/D-Bus-related code generation.
+
+The master copy of this library is in the telepathy-glib repository -
+please make any changes there.
+"""
+
+# Copyright (C) 2006, 2007 Collabora Limited
+#
+# 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from string import ascii_letters, digits
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+_ASCII_ALNUM = ascii_letters + digits
+
+
+def camelcase_to_lower(s):
+ out ="";
+ out += s[0].lower()
+ last_upper=False
+ if s[0].isupper():
+ last_upper=True
+ for i in range(1,len(s)):
+ if s[i].isupper():
+ if last_upper:
+ if (i+1) < len(s) and s[i+1].islower():
+ out += "_" + s[i].lower()
+ else:
+ out += s[i].lower()
+ else:
+ out += "_" + s[i].lower()
+ last_upper=True
+ else:
+ out += s[i]
+ last_upper=False
+ return out
+
+
+def camelcase_to_upper(s):
+ return camelcase_to_lower(s).upper()
+
+
+def cmp_by_name(node1, node2):
+ return cmp(node1.getAttributeNode("name").nodeValue,
+ node2.getAttributeNode("name").nodeValue)
+
+
+def dbus_gutils_wincaps_to_uscore(s):
+ """Bug-for-bug compatible Python port of _dbus_gutils_wincaps_to_uscore
+ which gets sequences of capital letters wrong in the same way.
+ (e.g. in Telepathy, SendDTMF -> send_dt_mf)
+ """
+ ret = ''
+ for c in s:
+ if c >= 'A' and c <= 'Z':
+ length = len(ret)
+ if length > 0 and (length < 2 or ret[length-2] != '_'):
+ ret += '_'
+ ret += c.lower()
+ else:
+ ret += c
+ return ret
+
+
+def escape_as_identifier(identifier):
+ """Escape the given string to be a valid D-Bus object path or service
+ name component, using a reversible encoding to ensure uniqueness.
+
+ The reversible encoding is as follows:
+
+ * The empty string becomes '_'
+ * Otherwise, each non-alphanumeric character is replaced by '_' plus
+ two lower-case hex digits; the same replacement is carried out on
+ the first character, if it's a digit
+ """
+ # '' -> '_'
+ if not identifier:
+ return '_'
+
+ # A bit of a fast path for strings which are already OK.
+ # We deliberately omit '_' because, for reversibility, that must also
+ # be escaped.
+ if (identifier.strip(_ASCII_ALNUM) == '' and
+ identifier[0] in ascii_letters):
+ return identifier
+
+ # The first character may not be a digit
+ if identifier[0] not in ascii_letters:
+ ret = ['_%02x' % ord(identifier[0])]
+ else:
+ ret = [identifier[0]]
+
+ # Subsequent characters may be digits or ASCII letters
+ for c in identifier[1:]:
+ if c in _ASCII_ALNUM:
+ ret.append(c)
+ else:
+ ret.append('_%02x' % ord(c))
+
+ return ''.join(ret)
+
+
+def get_docstring(element):
+ docstring = None
+ for x in element.childNodes:
+ if x.namespaceURI == NS_TP and x.localName == 'docstring':
+ docstring = x
+ if docstring is not None:
+ docstring = docstring.toxml().replace('\n', ' ').strip()
+ if docstring.startswith('<tp:docstring>'):
+ docstring = docstring[14:].lstrip()
+ if docstring.endswith('</tp:docstring>'):
+ docstring = docstring[:-15].rstrip()
+ if docstring in ('<tp:docstring/>', ''):
+ docstring = ''
+ return docstring
+
+
+def signal_to_marshal_type(signal):
+ """
+ return a list of strings indicating the marshalling type for this signal.
+ """
+
+ mtype=[]
+ for i in signal.getElementsByTagName("arg"):
+ name =i.getAttribute("name")
+ type = i.getAttribute("type")
+ mtype.append(type_to_gtype(type)[2])
+
+ return mtype
+
+
+_glib_marshallers = ['VOID', 'BOOLEAN', 'CHAR', 'UCHAR', 'INT',
+ 'STRING', 'UINT', 'LONG', 'ULONG', 'ENUM', 'FLAGS', 'FLOAT',
+ 'DOUBLE', 'STRING', 'PARAM', 'BOXED', 'POINTER', 'OBJECT',
+ 'UINT_POINTER']
+
+
+def signal_to_marshal_name(signal, prefix):
+
+ mtype = signal_to_marshal_type(signal)
+ if len(mtype):
+ name = '_'.join(mtype)
+ else:
+ name = 'VOID'
+
+ if name in _glib_marshallers:
+ return 'g_cclosure_marshal_VOID__' + name
+ else:
+ return prefix + '_marshal_VOID__' + name
+
+
+def method_to_glue_marshal_name(method, prefix):
+
+ mtype = []
+ for i in method.getElementsByTagName("arg"):
+ if i.getAttribute("direction") != "out":
+ type = i.getAttribute("type")
+ mtype.append(type_to_gtype(type)[2])
+
+ mtype.append('POINTER')
+
+ name = '_'.join(mtype)
+
+ if name in _glib_marshallers:
+ return 'g_cclosure_marshal_VOID__' + name
+ else:
+ return prefix + '_marshal_VOID__' + name
+
+
+class _SignatureIter:
+ """Iterator over a D-Bus signature. Copied from dbus-python 0.71 so we
+ can run genginterface in a limited environment with only Python
+ (like Scratchbox).
+ """
+ def __init__(self, string):
+ self.remaining = string
+
+ def next(self):
+ if self.remaining == '':
+ raise StopIteration
+
+ signature = self.remaining
+ block_depth = 0
+ block_type = None
+ end = len(signature)
+
+ for marker in range(0, end):
+ cur_sig = signature[marker]
+
+ if cur_sig == 'a':
+ pass
+ elif cur_sig == '{' or cur_sig == '(':
+ if block_type == None:
+ block_type = cur_sig
+
+ if block_type == cur_sig:
+ block_depth = block_depth + 1
+
+ elif cur_sig == '}':
+ if block_type == '{':
+ block_depth = block_depth - 1
+
+ if block_depth == 0:
+ end = marker
+ break
+
+ elif cur_sig == ')':
+ if block_type == '(':
+ block_depth = block_depth - 1
+
+ if block_depth == 0:
+ end = marker
+ break
+
+ else:
+ if block_depth == 0:
+ end = marker
+ break
+
+ end = end + 1
+ self.remaining = signature[end:]
+ return Signature(signature[0:end])
+
+
+class Signature(str):
+ """A string, iteration over which is by D-Bus single complete types
+ rather than characters.
+ """
+ def __iter__(self):
+ return _SignatureIter(self)
+
+
+def type_to_gtype(s):
+ if s == 'y': #byte
+ return ("guchar ", "G_TYPE_UCHAR","UCHAR", False)
+ elif s == 'b': #boolean
+ return ("gboolean ", "G_TYPE_BOOLEAN","BOOLEAN", False)
+ elif s == 'n': #int16
+ return ("gint ", "G_TYPE_INT","INT", False)
+ elif s == 'q': #uint16
+ return ("guint ", "G_TYPE_UINT","UINT", False)
+ elif s == 'i': #int32
+ return ("gint ", "G_TYPE_INT","INT", False)
+ elif s == 'u': #uint32
+ return ("guint ", "G_TYPE_UINT","UINT", False)
+ elif s == 'x': #int64
+ return ("gint64 ", "G_TYPE_INT64","INT64", False)
+ elif s == 't': #uint64
+ return ("guint64 ", "G_TYPE_UINT64","UINT64", False)
+ elif s == 'd': #double
+ return ("gdouble ", "G_TYPE_DOUBLE","DOUBLE", False)
+ elif s == 's': #string
+ return ("gchar *", "G_TYPE_STRING", "STRING", True)
+ elif s == 'g': #signature - FIXME
+ return ("gchar *", "DBUS_TYPE_G_SIGNATURE", "STRING", True)
+ elif s == 'o': #object path
+ return ("gchar *", "DBUS_TYPE_G_OBJECT_PATH", "BOXED", True)
+ elif s == 'v': #variant
+ return ("GValue *", "G_TYPE_VALUE", "BOXED", True)
+ elif s == 'as': #array of strings
+ return ("gchar **", "G_TYPE_STRV", "BOXED", True)
+ elif s == 'ay': #byte array
+ return ("GArray *",
+ "dbus_g_type_get_collection (\"GArray\", G_TYPE_UCHAR)", "BOXED",
+ True)
+ elif s == 'au': #uint array
+ return ("GArray *", "DBUS_TYPE_G_UINT_ARRAY", "BOXED", True)
+ elif s == 'ai': #int array
+ return ("GArray *", "DBUS_TYPE_G_INT_ARRAY", "BOXED", True)
+ elif s == 'ax': #int64 array
+ return ("GArray *", "DBUS_TYPE_G_INT64_ARRAY", "BOXED", True)
+ elif s == 'at': #uint64 array
+ return ("GArray *", "DBUS_TYPE_G_UINT64_ARRAY", "BOXED", True)
+ elif s == 'ad': #double array
+ return ("GArray *", "DBUS_TYPE_G_DOUBLE_ARRAY", "BOXED", True)
+ elif s == 'ab': #boolean array
+ return ("GArray *", "DBUS_TYPE_G_BOOLEAN_ARRAY", "BOXED", True)
+ elif s == 'ao': #object path array
+ return ("GPtrArray *", "DBUS_TYPE_G_OBJECT_ARRAY", "BOXED", True)
+ elif s == 'a{ss}': #hash table of string to string
+ return ("GHashTable *", "DBUS_TYPE_G_STRING_STRING_HASHTABLE", "BOXED", False)
+ elif s[:2] == 'a{': #some arbitrary hash tables
+ if s[2] not in ('y', 'b', 'n', 'q', 'i', 'u', 's', 'o', 'g'):
+ raise Exception, "can't index a hashtable off non-basic type " + s
+ first = type_to_gtype(s[2])
+ second = type_to_gtype(s[3:-1])
+ return ("GHashTable *", "(dbus_g_type_get_map (\"GHashTable\", " + first[1] + ", " + second[1] + "))", "BOXED", False)
+ elif s[:2] in ('a(', 'aa'): # array of structs or arrays, recurse
+ gtype = type_to_gtype(s[1:])[1]
+ return ("GPtrArray *", "(dbus_g_type_get_collection (\"GPtrArray\", "+gtype+"))", "BOXED", True)
+ elif s[:1] == '(': #struct
+ gtype = "(dbus_g_type_get_struct (\"GValueArray\", "
+ for subsig in Signature(s[1:-1]):
+ gtype = gtype + type_to_gtype(subsig)[1] + ", "
+ gtype = gtype + "G_TYPE_INVALID))"
+ return ("GValueArray *", gtype, "BOXED", True)
+
+ # we just don't know ..
+ raise Exception, "don't know the GType for " + s
+
+
+def xml_escape(s):
+ s = s.replace('&', '&amp;').replace("'", '&apos;').replace('"', '&quot;')
+ return s.replace('<', '&lt;').replace('>', '&gt;')
diff --git a/salut/tools/make-release-mail.py b/salut/tools/make-release-mail.py
new file mode 100644
index 000000000..5c42b4798
--- /dev/null
+++ b/salut/tools/make-release-mail.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+#
+# Hello. This is make-release-mail.py from the Telepathy project. It's
+# designed to turn an item from a NEWS file into a mail suitable for sending
+# to <telepathy@lists.freedesktop.org>. I hope that you enjoy your stay.
+
+import sys
+
+def extract_description(package, version, news_path):
+ release_name = []
+ details = []
+
+ with open(news_path) as f:
+ lines = (line for line in f.readlines())
+ for line in lines:
+ # Find the 'telepathy-foo 0.1.2' header
+ if line.startswith("%s %s" % (package, version)):
+ break
+
+ # Skip the ====== line, and the first blank line
+ lines.next()
+ lines.next()
+
+ got_release_name = False
+
+ for line in lines:
+ line = line.rstrip()
+ # If we hit the next version header, we're done
+ if line.startswith(package):
+ break
+ # Else, if we hit a blank line and we're still reading the release
+ # name, we're done with the release name.
+ elif not got_release_name and line == '':
+ got_release_name = True
+ # Otherwise, append this to the relevant list
+ elif not got_release_name:
+ release_name.append(line)
+ else:
+ details.append(line)
+
+ assert got_release_name, (release_name, details)
+
+ # We rstrip details because it picks up a trailing blank line
+ return ('\n'.join(release_name), '\n'.join(details).rstrip())
+
+BASE_URL = 'http://telepathy.freedesktop.org/releases'
+GIT_URL = 'http://cgit.freedesktop.org/telepathy'
+
+def main(package, version, news_path):
+ release_name, details = extract_description(package, version, news_path)
+
+ print """
+%(release_name)s
+
+tarball: %(base_url)s/%(package)s/%(package)s-%(version)s.tar.gz
+signature: %(base_url)s/%(package)s/%(package)s-%(version)s.tar.gz.asc
+git: %(git_url)s/%(package)s
+
+%(details)s""".strip().rstrip() % {
+ 'base_url': BASE_URL,
+ 'git_url': GIT_URL,
+ 'package': package,
+ 'version': version,
+ 'release_name': release_name,
+ 'details': details,
+ }
+
+if __name__ == '__main__':
+ try:
+ package, version, news_path = sys.argv[1:]
+
+ main(package, version, news_path)
+ except ValueError, e:
+ sys.stderr.write(
+ 'Usage: %s package-name package.version.number path/to/NEWS\n' %
+ sys.argv[0])
+ sys.stderr.flush()
+ sys.exit(1)
diff --git a/salut/tools/telepathy.am b/salut/tools/telepathy.am
new file mode 100644
index 000000000..d080afe7e
--- /dev/null
+++ b/salut/tools/telepathy.am
@@ -0,0 +1,74 @@
+## Useful top-level Makefile.am snippets for Telepathy projects.
+
+dist-hook:
+ chmod u+w ${distdir}/ChangeLog
+ if test -d ${top_srcdir}/.git; then \
+ git log --date=iso $(CHANGELOG_RANGE) > ${distdir}/ChangeLog; \
+ fi
+
+distcheck-hook:
+ @test "z$(CHECK_FOR_UNRELEASED)" = z || \
+ case @VERSION@ in \
+ *.*.*.*|*+) ;; \
+ *) \
+ if grep -r UNRELEASED $(CHECK_FOR_UNRELEASED); \
+ then \
+ echo "^^^ This is meant to be a release, but some files say UNRELEASED" >&2; \
+ exit 2; \
+ fi \
+ ;; \
+ esac
+
+_is-release-check:
+ @case @VERSION@ in \
+ (*.*.*.*|*+) \
+ echo "Hey! @VERSION@ is not a release!" >&2; \
+ exit 2; \
+ ;; \
+ esac
+ @if ! git diff --no-ext-diff --quiet --exit-code; then \
+ echo "Hey! Your tree is dirty! No release for you." >&2; \
+ exit 2; \
+ fi
+ @if ! git diff --cached --no-ext-diff --quiet --exit-code; then \
+ echo "Hey! You have changes staged! No release for you." >&2; \
+ exit 2; \
+ fi
+if ENABLE_GTK_DOC
+else
+ @echo "Hey! You need to pass --enable-gtk-doc to configure!"
+ @exit 2;
+endif
+
+%.tar.gz.asc: %.tar.gz
+ $(AM_V_GEN)gpg --detach-sign --armor $@
+
+@PACKAGE@-@VERSION@.tar.gz: _is-release-check check distcheck
+
+maintainer-prepare-release: _is-release-check all distcheck release-mail
+ git tag -s @PACKAGE@-@VERSION@ -m @PACKAGE@' '@VERSION@
+ gpg --detach-sign --armor @PACKAGE@-@VERSION@.tar.gz
+
+release-mail: NEWS
+ $(AM_V_GEN)(python $(top_srcdir)/tools/make-release-mail.py \
+ @PACKAGE@ @VERSION@ $(top_srcdir)/NEWS > $@.tmp && \
+ mv $@.tmp $@)
+
+maintainer-upload-release: _maintainer-upload-release
+
+_maintainer-upload-release-check: _is-release-check
+ test -f @PACKAGE@-@VERSION@.tar.gz
+ test -f @PACKAGE@-@VERSION@.tar.gz.asc
+ gpg --verify @PACKAGE@-@VERSION@.tar.gz.asc
+
+_maintainer-upload-release: _maintainer-upload-release-check
+ rsync -vzP @PACKAGE@-@VERSION@.tar.gz telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz
+ rsync -vzP @PACKAGE@-@VERSION@.tar.gz.asc telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz.asc
+
+maintainer-make-release: maintainer-prepare-release maintainer-upload-release
+ @echo "Now:"
+ @echo " • bump the nano-version;"
+ @echo " • push the branch and tags upstream; and"
+ @echo " • send release-mail to <telepathy@lists.freedesktop.org>."
+
+## vim:set ft=automake:
diff --git a/salut/tools/xep.xsl b/salut/tools/xep.xsl
new file mode 100644
index 000000000..5ea7703e1
--- /dev/null
+++ b/salut/tools/xep.xsl
@@ -0,0 +1,909 @@
+<?xml version='1.0' encoding='UTF-8'?>
+
+<!--
+
+Copyright (c) 1999 - 2008 XMPP Standards Foundation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+-->
+
+<!-- Authors: stpeter and temas -->
+
+<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
+
+ <xsl:output doctype-public='-//W3C//DTD XHTML 1.0 Transitional//EN' doctype-system='http://www.w3.org/TR/xhtml1/DTD/xhtml1-loose.dtd' method='xml'/>
+
+ <xsl:template match='/'>
+ <html>
+ <head>
+ <title>XEP-<xsl:value-of select='/xep/header/number'/>:<xsl:text> </xsl:text><xsl:value-of select='/xep/header/title' /></title>
+ <link rel='stylesheet' type='text/css' href='/xmpp.css' />
+ <link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
+ <!-- BEGIN META TAGS FOR DUBLIN CORE -->
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Title</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'><xsl:value-of select='/xep/header/title'/></xsl:attribute>
+ </meta>
+ <xsl:apply-templates select='/xep/header/author' mode='meta'/>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Description</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'><xsl:value-of select='/xep/header/abstract'/></xsl:attribute>
+ </meta>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Publisher</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'>XMPP Standards Foundation</xsl:attribute>
+ </meta>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Contributor</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'>XMPP Extensions Editor</xsl:attribute>
+ </meta>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Date</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'><xsl:value-of select='/xep/header/revision/date'/></xsl:attribute>
+ </meta>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Type</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'>XMPP Extension Protocol</xsl:attribute>
+ </meta>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Format</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'>XHTML</xsl:attribute>
+ </meta>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Identifier</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'>XEP-<xsl:value-of select='/xep/header/number'/></xsl:attribute>
+ </meta>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Language</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'>en</xsl:attribute>
+ </meta>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Rights</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'><xsl:value-of select='/xep/header/legal/copyright'/></xsl:attribute>
+ </meta>
+ <!-- END META TAGS FOR DUBLIN CORE -->
+ </head>
+ <body>
+ <!-- TITLE -->
+ <h1>XEP-<xsl:value-of select='/xep/header/number' />:<xsl:text> </xsl:text><xsl:value-of select='/xep/header/title' /></h1>
+ <!-- ABSTRACT -->
+ <p><xsl:value-of select='/xep/header/abstract'/></p>
+ <!-- NOTICE -->
+ <hr />
+ <xsl:variable name='thestatus' select='/xep/header/status'/>
+ <xsl:variable name='thetype' select='/xep/header/type'/>
+ <xsl:if test='$thestatus = "Active" and $thetype = "Historical"'>
+ <p style='color:green'>NOTICE: This Historical specification provides canonical documentation of a protocol that is in use within the Jabber/XMPP community. This document is not a standards-track specification within the XMPP Standards Foundation's standards process; however, it may be converted to standards-track in the future or may be obsoleted by a more modern protocol.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Active" and $thetype = "Humorous"'>
+ <p style='color:green'>NOTICE: This document is Humorous. It MAY provide amusement but SHOULD NOT be taken seriously.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Active" and $thetype = "Informational"'>
+ <p style='color:green'>NOTICE: This Informational specification defines a best practice or protocol profile that has been approved by the XMPP Council and/or the XSF Board of Directors. Implementations are encouraged and the best practice or protocol profile is appropriate for deployment in production systems.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Active" and $thetype = "Procedural"'>
+ <p style='color:green'>NOTICE: This Procedural document defines a process or activity of the XMPP Standards Foundation (XSF) that has been approved by the XMPP Council and/or the XSF Board of Directors. The XSF is currently following the process or activity defined herein and will do so until this document is deprecated or obsoleted.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Deferred"'>
+ <p style='color:red'>WARNING: Consideration of this document has been Deferred by the XMPP Standards Foundation. Implementation of the protocol described herein is not recommended.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Deprecated"'>
+ <p style='color:red'>WARNING: This document has been deprecated by the XMPP Standards Foundation. Implementation of the protocol described herein is not recommended. Developers desiring similar functionality should implement the protocol that supersedes this one (if any).</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Draft"'>
+ <p style='color:green'>NOTICE: The protocol defined herein is a Draft Standard of the XMPP Standards Foundation. Implementations are encouraged and the protocol is appropriate for deployment in production systems, but some changes to the protocol are possible before it becomes a Final Standard.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Experimental" and $thetype = "Historical"'>
+ <p style='color:red'>NOTICE: This Historical document attempts to provide canonical documentation of a protocol that is in use within the Jabber/XMPP community. Publication as an XMPP Extension Protocol does not imply approval of this proposal by the XMPP Standards Foundation. This document is not a standards-track specification within the XMPP Standards Foundation's standards process; however, it may be converted to standards-track in the future or may be obsoleted by a more modern protocol.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Experimental" and $thetype = "Informational"'>
+ <p style='color:red'>WARNING: This Informational document is Experimental. Publication as an XMPP Extension Protocol does not imply approval of this proposal by the XMPP Standards Foundation. Implementation of the best practice or protocol profile described herein is encouraged in exploratory implementations, although production systems should not deploy implementations of this protocol until it advances to a status of Draft.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Experimental" and $thetype = "Procedural"'>
+ <p style='color:red'>NOTICE: This Procedural document proposes that the process or activity defined herein shall be followed by the XMPP Standards Foundation (XSF). However, this process or activity has not yet been approved by the XMPP Council and/or the XSF Board of Directors and is therefore not currently in force.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Experimental" and $thetype = "Standards Track"'>
+ <p style='color:red'>WARNING: This Standards-Track document is Experimental. Publication as an XMPP Extension Protocol does not imply approval of this proposal by the XMPP Standards Foundation. Implementation of the protocol described herein is encouraged in exploratory implementations, but production systems should not deploy implementations of this protocol until it advances to a status of Draft.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Final"'>
+ <p style='color:green'>NOTICE: The protocol defined herein is a Final Standard of the XMPP Standards Foundation and may be considered a stable technology for implementation and deployment.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Obsolete"'>
+ <p style='color:red'>WARNING: This document has been obsoleted by the XMPP Standards Foundation. Implementation of the protocol described herein is not recommended. Developers desiring similar functionality should implement the protocol that supersedes this one (if any).</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Proposed"'>
+ <p style='color:red'>NOTICE: This document is currently within Last Call or under consideration by the XMPP Council for advancement to the next stage in the XSF standards process.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "ProtoXEP"'>
+ <p style='color:red'>WARNING: This document has not yet been accepted for consideration or approved in any official manner by the XMPP Standards Foundation, and this document must not be referred to as an XMPP Extension Protocol (XEP). If this document is accepted as a XEP by the XMPP Council, it will be published at &lt;<a href="http://www.xmpp.org/extensions/">http://www.xmpp.org/extensions/</a>&gt; and announced on the &lt;standards@xmpp.org&gt; mailing list.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Rejected"'>
+ <p style='color:red'>WARNING: This document has been Rejected by the XMPP Council. Implementation of the protocol described herein is not recommended under any circumstances.</p>
+ </xsl:if>
+ <xsl:if test='$thestatus = "Retracted"'>
+ <p style='color:red'>WARNING: This document has been retracted by the author(s). Implementation of the protocol described herein is not recommended. Developers desiring similar functionality should implement the protocol that supersedes this one (if any).</p>
+ </xsl:if>
+ <hr />
+ <!-- XEP INFO -->
+ <h2>Document Information</h2>
+ <p class='indent'>
+ Series: <a href='http://www.xmpp.org/extensions/'>XEP</a><br />
+ Number: <xsl:value-of select='/xep/header/number'/><br />
+ Publisher: <a href='/xsf/'>XMPP Standards Foundation</a><br />
+ Status:
+ <a>
+ <xsl:attribute name='href'><xsl:text>http://www.xmpp.org/extensions/xep-0001.html#states-</xsl:text><xsl:value-of select='/xep/header/status'/></xsl:attribute>
+ <xsl:value-of select='/xep/header/status'/>
+ </a>
+ <br />
+ Type:
+ <a>
+ <xsl:attribute name='href'><xsl:text>http://www.xmpp.org/extensions/xep-0001.html#types-</xsl:text><xsl:value-of select='/xep/header/type'/></xsl:attribute>
+ <xsl:value-of select='/xep/header/type'/>
+ </a>
+ <br />
+ Version: <xsl:value-of select='/xep/header/revision[position()=1]/version'/><br />
+ Last Updated: <xsl:value-of select='/xep/header/revision[position()=1]/date'/><br />
+ <xsl:variable name='expires.count' select='count(/xep/header/expires)'/>
+ <xsl:if test='$expires.count=1'>
+ Expires: <xsl:value-of select='/xep/header/expires'/><br />
+ </xsl:if>
+ <xsl:variable name='ApprovingBody' select='/xep/header/approver'/>
+ <xsl:choose>
+ <xsl:when test='$ApprovingBody = "Board"'>
+ Approving Body: <a href='http://www.xmpp.org/xsf/board/'>XSF Board of Directors</a><br />
+ </xsl:when>
+ <xsl:otherwise>
+ Approving Body: <a href='http://www.xmpp.org/council/'>XMPP Council</a><br />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:variable name='dependencies.count' select='count(/xep/header/dependencies/spec)'/>
+ <xsl:choose>
+ <xsl:when test='$dependencies.count &gt; 0'>
+ <xsl:text>Dependencies: </xsl:text>
+ <xsl:apply-templates select='/xep/header/dependencies/spec'>
+ <xsl:with-param name='speccount' select='$dependencies.count'/>
+ </xsl:apply-templates>
+ <br />
+ </xsl:when>
+ <xsl:otherwise>
+ Dependencies: None<br />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:variable name='supersedes.count' select='count(/xep/header/supersedes/spec)'/>
+ <xsl:choose>
+ <xsl:when test='$supersedes.count &gt; 0'>
+ <xsl:text>Supersedes: </xsl:text>
+ <xsl:apply-templates select='/xep/header/supersedes/spec'>
+ <xsl:with-param name='speccount' select='$supersedes.count'/>
+ </xsl:apply-templates>
+ <br />
+ </xsl:when>
+ <xsl:otherwise>
+ Supersedes: None<br />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:variable name='supersededby.count' select='count(/xep/header/supersededby/spec)'/>
+ <xsl:choose>
+ <xsl:when test='$supersededby.count &gt; 0'>
+ <xsl:text>Superseded By: </xsl:text>
+ <xsl:apply-templates select='/xep/header/supersededby/spec'>
+ <xsl:with-param name='speccount' select='$supersededby.count'/>
+ </xsl:apply-templates>
+ <br />
+ </xsl:when>
+ <xsl:otherwise>
+ Superseded By: None<br />
+ </xsl:otherwise>
+ </xsl:choose>
+ Short Name: <xsl:value-of select='/xep/header/shortname'/><br />
+ <xsl:variable name='schema.count' select='count(/xep/header/schemaloc)'/>
+ <xsl:if test='$schema.count &gt; 0'>
+ <xsl:apply-templates select='/xep/header/schemaloc'/>
+ </xsl:if>
+ <xsl:variable name='reg.count' select='count(/xep/header/registry)'/>
+ <xsl:if test='$reg.count=1'>
+ Registry:
+ <xsl:variable name='registryURL'>
+ <xsl:text>http://www.xmpp.org/registrar/</xsl:text>
+ <xsl:value-of select='/xep/header/shortname'/>
+ <xsl:text>.html</xsl:text>
+ </xsl:variable>
+ &lt;<a href='{$registryURL}'><xsl:value-of select='$registryURL'/></a>&gt;
+ <br />
+ </xsl:if>
+ <xsl:variable name='wikiURL'>
+ <xsl:text>http://wiki.jabber.org/index.php/</xsl:text>
+ <xsl:value-of select='/xep/header/title'/>
+ <xsl:text> (XEP-</xsl:text>
+ <xsl:value-of select='/xep/header/number'/>
+ <xsl:text>)</xsl:text>
+ </xsl:variable>
+ <xsl:if test='$thestatus != "ProtoXEP"'>
+ Wiki Page: &lt;<a href='{$wikiURL}'><xsl:value-of select='$wikiURL'/></a>&gt;
+ </xsl:if>
+ </p>
+ <hr />
+ <!-- AUTHOR INFO -->
+ <h2>Author Information</h2>
+ <div class='indent'>
+ <xsl:apply-templates select='/xep/header/author'/>
+ </div>
+ <hr />
+ <!-- LEGAL NOTICES -->
+ <xsl:apply-templates select='/xep/header/legal'/>
+ <hr />
+ <!-- DISCUSSION VENUE -->
+ <h2>Discussion Venue</h2>
+ <xsl:variable name='Approver' select='/xep/header/approver'/>
+ <xsl:choose>
+ <xsl:when test='$Approver = "Board"'>
+ <p class='indent'>The preferred venue for discussion of this document is the Standards discussion list: &lt;<a href="http://mail.jabber.org/mailman/listinfo/standards">http://mail.jabber.org/mailman/listinfo/standards</a>&gt;.</p>
+ <p class='indent'>Discussion by the membership of the XSF may also be appropriate (see &lt;<a href="http://mail.jabber.org/mailman/listinfo/members">http://mail.jabber.org/mailman/listinfo/members</a>&gt; for details).</p>
+ </xsl:when>
+ <xsl:otherwise>
+ <p class='indent'>The preferred venue for discussion of this document is the Standards discussion list: &lt;<a href="http://mail.jabber.org/mailman/listinfo/standards">http://mail.jabber.org/mailman/listinfo/standards</a>&gt;.</p>
+ <xsl:if test='contains(/xep/header/dependencies,"RFC")'>
+ <p class='indent'>Given that this XMPP Extension Protocol normatively references IETF technologies, discussion on the XSF-IETF list may also be appropriate (see &lt;<a href="http://mail.jabber.org/mailman/listinfo/jsf-ietf">http://mail.jabber.org/mailman/listinfo/jsf-ietf</a>&gt; for details).</p>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ <p class='indent'>Errata may be sent to &lt;<a href='mailto:editor@xmpp.org'>editor@xmpp.org</a>&gt;.</p>
+ <!-- XMPP NOTICE AND CONFORMANCE TERMS-->
+ <!-- (we don't put these on Procedural XEPs) -->
+ <xsl:if test='$thetype = "Standards Track" or $thetype = "Historical" or $thetype = "Informational"'>
+ <h2>Relation to XMPP</h2>
+ <p class='indent'>The Extensible Messaging and Presence Protocol (XMPP) is defined in the XMPP Core (RFC 3920) and XMPP IM (RFC 3921) specifications contributed by the XMPP Standards Foundation to the Internet Standards Process, which is managed by the Internet Engineering Task Force in accordance with RFC 2026. Any protocol defined in this document has been developed outside the Internet Standards Process and is to be understood as an extension to XMPP rather than as an evolution, development, or modification of XMPP itself.</p>
+ <h2>Conformance Terms</h2>
+ <p class='indent'>The following keywords as used in this document are to be interpreted as described in <a href='http://www.ietf.org/rfc/rfc2119.txt'>RFC 2119</a>: "MUST", "SHALL", "REQUIRED"; "MUST NOT", "SHALL NOT"; "SHOULD", "RECOMMENDED"; "SHOULD NOT", "NOT RECOMMENDED"; "MAY", "OPTIONAL".</p>
+ </xsl:if>
+ <!-- TABLE OF CONTENTS -->
+ <hr />
+ <xsl:call-template name='processTOC' />
+ <!-- XEP CONTENTS -->
+ <hr />
+ <xsl:apply-templates select='/xep/section1'/>
+ <!-- NOTES -->
+ <hr />
+ <h2><a name="notes"></a>Notes</h2>
+ <div class='indent'>
+ <xsl:apply-templates select='//note' mode='endlist'/>
+ </div>
+ <!-- REVISION HISTORY -->
+ <hr />
+ <h2><a name="revs"></a>Revision History</h2>
+ <div class='indent'>
+ <xsl:apply-templates select='/xep/header/revision'/>
+ </div>
+ <hr />
+ <p>END</p>
+ </body>
+ </html>
+ </xsl:template>
+
+ <!-- From the docbook XSL -->
+ <xsl:template name="object.id">
+ <xsl:param name="object" select="."/>
+ <xsl:choose>
+ <xsl:when test="$object/@id">
+ <xsl:value-of select="$object/@id"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="generate-id($object)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template name='processTOC'>
+ <h2>Table of Contents</h2>
+ <div class='indent'>
+ <p>
+ <xsl:apply-templates select='//section1' mode='toc'/>
+ <br /><a href="#notes">Notes</a>
+ <br /><a href="#revs">Revision History</a>
+ </p>
+ </div>
+ </xsl:template>
+
+ <xsl:template match='author' mode='meta'>
+ <meta>
+ <xsl:attribute name='name'><xsl:text>DC.Creator</xsl:text></xsl:attribute>
+ <xsl:attribute name='content'><xsl:value-of select='firstname'/><xsl:text> </xsl:text><xsl:value-of select='surname'/></xsl:attribute>
+ </meta>
+ </xsl:template>
+
+ <xsl:template match='author'>
+ <h3><xsl:value-of select='firstname'/><xsl:text> </xsl:text><xsl:value-of select='surname'/></h3>
+ <p class='indent'>
+ <xsl:variable name='org.count' select='count(org)'/>
+ <xsl:variable name='email.count' select='count(email)'/>
+ <xsl:variable name='jid.count' select='count(jid)'/>
+ <xsl:variable name='uri.count' select='count(uri)'/>
+ <xsl:variable name='authornote.count' select='count(authornote)'/>
+ <xsl:if test='$authornote.count &gt; 0'>
+ See <a href='#authornote'>Author Note</a><br />
+ </xsl:if>
+ <xsl:if test='$org.count=1'>
+ Organization: <xsl:value-of select='org'/><br />
+ </xsl:if>
+ <xsl:if test='$email.count=1'>
+ Email:
+ <a>
+ <xsl:attribute name='href'>
+ <xsl:text>mailto:</xsl:text>
+ <xsl:value-of select='email' />
+ </xsl:attribute>
+ <xsl:value-of select='email' />
+ </a>
+ <br />
+ </xsl:if>
+ <xsl:if test='$jid.count=1'>
+ JabberID:
+ <a>
+ <xsl:attribute name='href'>
+ <xsl:text>xmpp:</xsl:text>
+ <xsl:value-of select='jid' />
+ </xsl:attribute>
+ <xsl:value-of select='jid' />
+ </a>
+ <br />
+ </xsl:if>
+ <xsl:if test='$uri.count=1'>
+ URI:
+ <a>
+ <xsl:attribute name='href'>
+ <xsl:value-of select='uri' />
+ </xsl:attribute>
+ <xsl:value-of select='uri' />
+ </a>
+ <br />
+ </xsl:if>
+ </p>
+ </xsl:template>
+
+ <xsl:template match='legal'>
+ <h2>Legal Notices</h2>
+ <div class='indent'>
+ <h3>Copyright</h3>
+ <xsl:apply-templates select='/xep/header/legal/copyright'/>
+ <h3>Permissions</h3>
+ <xsl:apply-templates select='/xep/header/legal/permissions'/>
+ <h3>Disclaimer of Warranty</h3>
+ <span style='font-weight: bold'>
+ <xsl:apply-templates select='/xep/header/legal/warranty'/>
+ </span>
+ <h3>Limitation of Liability</h3>
+ <xsl:apply-templates select='/xep/header/legal/liability'/>
+ <h3>IPR Conformance</h3>
+ <xsl:apply-templates select='/xep/header/legal/conformance'/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match='spec'>
+ <xsl:param name='speccount' select='""'/>
+ <xsl:variable name='specpos' select='position()'/>
+ <xsl:choose>
+ <xsl:when test='$specpos &lt; $speccount'>
+ <xsl:value-of select='.'/><xsl:text>, </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select='.'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template match='schemaloc'>
+ <xsl:variable name='this.url' select='url'/>
+ <xsl:variable name='ns.count' select='count(ns)'/>
+ <xsl:choose>
+ <xsl:when test="$ns.count=1">
+ XML Schema for <xsl:value-of select='ns'/> namespace: &lt;<a href='{$this.url}'><xsl:value-of select='url'/></a>&gt;<br />
+ </xsl:when>
+ <xsl:otherwise>
+ Schema: &lt;<a href='{$this.url}'><xsl:value-of select='url'/></a>&gt;<br />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template match='revision'>
+ <h4>Version <xsl:value-of select='version'/><xsl:text> </xsl:text>(<xsl:value-of select='date'/>)</h4>
+ <div class='indent'>
+ <xsl:apply-templates select='remark'/>
+ <xsl:text> </xsl:text>(<xsl:value-of select='initials'/>)
+ </div>
+ </xsl:template>
+
+ <xsl:template match='section1' mode='toc'>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:variable name='anch'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:variable>
+ <xsl:variable name='num'>
+ <xsl:number level='multiple' count='section1'/><xsl:text>.</xsl:text>
+ </xsl:variable>
+ <xsl:variable name='sect2.count' select='count(section2)'/>
+ <br />
+ <xsl:value-of select='$num'/> <xsl:text> </xsl:text>
+ <a>
+ <xsl:attribute name='href'>
+ <xsl:text>#</xsl:text>
+ <xsl:choose>
+ <xsl:when test='$anch != ""'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>sect-</xsl:text>
+ <xsl:value-of select='$oid'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select='@topic' />
+ </a>
+ <xsl:if test='$sect2.count &gt; 0'>
+ <xsl:apply-templates select='section2' mode='toc'>
+ <xsl:with-param name='prevnum' select='$num'/>
+ </xsl:apply-templates>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match='section1'>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:variable name='anch'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:variable>
+ <h2>
+ <xsl:number level='single' count='section1'/>.
+ <xsl:text> </xsl:text>
+ <a>
+ <xsl:attribute name='name'>
+ <xsl:choose>
+ <xsl:when test='$anch != ""'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>sect-</xsl:text><xsl:value-of select='$oid'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select='@topic' />
+ </a>
+ </h2>
+ <xsl:apply-templates/>
+ </xsl:template>
+
+ <xsl:template match='section2' mode='toc'>
+ <xsl:param name='prevnum' select='""'/>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:variable name='anch'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:variable>
+ <xsl:variable name='num'>
+ <xsl:value-of select='$prevnum'/><xsl:number level='multiple' count='section2'/><xsl:text>.</xsl:text>
+ </xsl:variable>
+ <xsl:variable name='sect3.count' select='count(section3)'/>
+ <br />&#160;&#160;&#160;
+ <xsl:value-of select='$num'/> <xsl:text> </xsl:text>
+ <a>
+ <xsl:attribute name='href'>
+ <xsl:text>#</xsl:text>
+ <xsl:choose>
+ <xsl:when test='$anch != ""'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>sect-</xsl:text><xsl:value-of select='$oid'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select='@topic' />
+ </a>
+ <xsl:if test='$sect3.count &gt; 0'>
+ <xsl:apply-templates select='section3' mode='toc'>
+ <xsl:with-param name='prevnum' select='$num'/>
+ </xsl:apply-templates>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match='section2'>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:variable name='anch'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:variable>
+ <div class='indent'>
+ <h3>
+ <xsl:number level='single' count='section1'/>.<xsl:number level='single' count='section2'/>
+ <xsl:text> </xsl:text>
+ <a>
+ <xsl:attribute name='name'>
+ <xsl:choose>
+ <xsl:when test='$anch != ""'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>sect-</xsl:text><xsl:value-of select='$oid'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select='@topic' />
+ </a>
+ </h3>
+ <xsl:apply-templates/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match='section3' mode='toc'>
+ <xsl:param name='prevnum' select='""'/>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:variable name='anch'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:variable>
+ <xsl:variable name='num'>
+ <xsl:value-of select='$prevnum'/><xsl:number level='multiple' count='section3'/><xsl:text>.</xsl:text>
+ </xsl:variable>
+ <xsl:variable name='sect4.count' select='count(section4)'/>
+ <br />&#160;&#160;&#160;&#160;&#160;&#160;
+ <xsl:value-of select='$num'/> <xsl:text> </xsl:text>
+ <a>
+ <xsl:attribute name='href'>
+ <xsl:text>#</xsl:text>
+ <xsl:choose>
+ <xsl:when test='$anch != ""'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>sect-</xsl:text><xsl:value-of select='$oid'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select='@topic' />
+ </a>
+ <xsl:if test='$sect4.count &gt; 0'>
+ <xsl:apply-templates select='section4' mode='toc'>
+ <xsl:with-param name='prevnum' select='$num'/>
+ </xsl:apply-templates>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match='section3'>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:variable name='anch'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:variable>
+ <div class='indent'>
+ <h3>
+ <xsl:number level='single' count='section1'/>.<xsl:number level='single' count='section2'/>.<xsl:number level='single' count='section3'/>
+ <xsl:text> </xsl:text>
+ <a>
+ <xsl:attribute name='name'>
+ <xsl:choose>
+ <xsl:when test='$anch != ""'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>sect-</xsl:text><xsl:value-of select='$oid'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select='@topic' />
+ </a>
+ </h3>
+ <xsl:apply-templates/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match='section4' mode='toc'>
+ <xsl:param name='prevnum' select='""'/>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:variable name='anch'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:variable>
+ <xsl:variable name='num'>
+ <xsl:value-of select='$prevnum'/><xsl:number level='multiple' count='section4'/><xsl:text>.</xsl:text>
+ </xsl:variable>
+ <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;
+ <xsl:value-of select='$num'/> <xsl:text> </xsl:text>
+ <a>
+ <xsl:attribute name='href'>
+ <xsl:text>#</xsl:text>
+ <xsl:choose>
+ <xsl:when test='$anch != ""'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>sect-</xsl:text><xsl:value-of select='$oid'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select='@topic' />
+ </a>
+ </xsl:template>
+
+ <xsl:template match='section4'>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:variable name='anch'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:variable>
+ <div class='indent'>
+ <h3>
+ <xsl:number level='single' count='section1'/>.<xsl:number level='single' count='section2'/>.<xsl:number level='single' count='section3'/>.<xsl:number level='single' count='section4'/>
+ <xsl:text> </xsl:text>
+ <a>
+ <xsl:attribute name='name'>
+ <xsl:choose>
+ <xsl:when test='$anch != ""'>
+ <xsl:value-of select='@anchor'/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>sect-</xsl:text><xsl:value-of select='$oid'/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select='@topic' />
+ </a>
+ </h3>
+ <xsl:apply-templates/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match='remark'>
+ <xsl:apply-templates/>
+ </xsl:template>
+
+ <xsl:template match='p'>
+ <p>
+ <xsl:variable name='class.count' select='count(@class)'/>
+ <xsl:if test='$class.count=1'>
+ <xsl:attribute name='class'><xsl:value-of select='@class'/></xsl:attribute>
+ </xsl:if>
+ <xsl:variable name='style.count' select='count(@style)'/>
+ <xsl:if test='$style.count=1'>
+ <xsl:attribute name='style'><xsl:value-of select='@style'/></xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match='ul'>
+ <ul>
+ <xsl:variable name='class.count' select='count(@class)'/>
+ <xsl:if test='$class.count=1'>
+ <xsl:attribute name='class'><xsl:value-of select='@class'/></xsl:attribute>
+ </xsl:if>
+ <xsl:variable name='style.count' select='count(@style)'/>
+ <xsl:if test='$style.count=1'>
+ <xsl:attribute name='style'><xsl:value-of select='@style'/></xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </ul>
+ </xsl:template>
+
+ <xsl:template match='ol'>
+ <ol>
+ <xsl:variable name='start.count' select='count(@start)'/>
+ <xsl:if test='$start.count=1'>
+ <xsl:attribute name='start'><xsl:value-of select='@start'/></xsl:attribute>
+ </xsl:if>
+ <xsl:variable name='class.count' select='count(@class)'/>
+ <xsl:if test='$class.count=1'>
+ <xsl:attribute name='class'><xsl:value-of select='@class'/></xsl:attribute>
+ </xsl:if>
+ <xsl:variable name='style.count' select='count(@style)'/>
+ <xsl:if test='$style.count=1'>
+ <xsl:attribute name='style'><xsl:value-of select='@style'/></xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </ol>
+ </xsl:template>
+
+ <xsl:template match='li'>
+ <li>
+ <xsl:variable name='class.count' select='count(@class)'/>
+ <xsl:if test='$class.count=1'>
+ <xsl:attribute name='class'><xsl:value-of select='@class'/></xsl:attribute>
+ </xsl:if>
+ <xsl:variable name='style.count' select='count(@style)'/>
+ <xsl:if test='$style.count=1'>
+ <xsl:attribute name='style'><xsl:value-of select='@style'/></xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </li>
+ </xsl:template>
+
+ <xsl:template match='link'>
+ <a>
+ <xsl:attribute name='href'><xsl:value-of select='@url'/></xsl:attribute>
+ <xsl:apply-templates/>
+ </a>
+ </xsl:template>
+
+ <xsl:template match='example'>
+ <p class='caption'><a><xsl:attribute name='name'><xsl:text>example-</xsl:text><xsl:number level='any' count='example'/></xsl:attribute></a>Example <xsl:number level='any' count='example'/>.<xsl:text> </xsl:text><xsl:value-of select='@caption'/></p>
+ <div class='indent'>
+ <pre><xsl:apply-templates/></pre>
+ </div>
+ </xsl:template>
+
+ <xsl:template match='code'>
+ <p class='caption'><xsl:value-of select='@caption'/></p>
+ <div class='indent'>
+ <pre><xsl:apply-templates/></pre>
+ </div>
+ </xsl:template>
+
+ <xsl:template match='img'>
+ <img>
+ <xsl:attribute name='alt'><xsl:value-of select='@alt'/></xsl:attribute>
+ <xsl:attribute name='height'><xsl:value-of select='@height'/></xsl:attribute>
+ <xsl:attribute name='src'><xsl:value-of select='@src'/></xsl:attribute>
+ <xsl:attribute name='width'><xsl:value-of select='@width'/></xsl:attribute>
+ </img>
+ </xsl:template>
+
+ <xsl:template match='table'>
+ <p class='caption'><a><xsl:attribute name='name'><xsl:text>table-</xsl:text><xsl:number level='any' count='table'/></xsl:attribute></a>Table <xsl:number level='any' count='table'/>:<xsl:text> </xsl:text><xsl:value-of select='@caption'/></p>
+ <table border='1' cellpadding='3' cellspacing='0'>
+ <xsl:apply-templates/>
+ </table>
+ </xsl:template>
+
+ <xsl:template match='tr'>
+ <tr class='body'>
+ <xsl:apply-templates/>
+ </tr>
+ </xsl:template>
+
+ <xsl:template match='th'>
+ <th>
+ <xsl:variable name='colspan.count' select='count(@colspan)'/>
+ <xsl:variable name='rowspan.count' select='count(@rowspan)'/>
+ <xsl:if test='$colspan.count=1'>
+ <xsl:attribute name='colspan'><xsl:value-of select='@colspan'/></xsl:attribute>
+ </xsl:if>
+ <xsl:if test='$rowspan.count=1'>
+ <xsl:attribute name='rowspan'><xsl:value-of select='@rowspan'/></xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </th>
+ </xsl:template>
+
+ <xsl:template match='td'>
+ <td>
+ <xsl:variable name='align.count' select='count(@align)'/>
+ <xsl:variable name='colspan.count' select='count(@colspan)'/>
+ <xsl:variable name='rowspan.count' select='count(@rowspan)'/>
+ <xsl:if test='$align.count=1'>
+ <xsl:attribute name='align'><xsl:value-of select='@align'/></xsl:attribute>
+ </xsl:if>
+ <xsl:if test='$colspan.count=1'>
+ <xsl:attribute name='colspan'><xsl:value-of select='@colspan'/></xsl:attribute>
+ </xsl:if>
+ <xsl:if test='$rowspan.count=1'>
+ <xsl:attribute name='rowspan'><xsl:value-of select='@rowspan'/></xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </td>
+ </xsl:template>
+
+ <xsl:template match='note'>
+ <xsl:variable name='notenum'>
+ <xsl:number level='any' count='note'/>
+ </xsl:variable>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <xsl:text> [</xsl:text><a href='#nt-{$oid}'>
+ <xsl:value-of select='$notenum'/></a>
+ <xsl:text>]</xsl:text>
+ </xsl:template>
+
+ <xsl:template match='note' mode='endlist'>
+ <p>
+ <xsl:variable name='oid'>
+ <xsl:call-template name='object.id'/>
+ </xsl:variable>
+ <a name='nt-{$oid}'><xsl:value-of select='position()'/></a>
+ <xsl:text>. </xsl:text>
+ <xsl:apply-templates/>
+ </p>
+ </xsl:template>
+
+<!-- PRESENTATIONAL ELEMENTS -->
+
+ <xsl:template match='cite'>
+ <span class='ref'>
+ <xsl:apply-templates/>
+ </span>
+ </xsl:template>
+
+ <xsl:template match='dfn'>
+ <span class='dfn'>
+ <xsl:apply-templates/>
+ </span>
+ </xsl:template>
+
+ <xsl:template match='div'>
+ <div>
+ <xsl:variable name='class.count' select='count(@class)'/>
+ <xsl:if test='$class.count=1'>
+ <xsl:attribute name='class'><xsl:value-of select='@class'/></xsl:attribute>
+ </xsl:if>
+ <xsl:variable name='style.count' select='count(@style)'/>
+ <xsl:if test='$style.count=1'>
+ <xsl:attribute name='style'><xsl:value-of select='@style'/></xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </div>
+ </xsl:template>
+
+ <xsl:template match='em'>
+ <span class='em'>
+ <xsl:apply-templates/>
+ </span>
+ </xsl:template>
+
+ <xsl:template match='pre'>
+ <pre><xsl:apply-templates/></pre>
+ </xsl:template>
+
+ <xsl:template match='span'>
+ <span>
+ <xsl:variable name='class.count' select='count(@class)'/>
+ <xsl:if test='$class.count=1'>
+ <xsl:attribute name='class'><xsl:value-of select='@class'/></xsl:attribute>
+ </xsl:if>
+ <xsl:variable name='style.count' select='count(@style)'/>
+ <xsl:if test='$style.count=1'>
+ <xsl:attribute name='style'><xsl:value-of select='@style'/></xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </span>
+ </xsl:template>
+
+ <xsl:template match='strong'>
+ <span class='strong'>
+ <xsl:apply-templates/>
+ </span>
+ </xsl:template>
+
+ <xsl:template match='tt'>
+ <tt>
+ <xsl:apply-templates/>
+ </tt>
+ </xsl:template>
+
+<!-- END OF PRESENTATIONAL ELEMENTS -->
+
+</xsl:stylesheet>