diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2013-09-25 14:40:35 +0100 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2013-09-25 14:40:35 +0100 |
commit | 5c1edec846876d56b3376683a1f2c50a09db2514 (patch) | |
tree | 1ebe7b3bb3d75eac5a835eb97e834b14f4e5ad1f | |
parent | 7405b4da3e8c50b23d686cb5f0970e0fba1cf375 (diff) | |
parent | f9963519c30820e1369705ddd36f9e5a004c215e (diff) |
Merge remote-tracking branch 'smcv/next' into next
Reviewed-by: Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=69767
79 files changed, 2908 insertions, 8022 deletions
@@ -32,9 +32,11 @@ src/telepathy-haze src/telepathy-haze.8 tags /tests/haze-testing.log -/tests/im.telepathy1.ConnectionManager.haze.service -/tests/tmp-session-bus.conf +/tests/twisted/tools/im.telepathy1.ConnectionManager.haze.service +/tests/twisted/tools/tmp-session-bus.conf /tests/twisted/config.py +/tests/twisted/haze-twisted-tests.list +/tests/twisted/run-test.sh /telepathy-haze*.tar.gz* /telepathy-haze*/ @@ -1,7 +1,74 @@ -telepathy-haze 0.7.0 (UNRELEASED) +telepathy-haze 0.9.0 (UNRELEASED) ================================= -... +Dependencies: + +• telepathy-glib ≥ 0.21.0 is required + +Features removed: + +• StreamedMedia channels are no longer supported. We'd potentially + accept patches to bring back audio/video support, but only via the + newer Call1 channels. (fd.o #69318, Simon) + +Internal changes: + +• fix various deprecation warnings (fd.o #69272; Guillaume, Simon) + +• remove deprecated Capabilities and Presence interfaces (fd.o #69318; + Guillaume, Simon) + +Fixes: + +• Report contacts with unknown presence as 'unknown' rather than raising + an error, and don't crash if libpurple reports a "primitive status" + that we don't understand (fd.o #69474, Simon) + +• Fix a memory leak when IM channels are closed (fd.o #31723, Simon) + +• Fix ContactGroups.SetContactGroups() implementation so it removes + the contact from groups if desired (fd.o #49389, Simon) + +telepathy-haze 0.7.1 (2013-09-17) +================================= + +The “buzzes like a fridge” release. + +This is a release candidate for telepathy-haze 0.8, recommended for +use with GNOME 3.10. + +Deprecations: + +• This will be the last branch with StreamedMedia support. Anyone + relying on Haze for audio/video calling is invited to port it to Call1. + +Enhancements: + +• Support interactive password prompting for protocols like SIPE that + have an optional password (fd.o #63326; Stefan Becker, Will Thompson) + +Fixes: + +• Adapt to Sametime accounts getting a "usersplit" in libpurple 2.10.1 + (fd.o #44631, Simon McVittie) + +• Regression test improvements (fd.o #69269, #65290, #65296, #63119; + Will Thompson, Guillaume Desmottes, Simon McVittie) + +telepathy-haze 0.7.0 (2012-11-21) +================================= + +This is the start of a new unstable branch. Don't swing on it too hard. + +Enhancements: + +• Jonny implemented ContactCapabilities; +• Simon got rid of some deprecated API; +• So did Xavier; +• Gabriele Giacone kindly mapped two of the extant Skype prpls to + "skype-x11" and "skype-dbus". Maybe you'd like to give them a whirl? + (fd.o#57201) + telepathy-haze 0.6.0 (2012-04-04) ================================= diff --git a/configure.ac b/configure.ac index 077a1e2..a8c8876 100644 --- a/configure.ac +++ b/configure.ac @@ -7,8 +7,8 @@ AC_PREREQ([2.59]) # set nano_version to 1 m4_define([haze_major_version], [0]) -m4_define([haze_minor_version], [6]) -m4_define([haze_micro_version], [999]) +m4_define([haze_minor_version], [8]) +m4_define([haze_micro_version], [99]) m4_define([haze_nano_version], [1]) m4_define([haze_base_version], @@ -32,6 +32,7 @@ AC_CONFIG_FILES([Makefile m4/Makefile tests/Makefile tests/twisted/Makefile + tests/twisted/tools/Makefile tools/Makefile extensions/Makefile ]) @@ -72,10 +73,18 @@ AC_ARG_ENABLE(leaky-request-stubs, AC_SUBST(ENABLE_LEAKY_REQUEST_STUBS) PKG_CHECK_MODULES(PURPLE,[purple >= 2.7]) -PKG_CHECK_MODULES(TP_GLIB,[telepathy-glib-1 >= 0.99]) PKG_CHECK_MODULES(GLIB,[glib-2.0 >= 2.22, gobject-2.0, gio-2.0]) PKG_CHECK_MODULES(DBUS_GLIB,[dbus-glib-1 >= 0.73]) +AC_DEFINE([TP_SEAL_ENABLE], [], [Prevent to use sealed variables]) +AC_DEFINE([TP_VERSION_MIN_REQUIRED], [TP_VERSION_1_0], [Ignore post 1.0 deprecations]) +AC_DEFINE([TP_VERSION_MAX_ALLOWED], [TP_VERSION_1_0], [Prevent post 1.0 APIs]) +PKG_CHECK_MODULES([TP_GLIB], [telepathy-glib-1 >= 0.99.1, telepathy-glib-1-dbus >= 0.99.1]) + +dnl MIN_REQUIRED must stay to 2.30 because of GValueArray +AC_DEFINE([GLIB_VERSION_MIN_REQUIRED], [GLIB_VERSION_2_30], [Ignore post 2.30 deprecations]) +AC_DEFINE([GLIB_VERSION_MAX_ALLOWED], [GLIB_VERSION_2_30], [Prevent post 2.30 APIs]) + GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0` AC_SUBST(GLIB_GENMARSHAL) @@ -104,16 +113,6 @@ AC_MSG_RESULT([$TEST_PYTHON]) AC_SUBST(TEST_PYTHON) AM_CONDITIONAL([WANT_TWISTED_TESTS], test false != "$TEST_PYTHON") -AC_ARG_ENABLE(media, - AC_HELP_STRING([--disable-media],[disable audio/video calls]), - [ - AM_CONDITIONAL([MEDIA_ENABLED], false) - ],[ - AC_DEFINE(ENABLE_MEDIA, [], [Enable audio/video calls]) - AM_CONDITIONAL([MEDIA_ENABLED], true) - ]) -AC_SUBST(ENABLE_MEDIA) - #AS_AC_EXPAND(DATADIR, $datadir) #DBUS_SERVICES_DIR="$DATADIR/dbus-1/services" #AC_SUBST(DBUS_SERVICES_DIR) diff --git a/extensions/Connection_Interface_Mail_Notification.xml b/extensions/Connection_Interface_Mail_Notification.xml deleted file mode 100644 index 35678c2..0000000 --- a/extensions/Connection_Interface_Mail_Notification.xml +++ /dev/null @@ -1,653 +0,0 @@ -<?xml version="1.0" ?> -<node name="/Connection_Interface_Mail_Notification" - 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 -Library 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.Interface.MailNotification.DRAFT" - tp:causes-havoc="experimental"> - <tp:requires interface="org.freedesktop.Telepathy.Connection"/> - <tp:added version="0.19.1">(as draft 1)</tp:added> - - <tp:flags name="Mail_Notification_Flags" value-prefix="Mail_Notification_Flag" type="u" > - <tp:flag suffix="Supports_Unread_Mail_Count" value="1"> - <tp:docstring> - This Connection provides the number of unread e-mails (or e-mail - threads) in the main folder of your e-mail account, as the - <tp:member-ref>UnreadMailCount</tp:member-ref> property. The - connection manager will update this value by emitting the - <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal. - </tp:docstring> - </tp:flag> - <tp:flag suffix="Supports_Unread_Mails" value="2"> - <tp:docstring> - This Connection provides a detailed list of unread e-mails, as the - <tp:member-ref>UnreadMails</tp:member-ref> property. If this flag - is set, <tt>Supports_Unread_Mail_Count</tt> MUST be set, and - <tt>Emits_Mails_Received</tt> MUST NOT be set. - The Connection will update the list by emitting the - <tp:member-ref>UnreadMailsChanged</tp:member-ref> signals. - </tp:docstring> - </tp:flag> - <tp:flag suffix="Emits_Mails_Received" value="4"> - <tp:docstring> - This Connection emits the <tp:member-ref>MailsReceived</tp:member-ref> - signal, which provides details about newly arrived e-mails but does - not maintain their read/unread status afterwards. This flag MUST NOT - be combined with <tt>Supports_Unread_Mails</tt>. - </tp:docstring> - </tp:flag> - <tp:flag suffix="Supports_Request_Inbox_URL" value="8"> - <tp:docstring> - This Connection can provide a URL (with optional POST data) to - open the the inbox of the e-mail account in a web-based client, via - the <tp:member-ref>RequestInboxURL</tp:member-ref> method. - </tp:docstring> - </tp:flag> - <tp:flag suffix="Supports_Request_Mail_URL" value="16"> - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>This Connection can provide a URL (with optional POST data) to open - a specific mail in a web-based client, via the - <tp:member-ref>RequestMailURL</tp:member-ref> method. This feature - is not useful unless either Emits_Mails_Received or - Supports_Unread_Mails is set.</p> - - <p>If this flag is not set, clients SHOULD fall back to using - <tp:member-ref>RequestInboxURL</tp:member-ref> if available.</p> - </tp:docstring> - </tp:flag> - <tp:flag suffix="Thread_Based" value="32"> - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>Each <tp:type>Mail</tp:type> represents a thread of e-mails, which - MAY have more than one sender.</p> - - <tp:rationale> - <p>Google Talk notifies users about new mail in terms of unread - threads, rather than unread e-mails.</p> - </tp:rationale> - </tp:docstring> - </tp:flag> - - <tp:docstring> - <p>Flags representing capabilities provided by a connection manager. - Those values can be used as bitfield. Some flags depend on, or - conflict with, each other.</p> - - <p>Connections SHOULD implement as many of these features as the - underlying protocol allows, preferring to implement - Supports_Unread_Mails instead of Emits_Mails_Received if both are - possible.</p> - </tp:docstring> - </tp:flags> - - <tp:enum name="HTTP_Method" type="u"> - <tp:enumvalue suffix="Get" value="0"> - <tp:docstring> - Use the GET method when opening the URL. - </tp:docstring> - </tp:enumvalue> - <tp:enumvalue suffix="Post" value="1"> - <tp:docstring> - Use the POST method when opening the URL. Refer to - <tp:type>HTTP_Post_Data</tp:type> for more details. - </tp:docstring> - </tp:enumvalue> - <tp:docstring> - The HTTP Method with which to request a URL. - </tp:docstring> - </tp:enum> - - <tp:struct name="HTTP_Post_Data" array-name="HTTP_Post_Data_List"> - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>A pair (key, value) representing POST data compatible with the - application/x-www-form-urlencoded MIME type. The strings MUST be - valid UTF-8 strings, and the characters used in the key MUST obey - the requirements of the - <a href="http://www.w3.org/TR/html401/types.html#type-cdata"> - HTML CDATA type</a>. The value MUST NOT be - encoded with HTML entities.</p> - - <p>For example, if the POST data should contain a key "less-than" with value - "<", and a key "percent" with value "%", this should be represented as - two HTTP_Post_Data structures, ("less-than", "<") and ("percent", "%"), - resulting in a POST request whose request body is "less-than=&lt;&percent=%25". - If a client passes this to a browser by writing it into an HTML form, it - could do so by representing it as:</p> - - <pre> - <input type="hidden" name="less-than">&lt;</input> - <input type="hidden" name="percent">%</input> - </pre> - - <tp:rationale> - <p>This data can be used to generate a HTML file that will - automatically load the URL with appropriate POST data, in which case - the client MUST convert any characters that are special within HTML - into HTML entities. Alternatively, it can be used in an API that will - instruct the browser how to load the URL (like the Netscape Plug-in - API), in which case the client MUST escape - <a href="http://www.ietf.org/rfc/rfc1738.txt">characters that are - reserved in URLs</a>, if appropriate for that API.</p> - - <p>An array of pairs is used instead of a map from keys to values, - because it's valid to repeat keys in both HTML and - x-www-form-urlencoded data.</p> - </tp:rationale> - </tp:docstring> - <tp:member type="s" name="Key"> - <tp:docstring>The key, corresponding to a HTML control - name</tp:docstring> - </tp:member> - <tp:member type="s" name="Value"> - <tp:docstring>The value</tp:docstring> - </tp:member> - </tp:struct> - - <tp:struct name="Mail_Address" array-name="Mail_Address_List"> - <tp:docstring> - A pair (name, address) representing an e-mail address, - such as ("Nicolas Dufresne", "nicolas.dufresne@collabora.co.uk"). - </tp:docstring> - <tp:member type="s" name="Name"> - <tp:docstring>The displayed name corresponding to the e-mail - address</tp:docstring> - </tp:member> - <tp:member type="s" name="Address"> - <tp:docstring>The actual e-mail address</tp:docstring> - </tp:member> - </tp:struct> - - <tp:struct name="Mail_URL"> - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>A structure containing the required information to open a web-based - e-mail UI, without needing re-authentication (if possible).</p> - - <p>Because the URL and POST data frequently contain short-lived - credential tokens, a new URL should be requested (by calling one of - the methods that returns a Mail_URL) for each visit to the web-based - UI, and the URL should be visited soon after it is returned.</p> - </tp:docstring> - <tp:member type="s" name="URL"> - <tp:docstring> - The URL to which to send a request. - </tp:docstring> - </tp:member> - <tp:member type="u" name="Method" tp:type="HTTP_Method"> - <tp:docstring> - The HTTP method of the request. - </tp:docstring> - </tp:member> - <tp:member type="a(ss)" name="Post_Data" tp:type="HTTP_Post_Data[]"> - <tp:docstring> - An array of name-value pairs containing the POST data to use when - opening the URL. This MUST be an empty array if the Method is not - POST. - </tp:docstring> - </tp:member> - </tp:struct> - - <tp:mapping name="Mail" array-name="Mail_List"> - <tp:docstring> - An extensible map representing a mail, or (on protocols where - <tt>Thread_Based</tt> appears in - <tp:member-ref>MailNotificationFlags</tp:member-ref>) a thread of - mails. All keys are optional where not otherwise stated; however, at - least one of "senders" and "subject" must be included. - </tp:docstring> - - <tp:member type="s" name="Key"> - <tp:docstring> - <p>A key providing information about the mail or thread. Well-known - keys are as follows:</p> - - <dl> - <dt>id — s</dt> - <dd> - <p>A unique ID for this e-mail. CMs with - <tt>Supports_Unread_Mails</tt> set in - <tp:member-ref>MailNotificationFlags</tp:member-ref> MUST provide - this key in each <tp:type>Mail</tp:type>.</p> - - <p>If provided, the ID SHOULD be unique to a Mail at least until - that mail is removed with the - <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal - (in protocols with <tt>Supports_Unread_Emails</tt>), or - unique for the duration of a session (otherwise).</p> - - <tp:rationale> - <p>In protocols with Supports_Unread_Mails, this key is used to - indicate which mail was removed. In protocols without that - feature, it's impossible to tell when a mail has been removed - (and hence how long the identifier will remain valid for use - with <tp:member-ref>RequestMailURL</tp:member-ref>).</p> - </tp:rationale> - </dd> - - <dt>url-data — any type</dt> - <dd>An opaque identifier (typically a string or list of strings) - provided to the Connection when calling - <tp:member-ref>RequestMailURL</tp:member-ref>, - containing information used by the Connection to build the URL. - </dd> - - <dt>senders — a(ss) (<tp:type>Mail_Address</tp:type>)</dt> - <dd> - An array of sender display name and e-mail address pairs. Note that - only e-mails represented as a thread can have multiple senders. - </dd> - - <dt>to-addresses — a(ss) (<tp:type>Mail_Address</tp:type>)</dt> - <dd> - An array of display name and e-mail address pairs representing - the recipients. - </dd> - - <dt>cc-addresses — a(ss) (<tp:type>Mail_Address</tp:type>)</dt> - <dd> - An array of display name and e-mail address pairs representing - the carbon-copy recipients. - </dd> - - <dt>sent-timestamp — x (<tp:type>Unix_Timestamp64</tp:type>)</dt> - <dd>A UNIX timestamp indicating when the message was sent, or for - a thread, when the most recent message was sent. - </dd> - - <dt>received-timestamp — x (<tp:type>Unix_Timestamp64</tp:type>)</dt> - <dd>A UNIX timestamp indicating when the message was received, or for - a thread, when the most recent message was received. - </dd> - - <dt>has-attachments — b</dt> - <dd>If true, this mail has attachments.</dd> - - <dt>subject — s</dt> - <dd> - The subject of the message. This MUST be encoded in UTF-8. - </dd> - - <dt>content-type — s</dt> - <dd> - <p>The MIME type of the message content. Two types are currently - supported: "text/plain" for plain text, and "text/html" for a - HTML document. If omitted, "text/plain" MUST be assumed. - Regardless of MIME type, the content MUST be valid UTF-8 (which - may require that the Connection transcodes it from a legacy - encoding).</p> - - <tp:rationale> - <p>All strings on D-Bus must be UTF-8.</p> - </tp:rationale> - </dd> - - <dt>truncated — b</dt> - <dd> - If true, the content is only a partial message; if false or - omitted, the content is the entire message. - </dd> - - <dt>content — s</dt> - <dd> - The body of the message, possibly truncated, encoded as appropriate - for "content-type". - </dd> - - <dt>folder — s</dt> - <dd> - The name of the folder containing this e-mails. - If omitted, the inbox SHOULD be assumed. - </dd> - </dl> - </tp:docstring> - </tp:member> - - <tp:member name="Value" type="v"> - <tp:docstring>The value, of whatever type is appropriate for the - key.</tp:docstring> - </tp:member> - </tp:mapping> - - <property name="MailNotificationFlags" type="u" access="read" - tp:type="Mail_Notification_Flags" - tp:name-for-bindings="Mail_Notification_Flags"> - <tp:docstring> - Integer representing the bitwise-OR of supported features for e-mails - notification on this server. This property MUST NOT change after the - Connection becomes CONNECTED. - - <tp:rationale> - This property indicates the behavior and availability - of the other properties and signals within this interface. A - connection manager that cannot at least set one of the flags - in the <tp:type>Mail_Notification_Flags</tp:type> - SHOULD NOT provide this interface. - </tp:rationale> - </tp:docstring> - </property> - - <property name="UnreadMailCount" type="u" access="read" - tp:name-for-bindings="Unread_Mail_Count"> - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>The number of unread messages in the Inbox. Change notification is - via <tp:member-ref>UnreadMailsChanged</tp:member-ref>.</p> - - <p>This property is only useful if <tt>Supports_Unread_Mail_Count</tt> - is set in the <tp:member-ref>MailNotificationFlags</tp:member-ref>; - otherwise, it MUST be zero.</p> - - <p>If <tt>Thread_Based</tt> appears in the - <tp:member-ref>MailNotificationFlags</tp:member-ref>, this property - counts the number of threads, not the number of mails.</p> - </tp:docstring> - </property> - - <property name="UnreadMails" type="aa{sv}" tp:type="Mail[]" - tp:name-for-bindings="Unread_Mails" access="read"> - <tp:docstring> - A array of unread <tp:type>Mail</tp:type>s. Change notification is via - <tp:member-ref>UnreadMailsChanged</tp:member-ref>. This property is - only useful if <tt>Supports_Unread_Mails</tt> is set in - <tp:member-ref>MailNotificationFlags</tp:member-ref>; otherwise, it MUST be - an empty list. - </tp:docstring> - </property> - - <property name="MailAddress" type="s" - tp:name-for-bindings="Mail_Address" access="read"> - <tp:docstring> - A string representing the e-mail address of the account. The CMs MUST - provide this information. - <tp:rationale> - In close integration of MailNotification with other e-mail services, - the e-mail address can be used has a unique identifier for the - account. Possible integration could be between Telepathy and - Evolution where the e-mail address is the common information in - both interfaces. - </tp:rationale> - </tp:docstring> - </property> - - <signal name="MailsReceived" tp:name-for-bindings="Mails_Received"> - <arg name="Mails" type="aa{sv}" tp:type="Mail[]"> - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>An array of <tp:type>Mail</tp:type>s. Those e-mail MUST NOT have - the "id" key.</p> - - <tp:rationale> - <p>On connections that emit this signal, it's impossible to tell - when a mail has been removed, and hence when "id" has become - invalid.</p> - </tp:rationale> - </tp:docstring> - </arg> - - <tp:docstring> - Emitted when new e-mails messages arrive to the inbox associated with - this connection. This signal is used for protocols that are not able - to maintain the <tp:member-ref>UnreadMails</tp:member-ref> list, but - do provide real-time notification about newly arrived e-mails. It MUST - NOT be emitted unless <tt>Emits_Mails_Received</tt> is set in - <tp:member-ref>MailNotificationFlags</tp:member-ref>. - </tp:docstring> - </signal> - - <signal name="UnreadMailsChanged" - tp:name-for-bindings="Unread_Mails_Changed"> - <arg name="Count" type="u"> - <tp:docstring> - Number of unread messages in the inbox (the new value of - <tp:member-ref>UnreadMailCount</tp:member-ref>). - </tp:docstring> - </arg> - <arg name="Mails_Added" type="aa{sv}" tp:type="Mail[]"> - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>A list of <tp:type>Mail</tp:type> that are being added or updated - in <tp:member-ref>UnreadMails</tp:member-ref>.</p> - - <tp:rationale> - <p>Mails may be updated when the URL information (URL and POST data) - have changed, or senders were added or removed from an e-mail - thread.</p> - </tp:rationale> - - <p>If the <tt>Supports_Unread_Mails</tt> flag is not set, this list - MUST be empty, even if Count has increased.</p> - </tp:docstring> - </arg> - <arg name="Mails_Removed" type="as"> - <tp:docstring> - A list of e-mail IDs that are being removed from - <tp:member-ref>UnreadMails</tp:member-ref>. - If the <tt>Supports_Unread_Mails</tt> flag is not set, this list - MUST be empty, even if Count has decreased. - </tp:docstring> - </arg> - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>Emitted when <tp:member-ref>UnreadMails</tp:member-ref> or - <tp:member-ref>UnreadMailCount</tp:member-ref> have changed. It MUST - NOT be emited if <tt>Supports_Unread_Mail_Count</tt> flag is not set - in <tp:member-ref>MailNotificationFlags</tp:member-ref>.</p> - - <p><tt>Mails_Added</tt> and - <tt>Mails_Removed</tt> MUST be empty if the - <tt>Supports_Unread_Mails</tt> flag is not set.</p> - </tp:docstring> - </signal> - - <method name="Subscribe" - tp:name-for-bindings="Subscribe"> - <tp:docstring> - <p>This method subscribes a client to the notification interface. This - MUST be called by clients before using this interface.</p> - - <p>The Connection tracks a subscription count (like a refcount) for - each unique bus name that has called Subscribe(). When a client calls - Unsubscribe(), it releases one "reference". If a client exits - (or crashes), the Connection releases all "references" held on its - behalf.</p> - - <tp:rationale> - <p>The reference count imposed on the subscription simplifies - implementation of client running in the same process - (e.g. plug-ins): two plug-ins interested in mail notification can - call Subscribe and Unsubscribe independently without interfering - with each other.</p> - - <p>This method exists to reduce memory and network overhead when - there is no active subscription. An example of a protocol that - benefits from this method is the Google XMPP Mail Notification - extension: in this protocol, the CM receives a notification - that something has changed, but to get more information, the CM - must request this information. Knowing that nobody is currently - interested in this information, the CM can avoid generating - useless network traffic. Similarly, the CM may free - the list of unread messages to reduce memory overhead.</p> - </tp:rationale> - - </tp:docstring> - <tp:possible-errors> - <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/> - <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"/> - </tp:possible-errors> - </method> - - <method name="Unsubscribe" - tp:name-for-bindings="Unsubscribe"> - <tp:docstring> - This method unsubscribes a client from the notification interface. - This SHOULD be called by each client that has successfully called - Subscribe when it no longer needs the mail notification interface. - - <tp:rationale> - See <tp:member-ref>Subscribe</tp:member-ref> for rationale. - </tp:rationale> - </tp:docstring> - <tp:possible-errors> - <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"> - <tp:docstring> - Raised if the client calling this method has no references to - release. - </tp:docstring> - </tp:error> - <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"/> - </tp:possible-errors> - </method> - - <method name="RequestInboxURL" - tp:name-for-bindings="Request_Inbox_URL"> - <arg direction="out" name="URL" type="(sua(ss))" tp:type="Mail_URL" > - <tp:docstring> - A struture containing a URL and optional additional data to open a - webmail client, without re-authentication if possible. - </tp:docstring> - </arg> - <tp:docstring> - This method creates and returns a URL and an optional POST data that - allow opening the Inbox folder of a webmail account. This URL MAY - contain tokens with a short lifetime, so clients SHOULD request a new - URL for each visit to the webmail interface. This method is implemented - only if the <tt>Supports_Request_Inbox_URL</tt> flag is set in - <tp:member-ref>MailNotificationFlags</tp:member-ref>. - - <tp:rationale> - We are not using properties here because the tokens are unsuitable - for sharing between clients, and network round-trips may be required - to obtain the information that leads to authentication free webmail - access. - </tp:rationale> - </tp:docstring> - <tp:possible-errors> - <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/> - <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/> - <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"/> - </tp:possible-errors> - </method> - - <method name="RequestMailURL" - tp:name-for-bindings="Request_Mail_URL"> - <arg direction="in" name="ID" type="s"> - <tp:docstring> - The mail's <tt>id</tt> as found in the <tp:type>Mail</tp:type> - structure, or the empty string if no <tt>id</tt> key was provided. - </tp:docstring> - </arg> - <arg direction="in" name="URL_Data" type="v"> - <tp:docstring> - Whatever <tt>url-data</tt> was found in the <tp:type>Mail</tp:type> - structure, or the boolean value False (D-Bus type 'b') if no - <tt>url-data</tt> was provided in the Mail. - </tp:docstring> - </arg> - <arg direction="out" name="URL" type="(sua(ss))" tp:type="Mail_URL" > - <tp:docstring> - A struture that contains a URL and optional additional data to open a - webmail client, without re-authentication if possible. - </tp:docstring> - </arg> - <tp:docstring> - This method creates and returns a URL and optional POST data that - allow opening a specific mail in a webmail interface. This - method is implemented only if <tt>Supports_Request_Mail_URL</tt> flag - is set in <tp:member-ref>MailNotificationFlags</tp:member-ref>. - <tp:rationale> - See <tp:member-ref>RequestInboxURL</tp:member-ref> for design - rationale. - </tp:rationale> - </tp:docstring> - <tp:possible-errors> - <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/> - <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/> - <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"/> - <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/> - </tp:possible-errors> - </method> - - <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> - <p>An interface to support receiving notifications about a e-mail - account associated with this connection.</p> - - <p>In protocols where this is possible, this interface also allows the - connection manager to provide the necessary information for clients - to open a web-based mail client without having to re-authenticate.</p> - - <p>To use this interface, a client MUST first subscribe using the - <tp:member-ref>Subscribe</tp:member-ref> method. The subscription - mechanic aims at reducing network traffic and memory footprint in the - situation where nobody is currently interesting in provided - information. When done with this interface, clients SHOULD call - <tp:member-ref>Unsubscribe</tp:member-ref> to release resources in - the CM.</p> - - <p>Protocols have various different levels of Mail Notification support. - To describe the level of support, the interface provides a property - called <tp:member-ref>MailNotificationFlags</tp:member-ref>. - Not all combinations are valid; protocols can be divided into four - categories as follows.</p> - - <p>Connections to the most capable protocols, such as Google's XMPP Mail - Notification extension, have the Supports_Unread_Mails flag (this - implies that they must also have Supports_Unread_Mail_Count, but not - Emits_Mails_Received). On these connections, clients - requiring change notification MUST monitor the - <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal, and - either recover the initial state from the - <tp:member-ref>UnreadMails</tp:member-ref> property (if they require - details other than the number of mails) or the - <tp:member-ref>UnreadMailCount</tp:member-ref> property (if they - are only interested in the number of unread mails). The - <tp:member-ref>MailsReceived</tp:member-ref> signal is never emitted - on these connections, so clients that will display a short-term - notification for each new mail MUST do so in response to emission of - the <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal.</p> - - <p>The most common situation, seen in protocols like MSN and Yahoo, is - that the number of unread mails is provided and kept up-to-date, - and a separate notification is emitted with some details of each new - mail. This is a combination of the following two features, and clients - SHOULD implement one or both as appropriate for their requirements.</p> - - <p>On protocols that have the Emits_Mails_Received flag (which implies - that they do not have Supports_Unread_Mails), the CM does not keep - track of any mails; it simply emits a notification whenever new mail - arrives. Those events may be used for short term display (like a - notification popup) to inform the user. No protocol is known to support - only this feature, but it is useful for integration with libraries that - that do not implement tracking of the number of mails. Clients - requiring these notifications MUST monitor the - <tp:member-ref>MailsReceived</tp:member-ref> signal on any connections - with this flag.</p> - - <p>On protocols that have the Supports_Unread_Mail_Count flag but not - the Supports_Unread_Mails flag, clients cannot display complete - details of unread email, but can display an up-to-date count of the - <em>number</em> of unread mails. To do this, they must monitor the - <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal, and - retrieve the initial state from the - <tp:member-ref>UnreadMailCount</tp:member-ref> property.</p> - - <p> - Orthogonal features described by the - <tp:member-ref>MailNotificationFlags</tp:member-ref> property include the - RequestSomethingURL methods, which are used to obtain URLs allowing - clients to open a webmail client. Connections SHOULD support as many - of these methods as possible.</p> - </tp:docstring> - </interface> -</node> -<!-- vim:set sw=2 sts=2 et ft=xml: --> - diff --git a/extensions/Makefile.am b/extensions/Makefile.am index c10f042..239c599 100644 --- a/extensions/Makefile.am +++ b/extensions/Makefile.am @@ -2,7 +2,7 @@ tools_dir = $(top_srcdir)/tools EXTRA_DIST = \ all.xml \ - Connection_Interface_Mail_Notification.xml + $(NULL) noinst_LTLIBRARIES = libhaze-extensions.la @@ -52,7 +52,7 @@ _gen/svc.c _gen/svc.h: _gen/all.xml $(tools_dir)/glib-ginterface-gen.py \ Makefile.am $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-ginterface-gen.py \ --filename=_gen/svc --signal-marshal-prefix=_haze_ext \ - --include='<telepathy-glib/dbus.h>' \ + --include='<telepathy-glib/telepathy-glib.h>' \ --include='"_gen/signals-marshal.h"' \ --allow-unstable \ --not-implemented-func='tp_dbus_g_method_return_not_implemented' \ diff --git a/extensions/all.xml b/extensions/all.xml index 708cc7d..66b4a89 100644 --- a/extensions/all.xml +++ b/extensions/all.xml @@ -22,11 +22,6 @@ 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> -<tp:generic-types> - <tp:external-type name="Unix_Timestamp64" type="t" - from="Telepathy specification"/> -</tp:generic-types> - -<xi:include href="Connection_Interface_Mail_Notification.xml"/> +<!-- currently empty --> </tp:spec> diff --git a/extensions/extensions.c b/extensions/extensions.c index c81d126..77cc3f2 100644 --- a/extensions/extensions.c +++ b/extensions/extensions.c @@ -1,3 +1,4 @@ +#include <config.h> #include "extensions.h" /* auto-generated stubs */ diff --git a/src/Makefile.am b/src/Makefile.am index bc9fd28..59bae17 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,19 +8,6 @@ man_MANS = telepathy-haze.8 EXTRA_DIST = telepathy-haze.8.in CLEANFILES = $(man_MANS) -if MEDIA_ENABLED -haze_media_sources = media-backend.c \ - media-backend.h \ - media-channel.c \ - media-channel.h \ - media-manager.c \ - media-manager.h \ - media-stream.c \ - media-stream.h -else -haze_media_sources = -endif - telepathy_haze_SOURCES = main.c \ defines.h \ debug.c \ @@ -53,7 +40,7 @@ telepathy_haze_SOURCES = main.c \ request.h \ util.c \ util.h \ - $(haze_media_sources) + $(NULL) telepathy_haze_LDADD = $(top_builddir)/extensions/libhaze-extensions.la diff --git a/src/connection-aliasing.c b/src/connection-aliasing.c index 6d1017c..7b78a60 100644 --- a/src/connection-aliasing.c +++ b/src/connection-aliasing.c @@ -18,6 +18,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ + +#include <config.h> #include "connection-aliasing.h" #include <telepathy-glib/telepathy-glib.h> @@ -38,24 +40,57 @@ can_alias (HazeConnection *conn) return (prpl->alias_buddy != NULL); } -static void -haze_connection_get_alias_flags (TpSvcConnectionInterfaceAliasing *self, - DBusGMethodInvocation *context) -{ - TpBaseConnection *base = TP_BASE_CONNECTION (self); - HazeConnection *conn = HAZE_CONNECTION (base); - guint flags = 0; +typedef enum { + DP_FLAGS +} AliasingDBusProperty; - TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); +static TpDBusPropertiesMixinPropImpl props[] = { + { "AliasFlags", GINT_TO_POINTER (DP_FLAGS), NULL }, + { NULL } +}; +TpDBusPropertiesMixinPropImpl *haze_connection_aliasing_properties = props; - if (can_alias (conn)) +void +haze_connection_aliasing_properties_getter (GObject *object, + GQuark interface, + GQuark name, + GValue *value, + gpointer getter_data) +{ + AliasingDBusProperty which = GPOINTER_TO_INT (getter_data); + HazeConnection *conn = HAZE_CONNECTION (object); + TpBaseConnection *base = (TpBaseConnection *) conn; + + if (tp_base_connection_get_status (base) != TP_CONNECTION_STATUS_CONNECTED) { - flags = TP_CONNECTION_ALIAS_FLAG_USER_SET; + /* not CONNECTED yet, so our connection doesn't have the prpl info + * yet - return dummy values */ + if (G_VALUE_HOLDS_UINT (value)) + g_value_set_uint (value, 0); + else + g_assert_not_reached (); + + return; } - DEBUG ("alias flags: %u", flags); - tp_svc_connection_interface_aliasing_return_from_get_alias_flags ( - context, flags); + switch (which) + { + case DP_FLAGS: + { + guint flags = 0; + + if (can_alias (conn)) + { + flags = TP_CONNECTION_ALIAS_FLAG_USER_SET; + } + + g_value_set_uint (value, flags); + } + break; + + default: + g_assert_not_reached (); + } } static const gchar * @@ -68,7 +103,7 @@ get_alias (HazeConnection *self, const gchar *bname = tp_handle_inspect (contact_handles, handle); const gchar *alias; - if (handle == base->self_handle) + if (handle == tp_base_connection_get_self_handle (base)) { alias = purple_connection_get_display_name (self->account->gc); @@ -97,41 +132,6 @@ get_alias (HazeConnection *self, } static void -haze_connection_get_aliases (TpSvcConnectionInterfaceAliasing *self, - const GArray *contacts, - DBusGMethodInvocation *context) -{ - HazeConnection *conn = HAZE_CONNECTION (self); - TpBaseConnection *base = TP_BASE_CONNECTION (conn); - TpHandleRepoIface *contact_handles = - tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); - guint i; - GError *error = NULL; - GHashTable *aliases; - - if (!tp_handles_are_valid (contact_handles, contacts, FALSE, &error)) - { - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - aliases = g_hash_table_new (NULL, NULL); - - for (i = 0; i < contacts->len; i++) - { - TpHandle handle = g_array_index (contacts, TpHandle, i); - - g_hash_table_insert (aliases, GUINT_TO_POINTER (handle), - (gchar *) get_alias (conn, handle)); - } - - tp_svc_connection_interface_aliasing_return_from_get_aliases ( - context, aliases); - g_hash_table_destroy (aliases); -} - -static void haze_connection_request_aliases (TpSvcConnectionInterfaceAliasing *self, const GArray *contacts, DBusGMethodInvocation *context) @@ -177,31 +177,22 @@ set_alias_success_cb (PurpleAccount *account, const char *new_alias) { TpBaseConnection *base_conn; - GPtrArray *aliases; - GValue entry = {0, }; + GHashTable *aliases; DEBUG ("purple_account_set_public_alias succeeded, new alias %s", new_alias); base_conn = ACCOUNT_GET_TP_BASE_CONNECTION (account); - 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, base_conn->self_handle, - 1, new_alias, - G_MAXUINT); - - aliases = g_ptr_array_sized_new (1); - g_ptr_array_add (aliases, g_value_get_boxed (&entry)); + aliases = g_hash_table_new (NULL, NULL); + g_hash_table_insert (aliases, + GUINT_TO_POINTER (tp_base_connection_get_self_handle (base_conn)), + (gchar *) new_alias); tp_svc_connection_interface_aliasing_emit_aliases_changed (base_conn, aliases); - g_value_unset (&entry); - g_ptr_array_free (aliases, TRUE); + g_hash_table_unref (aliases); } static void @@ -228,7 +219,8 @@ set_aliases_foreach (gpointer key, { /* stop already */ } - else if (handle == TP_BASE_CONNECTION (data->conn)->self_handle) + else if (handle == tp_base_connection_get_self_handle ( + TP_BASE_CONNECTION (data->conn))) { DEBUG ("setting alias for myself to \"%s\"", new_alias); purple_account_set_public_alias (data->conn->account, @@ -316,8 +308,6 @@ haze_connection_aliasing_iface_init (gpointer g_iface, #define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\ klass, haze_connection_##x) - IMPLEMENT(get_alias_flags); - IMPLEMENT(get_aliases); IMPLEMENT(request_aliases); IMPLEMENT(set_aliases); #undef IMPLEMENT @@ -330,7 +320,7 @@ blist_node_aliased_cb (PurpleBlistNode *node, { PurpleBuddy *buddy; TpBaseConnection *base_conn; - GPtrArray *aliases; + GHashTable *aliases; TpHandle handle; TpHandleRepoIface *contact_handles; @@ -343,17 +333,15 @@ blist_node_aliased_cb (PurpleBlistNode *node, tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); handle = tp_handle_ensure (contact_handles, buddy->name, NULL, NULL); - aliases = g_ptr_array_sized_new (1); - g_ptr_array_add (aliases, tp_value_array_build (2, - G_TYPE_UINT, handle, - G_TYPE_STRING, purple_buddy_get_alias (buddy), - G_TYPE_INVALID)); + aliases = g_hash_table_new (NULL, NULL); + g_hash_table_insert (aliases, + GUINT_TO_POINTER (handle), + (gchar *) purple_buddy_get_alias (buddy)); tp_svc_connection_interface_aliasing_emit_aliases_changed (base_conn, aliases); - g_ptr_array_free (aliases, TRUE); - tp_handle_unref (contact_handles, handle); + g_hash_table_unref (aliases); } void diff --git a/src/connection-aliasing.h b/src/connection-aliasing.h index b1002ec..6bc565a 100644 --- a/src/connection-aliasing.h +++ b/src/connection-aliasing.h @@ -23,10 +23,15 @@ #include <glib-object.h> +#include <telepathy-glib/telepathy-glib.h> void haze_connection_aliasing_iface_init (gpointer g_iface, gpointer iface_data); void haze_connection_aliasing_class_init (GObjectClass *object_class); void haze_connection_aliasing_init (GObject *object); +extern TpDBusPropertiesMixinPropImpl *haze_connection_aliasing_properties; +void haze_connection_aliasing_properties_getter (GObject *object, + GQuark interface, GQuark name, GValue *value, gpointer getter_data); + #endif diff --git a/src/connection-avatars.c b/src/connection-avatars.c index 169950a..375bd2e 100644 --- a/src/connection-avatars.c +++ b/src/connection-avatars.c @@ -19,13 +19,13 @@ * */ +#include <config.h> #include "connection-avatars.h" #include <string.h> -#include <telepathy-glib/contacts-mixin.h> -#include <telepathy-glib/interfaces.h> -#include <telepathy-glib/svc-connection.h> +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/telepathy-glib-dbus.h> #include <libpurple/cipher.h> @@ -33,6 +33,25 @@ #include "debug.h" static gchar ** +dup_mime_types (PurpleBuddyIconSpec *icon_spec) +{ + gchar **mime_types, **i; + gchar *format; + + mime_types = g_strsplit (icon_spec->format, ",", 0); + + for (i = mime_types; *i != NULL; i++) + { + format = *i; + /* FIXME: image/ico is not the correct mime type. */ + *i = g_strconcat ("image/", format, NULL); + g_free (format); + } + + return mime_types; +} + +static gchar ** _get_acceptable_mime_types (HazeConnection *self) { PurplePluginProtocolInfo *prpl_info = HAZE_CONNECTION_GET_PRPL_INFO (self); @@ -41,20 +60,8 @@ _get_acceptable_mime_types (HazeConnection *self) if (self->acceptable_avatar_mime_types == NULL) { - gchar **mime_types, **i; - gchar *format; - - mime_types = g_strsplit (prpl_info->icon_spec.format, ",", 0); - - for (i = mime_types; *i != NULL; i++) - { - format = *i; - /* FIXME: image/ico is not the correct mime type. */ - *i = g_strconcat ("image/", format, NULL); - g_free (format); - } - - self->acceptable_avatar_mime_types = mime_types; + self->acceptable_avatar_mime_types = dup_mime_types ( + &prpl_info->icon_spec); } return self->acceptable_avatar_mime_types; @@ -97,7 +104,7 @@ haze_connection_avatars_properties_getter (GObject *object, PurplePluginProtocolInfo *prpl_info; PurpleBuddyIconSpec *icon_spec; - if (base->status != TP_CONNECTION_STATUS_CONNECTED) + if (tp_base_connection_get_status (base) != TP_CONNECTION_STATUS_CONNECTED) { /* not CONNECTED yet, so our connection doesn't have the prpl info * yet - return dummy values */ @@ -153,6 +160,36 @@ haze_connection_avatars_properties_getter (GObject *object, } } +void +haze_connection_get_icon_spec_requirements (PurpleBuddyIconSpec *icon_spec, + GStrv *mime_types, + guint *min_height, + guint *min_width, + guint *rec_height, + guint *rec_width, + guint *max_height, + guint *max_width, + guint *max_bytes) +{ + if (mime_types != NULL) + *mime_types = dup_mime_types (icon_spec); + if (min_height != NULL) + *min_height = icon_spec->min_height; + if (min_width != NULL) + *min_width = icon_spec->min_width; + /* libpurple has no recommendation */ + if (rec_height != NULL) + *rec_height = 0; + if (rec_width != NULL) + *rec_width = 0; + if (max_height != NULL) + *max_height = icon_spec->max_height; + if (max_width != NULL) + *max_width = icon_spec->max_width; + if (max_bytes != NULL) + *max_bytes = icon_spec->max_filesize; +} + static GArray * get_avatar (HazeConnection *conn, TpHandle handle) @@ -163,7 +200,8 @@ get_avatar (HazeConnection *conn, tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); gconstpointer icon_data = NULL; size_t icon_size = 0; - if (handle == base->self_handle) + + if (handle == tp_base_connection_get_self_handle (base)) { PurpleStoredImage *image = purple_buddy_icons_find_account_icon (conn->account); @@ -283,7 +321,7 @@ haze_connection_get_known_avatar_tokens (TpSvcConnectionInterfaceAvatars *self, * avatar you last used. So we special-case self_handle here. */ - if (handle == base_conn->self_handle) + if (handle == tp_base_connection_get_self_handle (base_conn)) { GArray *avatar = get_avatar (conn, handle); if (avatar != NULL) @@ -347,7 +385,7 @@ haze_connection_clear_avatar (TpSvcConnectionInterfaceAvatars *self, tp_svc_connection_interface_avatars_return_from_clear_avatar (context); tp_svc_connection_interface_avatars_emit_avatar_updated (conn, - base_conn->self_handle, ""); + tp_base_connection_get_self_handle (base_conn), ""); } static void @@ -425,7 +463,7 @@ haze_connection_set_avatar (TpSvcConnectionInterfaceAvatars *self, tp_svc_connection_interface_avatars_return_from_set_avatar (context, token); tp_svc_connection_interface_avatars_emit_avatar_updated (conn, - base_conn->self_handle, token); + tp_base_connection_get_self_handle (base_conn), token); g_free (token); } @@ -463,7 +501,6 @@ buddy_icon_changed_cb (PurpleBuddy *buddy, tp_svc_connection_interface_avatars_emit_avatar_updated (conn, contact, token); - tp_handle_unref (contact_repo, contact); g_free (token); } diff --git a/src/connection-avatars.h b/src/connection-avatars.h index 8517cc3..8ac37ab 100644 --- a/src/connection-avatars.h +++ b/src/connection-avatars.h @@ -23,7 +23,9 @@ #include <glib-object.h> -#include <telepathy-glib/dbus-properties-mixin.h> +#include <telepathy-glib/telepathy-glib.h> + +#include <libpurple/purple.h> void haze_connection_avatars_iface_init (gpointer g_iface, gpointer iface_data); void haze_connection_avatars_class_init (GObjectClass *object_class); @@ -33,4 +35,14 @@ extern TpDBusPropertiesMixinPropImpl *haze_connection_avatars_properties; void haze_connection_avatars_properties_getter (GObject *object, GQuark interface, GQuark name, GValue *value, gpointer getter_data); +void haze_connection_get_icon_spec_requirements (PurpleBuddyIconSpec *icon_spec, + GStrv *mime_types, + guint *min_height, + guint *min_width, + guint *rec_height, + guint *rec_width, + guint *max_height, + guint *max_width, + guint *max_bytes); + #endif diff --git a/src/connection-capabilities.c b/src/connection-capabilities.c index 0d5195f..5a05e29 100644 --- a/src/connection-capabilities.c +++ b/src/connection-capabilities.c @@ -29,119 +29,6 @@ #include "connection.h" #include "debug.h" -#ifdef ENABLE_MEDIA -#include "mediamanager.h" -#endif - -static void -free_rcc_list (GPtrArray *rccs) -{ - g_boxed_free (TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, rccs); -} - -#ifdef ENABLE_MEDIA -static PurpleMediaCaps -tp_flags_to_purple_caps (guint flags) -{ - PurpleMediaCaps caps = PURPLE_MEDIA_CAPS_NONE; - if (flags & TP_CHANNEL_MEDIA_CAPABILITY_AUDIO) - caps |= PURPLE_MEDIA_CAPS_AUDIO; - if (flags & TP_CHANNEL_MEDIA_CAPABILITY_VIDEO) - caps |= PURPLE_MEDIA_CAPS_VIDEO; - return caps; -} - -static guint -purple_caps_to_tp_flags (PurpleMediaCaps caps) -{ - guint flags = 0; - if (caps & PURPLE_MEDIA_CAPS_AUDIO) - flags |= TP_CHANNEL_MEDIA_CAPABILITY_AUDIO; - if (caps & PURPLE_MEDIA_CAPS_VIDEO) - flags |= TP_CHANNEL_MEDIA_CAPABILITY_VIDEO; - return flags; -} - -static GPtrArray * haze_connection_get_handle_contact_capabilities ( - HazeConnection *self, TpHandle handle); - -static void -_emit_capabilities_changed (HazeConnection *conn, - TpHandle handle, - const guint old_specific, - const guint new_specific) -{ - GPtrArray *caps_arr; - guint i; - - /* o.f.T.C.Capabilities */ - - caps_arr = g_ptr_array_new (); - - if (old_specific != 0 || new_specific != 0) - { - GValue caps_monster_struct = {0, }; - guint old_generic = old_specific ? - TP_CONNECTION_CAPABILITY_FLAG_CREATE | - TP_CONNECTION_CAPABILITY_FLAG_INVITE : 0; - guint new_generic = new_specific ? - TP_CONNECTION_CAPABILITY_FLAG_CREATE | - TP_CONNECTION_CAPABILITY_FLAG_INVITE : 0; - - if (0 != (old_specific ^ new_specific)) - { - g_value_init (&caps_monster_struct, - TP_STRUCT_TYPE_CAPABILITY_CHANGE); - g_value_take_boxed (&caps_monster_struct, - dbus_g_type_specialized_construct - (TP_STRUCT_TYPE_CAPABILITY_CHANGE)); - - dbus_g_type_struct_set (&caps_monster_struct, - 0, handle, - 1, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, - 2, old_generic, - 3, new_generic, - 4, old_specific, - 5, new_specific, - G_MAXUINT); - - g_ptr_array_add (caps_arr, g_value_get_boxed (&caps_monster_struct)); - } - } - - if (caps_arr->len) - tp_svc_connection_interface_capabilities_emit_capabilities_changed ( - conn, caps_arr); - - if (caps_arr->len > 0) - { - GHashTable *ret = g_hash_table_new_full (g_direct_hash, g_direct_equal, - NULL, (GDestroyNotify) free_rcc_list); - GPtrArray *arr; - - arr = haze_connection_get_handle_contact_capabilities (conn, handle); - g_hash_table_insert (ret, GUINT_TO_POINTER (handle), arr); - - tp_svc_connection_interface_contact_capabilities_emit_contact_capabilities_changed ( - conn, ret); - - g_hash_table_unref (ret); - } - - for (i = 0; i < caps_arr->len; i++) - { - g_boxed_free (TP_STRUCT_TYPE_CAPABILITY_CHANGE, - g_ptr_array_index (caps_arr, i)); - } - - g_ptr_array_free (caps_arr, TRUE); -} -#endif - -typedef enum { - CAPS_FLAGS_AUDIO = 1 << 0, - CAPS_FLAGS_VIDEO = 1 << 1, -} CapsFlags; static void haze_connection_update_capabilities (TpSvcConnectionInterfaceContactCapabilities *iface, @@ -150,76 +37,9 @@ haze_connection_update_capabilities (TpSvcConnectionInterfaceContactCapabilities { HazeConnection *self = HAZE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; -#ifdef ENABLE_MEDIA - guint i; - PurpleMediaCaps old_caps, caps; - GHashTableIter iter; - gpointer value; -#endif TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); -#ifdef ENABLE_MEDIA - caps = PURPLE_MEDIA_CAPS_NONE; - old_caps = purple_media_manager_get_ui_caps ( - purple_media_manager_get ()); - - DEBUG ("enter"); - - /* go through all the clients and if they can do audio or video save - * it in the client_caps hash table */ - 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 *rccs = g_value_get_boxed (va->values + 1); - guint j; - CapsFlags flags = 0; - - g_hash_table_remove (self->client_caps, client_name); - - for (j = 0; j < rccs->len; j++) - { - GHashTable *class = g_ptr_array_index (rccs, i); - - if (tp_strdiff (tp_asv_get_string (class, TP_PROP_CHANNEL_CHANNEL_TYPE), - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA)) - continue; - - if (tp_asv_get_boolean (class, - TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO, NULL)) - flags |= CAPS_FLAGS_AUDIO; - - if (tp_asv_get_boolean (class, - TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO, NULL)) - flags |= CAPS_FLAGS_VIDEO; - } - - if (flags != 0) - { - g_hash_table_insert (self->client_caps, g_strdup (client_name), - GUINT_TO_POINTER (flags)); - } - } - - /* now we have an updated client_caps hash table, go through it and - * let libpurple know */ - g_hash_table_iter_init (&iter, self->client_caps); - while (g_hash_table_iter_next (&iter, NULL, &value)) - { - CapsFlags flags = GPOINTER_TO_UINT (value); - - if (flags & CAPS_FLAGS_AUDIO) - caps |= PURPLE_MEDIA_CAPS_AUDIO; - if (flags & CAPS_FLAGS_VIDEO) - caps |= PURPLE_MEDIA_CAPS_VIDEO; - } - - purple_media_manager_set_ui_caps (purple_media_manager_get(), caps); - - _emit_capabilities_changed (self, base->self_handle, old_caps, caps); -#endif - tp_svc_connection_interface_contact_capabilities_return_from_update_capabilities ( context); } @@ -228,22 +48,6 @@ static GPtrArray * haze_connection_get_handle_contact_capabilities (HazeConnection *self, TpHandle handle) { -#ifdef ENABLE_MEDIA - PurpleAccount *account = self->account; - TpBaseConnection *conn = TP_BASE_CONNECTION (self); - TpHandleRepoIface *contact_handles = - tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); - const gchar *bname; - PurpleMediaCaps caps; - GValue media_monster = {0, }; - guint typeflags = 0; - const gchar * const sm_allowed_audio[] = { - TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO, NULL }; - const gchar * const sm_allowed_video[] = { - TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO, - TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO, - NULL }; -#endif GPtrArray *arr = g_ptr_array_new (); GValue monster = {0, }; GHashTable *fixed_properties; @@ -260,57 +64,6 @@ haze_connection_get_handle_contact_capabilities (HazeConnection *self, /* TODO: Check for presence */ -#ifdef ENABLE_MEDIA - if (handle == conn->self_handle) - caps = purple_media_manager_get_ui_caps (purple_media_manager_get ()); - else - { - bname = tp_handle_inspect (contact_handles, handle); - caps = purple_prpl_get_media_caps (account, bname); - } - - typeflags = purple_caps_to_tp_flags(caps); - - if (typeflags != 0) - { - const gchar * const *allowed; - - g_value_init (&media_monster, - TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS); - g_value_take_boxed (&media_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_STREAMED_MEDIA); - g_hash_table_insert (fixed_properties, TP_PROP_CHANNEL_CHANNEL_TYPE, - 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_PROP_CHANNEL_TARGET_HANDLE_TYPE, - target_handle_type_value); - - if (typeflags & TP_CHANNEL_MEDIA_CAPABILITY_VIDEO) - allowed = sm_allowed_video; - else - allowed = sm_allowed_audio; - - dbus_g_type_struct_set (&media_monster, - 0, fixed_properties, - 1, allowed, - G_MAXUINT); - - g_hash_table_unref (fixed_properties); - - g_ptr_array_add (arr, g_value_get_boxed (&media_monster)); - } -#endif - g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS); g_value_take_boxed (&monster, dbus_g_type_specialized_construct ( @@ -372,47 +125,6 @@ conn_capabilities_fill_contact_attributes_contact_caps ( } } -static void -haze_connection_get_contact_capabilities ( - TpSvcConnectionInterfaceContactCapabilities *svc, - const GArray *handles, - DBusGMethodInvocation *context) -{ - HazeConnection *self = HAZE_CONNECTION (svc); - 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) free_rcc_list); - - for (i = 0; i < handles->len; i++) - { - TpHandle handle = g_array_index (handles, TpHandle, i); - GPtrArray *arr; - - arr = haze_connection_get_handle_contact_capabilities (self, handle); - g_hash_table_insert (ret, GUINT_TO_POINTER (handle), arr); - } - - tp_svc_connection_interface_contact_capabilities_return_from_get_contact_capabilities - (context, ret); - - g_hash_table_unref (ret); -} - void haze_connection_contact_capabilities_iface_init (gpointer g_iface, gpointer iface_data) @@ -423,56 +135,13 @@ haze_connection_contact_capabilities_iface_init (gpointer g_iface, tp_svc_connection_interface_contact_capabilities_implement_##x (\ klass, haze_connection_##x) IMPLEMENT(update_capabilities); - IMPLEMENT(get_contact_capabilities); #undef IMPLEMENT } -#ifdef ENABLE_MEDIA -static void -caps_changed_cb (PurpleBuddy *buddy, - PurpleMediaCaps caps, - PurpleMediaCaps oldcaps) -{ - PurpleAccount *account = purple_buddy_get_account (buddy); - HazeConnection *conn = ACCOUNT_GET_HAZE_CONNECTION (account); - TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn); - TpHandleRepoIface *contact_repo = - tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); - const gchar *bname = purple_buddy_get_name(buddy); - TpHandle contact = tp_handle_ensure (contact_repo, bname, NULL, NULL); - - _emit_capabilities_changed (conn, contact, - purple_caps_to_tp_flags(oldcaps), - purple_caps_to_tp_flags(caps)); -} -#endif - -void -haze_connection_capabilities_class_init (GObjectClass *object_class) -{ -#ifdef ENABLE_MEDIA - purple_signal_connect (purple_blist_get_handle (), "buddy-caps-changed", - object_class, PURPLE_CALLBACK (caps_changed_cb), NULL); -#endif -} - void haze_connection_capabilities_init (GObject *object) { - HazeConnection *self = HAZE_CONNECTION (object); - tp_contacts_mixin_add_contact_attributes_iface (object, TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, conn_capabilities_fill_contact_attributes_contact_caps); - - self->client_caps = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) g_free, NULL); -} - -void -haze_connection_capabilities_finalize (GObject *object) -{ - HazeConnection *self = HAZE_CONNECTION (object); - - tp_clear_pointer (&self->client_caps, g_hash_table_unref); } diff --git a/src/connection-capabilities.h b/src/connection-capabilities.h index 791581b..e98bf98 100644 --- a/src/connection-capabilities.h +++ b/src/connection-capabilities.h @@ -25,8 +25,6 @@ void haze_connection_contact_capabilities_iface_init (gpointer g_iface, gpointer iface_data); -void haze_connection_capabilities_class_init (GObjectClass *object_class); void haze_connection_capabilities_init (GObject *object); -void haze_connection_capabilities_finalize (GObject *object); #endif diff --git a/src/connection-mail.c b/src/connection-mail.c index f286c27..767ee2d 100644 --- a/src/connection-mail.c +++ b/src/connection-mail.c @@ -17,12 +17,12 @@ * 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 "extensions/extensions.h" -#include <telepathy-glib/dbus.h> -#include <telepathy-glib/interfaces.h> -#include <telepathy-glib/svc-connection.h> +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/telepathy-glib-dbus.h> #include "connection.h" #include "connection-mail.h" @@ -41,32 +41,9 @@ enum static GPtrArray empty_array = { 0 }; - -static void -haze_connection_mail_subscribe ( - HazeSvcConnectionInterfaceMailNotification *iface, - DBusGMethodInvocation *context) -{ - /* Nothing do do, no resources attached to mail notification */ - haze_svc_connection_interface_mail_notification_return_from_subscribe ( - context); -} - - -static void -haze_connection_mail_unsubscribe ( - HazeSvcConnectionInterfaceMailNotification *iface, - DBusGMethodInvocation *context) -{ - /* Nothing do do, no resources attached to mail notification */ - haze_svc_connection_interface_mail_notification_return_from_unsubscribe ( - context); -} - - static void haze_connection_mail_request_inbox_url ( - HazeSvcConnectionInterfaceMailNotification *iface, + TpSvcConnectionInterfaceMailNotification *iface, DBusGMethodInvocation *context) { GError e = {TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, @@ -77,7 +54,7 @@ haze_connection_mail_request_inbox_url ( static void haze_connection_mail_request_mail_url ( - HazeSvcConnectionInterfaceMailNotification *iface, + TpSvcConnectionInterfaceMailNotification *iface, const gchar *in_id, const GValue *in_url_data, DBusGMethodInvocation *context) @@ -94,11 +71,11 @@ haze_connection_mail_request_mail_url ( result = tp_value_array_build (3, G_TYPE_STRING, g_value_get_string (in_url_data), - G_TYPE_UINT, HAZE_HTTP_METHOD_GET, - HAZE_ARRAY_TYPE_HTTP_POST_DATA_LIST, &empty_array, + G_TYPE_UINT, TP_HTTP_METHOD_GET, + TP_ARRAY_TYPE_HTTP_POST_DATA_LIST, &empty_array, G_TYPE_INVALID); - haze_svc_connection_interface_mail_notification_return_from_request_inbox_url ( + tp_svc_connection_interface_mail_notification_return_from_request_inbox_url ( context, result); g_value_array_free (result); @@ -116,12 +93,10 @@ void haze_connection_mail_iface_init (gpointer g_iface, gpointer iface_data) { - HazeSvcConnectionInterfaceMailNotificationClass *klass = g_iface; + TpSvcConnectionInterfaceMailNotificationClass *klass = g_iface; -#define IMPLEMENT(x) haze_svc_connection_interface_mail_notification_implement_##x (\ +#define IMPLEMENT(x) tp_svc_connection_interface_mail_notification_implement_##x (\ klass, haze_connection_mail_##x) - IMPLEMENT(subscribe); - IMPLEMENT(unsubscribe); IMPLEMENT(request_inbox_url); IMPLEMENT(request_mail_url); #undef IMPLEMENT @@ -181,8 +156,8 @@ haze_connection_mail_properties_getter (GObject *object, if (name == prop_quarks[PROP_MAIL_NOTIFICATION_FLAGS]) g_value_set_uint (value, - HAZE_MAIL_NOTIFICATION_FLAG_EMITS_MAILS_RECEIVED - | HAZE_MAIL_NOTIFICATION_FLAG_SUPPORTS_REQUEST_MAIL_URL); + TP_MAIL_NOTIFICATION_FLAG_EMITS_MAILS_RECEIVED + | TP_MAIL_NOTIFICATION_FLAG_SUPPORTS_REQUEST_MAIL_URL); else if (name == prop_quarks[PROP_UNREAD_MAIL_COUNT]) g_value_set_uint (value, 0); else if (name == prop_quarks[PROP_UNREAD_MAILS]) @@ -217,7 +192,7 @@ static GPtrArray * wrap_mail_address (const char *name_str, const char *addr_str) { GPtrArray *addr_array; - GType addr_type = HAZE_STRUCT_TYPE_MAIL_ADDRESS; + GType addr_type = TP_STRUCT_TYPE_MAIL_ADDRESS; GValue addr = {0}; addr_array = g_ptr_array_new (); @@ -247,8 +222,8 @@ haze_connection_mail_notify_emails (PurpleConnection *pc, const char **urls) { GPtrArray *mails; - HazeSvcConnectionInterfaceMailNotification *conn = - HAZE_SVC_CONNECTION_INTERFACE_MAIL_NOTIFICATION ( + TpSvcConnectionInterfaceMailNotification *conn = + TP_SVC_CONNECTION_INTERFACE_MAIL_NOTIFICATION ( ACCOUNT_GET_TP_BASE_CONNECTION ( purple_connection_get_account (pc))); @@ -281,7 +256,7 @@ haze_connection_mail_notify_emails (PurpleConnection *pc, /* Filter out any aberations */ if (from && to && subject && url) { - GType addr_list_type = HAZE_ARRAY_TYPE_MAIL_ADDRESS_LIST; + GType addr_list_type = TP_ARRAY_TYPE_MAIL_ADDRESS_LIST; GPtrArray *senders, *recipients; GHashTable *mail; @@ -317,7 +292,7 @@ haze_connection_mail_notify_emails (PurpleConnection *pc, } } - haze_svc_connection_interface_mail_notification_emit_mails_received ( + tp_svc_connection_interface_mail_notification_emit_mails_received ( conn, mails); g_ptr_array_unref (mails); diff --git a/src/connection-manager.c b/src/connection-manager.c index 6377b9e..58c802d 100644 --- a/src/connection-manager.c +++ b/src/connection-manager.c @@ -19,6 +19,9 @@ * */ +#include <config.h> +#include "connection-manager.h" + #include <string.h> #include <glib.h> @@ -27,9 +30,8 @@ #include <libpurple/prpl.h> #include <libpurple/accountopt.h> -#include <telepathy-glib/debug-sender.h> +#include <telepathy-glib/telepathy-glib.h> -#include "connection-manager.h" #include "debug.h" G_DEFINE_TYPE(HazeConnectionManager, @@ -95,9 +97,7 @@ haze_connection_manager_class_init (HazeConnectionManagerClass *klass) object_class->constructed = _haze_cm_constructed; object_class->finalize = _haze_cm_finalize; - base_class->new_connection = NULL; base_class->cm_dbus_name = "haze"; - base_class->protocol_params = NULL; g_type_class_add_private (klass, sizeof (HazeConnectionManagerPrivate)); } diff --git a/src/connection-manager.h b/src/connection-manager.h index b90f37f..4d24ff8 100644 --- a/src/connection-manager.h +++ b/src/connection-manager.h @@ -22,7 +22,7 @@ */ #include <glib-object.h> -#include <telepathy-glib/base-connection-manager.h> +#include <telepathy-glib/telepathy-glib.h> #include "protocol.h" #include "connection.h" diff --git a/src/connection-presence.c b/src/connection-presence.c index 812e5f9..ddc5809 100644 --- a/src/connection-presence.c +++ b/src/connection-presence.c @@ -19,10 +19,12 @@ * */ +#include <config.h> #include "connection-presence.h" + #include "debug.h" -#include <telepathy-glib/dbus.h> +#include <telepathy-glib/telepathy-glib.h> static const TpPresenceStatusOptionalArgumentSpec arg_specs[] = { { "message", "s" }, @@ -36,10 +38,12 @@ typedef enum { HAZE_STATUS_EXT_AWAY, HAZE_STATUS_INVISIBLE, HAZE_STATUS_OFFLINE, + HAZE_STATUS_UNKNOWN, HAZE_NUM_STATUSES } HazeStatusIndex; +/* Indexed by HazeStatusIndex */ static const TpPresenceStatusSpec statuses[] = { { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, arg_specs, NULL, NULL }, @@ -51,6 +55,7 @@ static const TpPresenceStatusSpec statuses[] = { arg_specs, NULL, NULL }, { "hidden", TP_CONNECTION_PRESENCE_TYPE_HIDDEN, TRUE, NULL, NULL, NULL }, { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL, NULL, NULL }, + { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL, NULL, NULL }, { NULL, TP_CONNECTION_PRESENCE_TYPE_UNSET, FALSE, NULL, NULL, NULL } }; @@ -61,7 +66,7 @@ static const PurpleStatusPrimitive primitives[] = { PURPLE_STATUS_AWAY, PURPLE_STATUS_EXTENDED_AWAY, PURPLE_STATUS_INVISIBLE, - PURPLE_STATUS_OFFLINE + PURPLE_STATUS_UNSET, }; /* Indexed by PurpleStatusPrimitive */ @@ -87,22 +92,36 @@ _get_tp_status (PurpleStatus *p_status) gchar *message; TpPresenceStatus *tp_status; - g_assert (p_status != NULL); + if (p_status == NULL) + { + status_ix = HAZE_STATUS_UNKNOWN; + } + else + { + type = purple_status_get_type (p_status); + prim = purple_status_type_get_primitive (type); - type = purple_status_get_type (p_status); - prim = purple_status_type_get_primitive (type); - status_ix = status_indices[prim]; + if (prim <= 0 || prim >= G_N_ELEMENTS (status_indices)) + { + /* guess wildly rather than crashing */ + status_ix = HAZE_STATUS_AVAILABLE; + } + else + { + status_ix = status_indices[prim]; + } - xhtml_message = purple_status_get_attr_string (p_status, "message"); - if (xhtml_message) - { - GValue *message_v = g_slice_new0 (GValue); + xhtml_message = purple_status_get_attr_string (p_status, "message"); + if (xhtml_message) + { + GValue *message_v = g_slice_new0 (GValue); - message = purple_markup_strip_html (xhtml_message); - g_value_init (message_v, G_TYPE_STRING); - g_value_set_string (message_v, message); - g_hash_table_insert (arguments, "message", message_v); - g_free (message); + message = purple_markup_strip_html (xhtml_message); + g_value_init (message_v, G_TYPE_STRING); + g_value_set_string (message_v, message); + g_hash_table_insert (arguments, "message", message_v); + g_free (message); + } } tp_status = tp_presence_status_new (status_ix, arguments); @@ -145,8 +164,7 @@ _status_available (GObject *obj, static GHashTable * _get_contact_statuses (GObject *obj, - const GArray *contacts, - GError **error) + const GArray *contacts) { GHashTable *status_table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); @@ -166,7 +184,7 @@ _get_contact_statuses (GObject *obj, g_assert (tp_handle_is_valid (handle_repo, handle, NULL)); - if (handle == base_conn->self_handle) + if (handle == tp_base_connection_get_self_handle (base_conn)) { p_status = purple_account_get_active_status (conn->account); } @@ -178,22 +196,18 @@ _get_contact_statuses (GObject *obj, if (buddy) { PurplePresence *presence = purple_buddy_get_presence (buddy); + p_status = purple_presence_get_active_status (presence); } else { DEBUG ("[%s] %s isn't on the blist, ergo no status!", conn->account->username, bname); - g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, - "Presence for %u unknown; subscribe to them first", handle); - g_hash_table_destroy (status_table); - status_table = NULL; - break; + p_status = NULL; } } tp_status = _get_tp_status (p_status); - g_hash_table_insert (status_table, GINT_TO_POINTER (handle), tp_status); } @@ -217,7 +231,7 @@ haze_connection_presence_account_status_changed (PurpleAccount *account, tp_status = _get_tp_status (status); tp_presence_mixin_emit_one_presence_update (G_OBJECT (base_conn), - base_conn->self_handle, tp_status); + tp_base_connection_get_self_handle (base_conn), tp_status); } } @@ -242,7 +256,6 @@ update_status (PurpleBuddy *buddy, tp_presence_mixin_emit_one_presence_update (G_OBJECT (conn), handle, tp_status); - tp_handle_unref (handle_repo, handle); } static void diff --git a/src/connection.c b/src/connection.c index e1b7761..8489f87 100644 --- a/src/connection.c +++ b/src/connection.c @@ -38,6 +38,7 @@ #include "connection-avatars.h" #include "connection-mail.h" #include "extensions/extensions.h" +#include "request.h" #include "connection-capabilities.h" @@ -79,7 +80,7 @@ G_DEFINE_TYPE_WITH_CODE(HazeConnection, tp_base_contact_list_mixin_groups_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING, tp_base_contact_list_mixin_blocking_iface_init); - G_IMPLEMENT_INTERFACE (HAZE_TYPE_SVC_CONNECTION_INTERFACE_MAIL_NOTIFICATION, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_MAIL_NOTIFICATION, haze_connection_mail_iface_init); ); @@ -87,7 +88,7 @@ static const gchar * implemented_interfaces[] = { /* Conditionally present */ TP_IFACE_CONNECTION_INTERFACE_AVATARS, - HAZE_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, + TP_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING, # define HAZE_NUM_CONDITIONAL_INTERFACES 3 @@ -107,16 +108,44 @@ static const gchar * implemented_interfaces[] = { NULL }; -const gchar ** -haze_connection_get_implemented_interfaces (void) +static void +add_always_present_connection_interfaces (GPtrArray *interfaces) +{ + const gchar **iter; + + for (iter = implemented_interfaces + HAZE_NUM_CONDITIONAL_INTERFACES; + *iter != NULL; iter++) + g_ptr_array_add (interfaces, (gchar *) *iter); +} + +static GPtrArray * +haze_connection_get_interfaces_always_present (TpBaseConnection *base) { - return implemented_interfaces; + GPtrArray *interfaces; + + interfaces = TP_BASE_CONNECTION_CLASS ( + haze_connection_parent_class)->get_interfaces_always_present (base); + + add_always_present_connection_interfaces (interfaces); + + return interfaces; } -const gchar ** -haze_connection_get_guaranteed_interfaces (void) +static void add_optional_connection_interfaces (GPtrArray *ifaces, + PurplePluginProtocolInfo *prpl_info); + +/* Returns a (transfer container) not NULL terminated of (const gchar *) + * interface names. */ +GPtrArray * +haze_connection_dup_implemented_interfaces (PurplePluginProtocolInfo *prpl_info) { - return implemented_interfaces + HAZE_NUM_CONDITIONAL_INTERFACES; + GPtrArray *ifaces; + + ifaces = g_ptr_array_new (); + add_always_present_connection_interfaces (ifaces); + add_optional_connection_interfaces (ifaces, prpl_info); + + return ifaces; } struct _HazeConnectionPrivate @@ -128,6 +157,9 @@ struct _HazeConnectionPrivate gchar *prpl_id; PurplePluginProtocolInfo *prpl_info; + /* Set if purple_account_request_password() was called */ + gpointer password_request; + /* Set if purple_account_disconnect has been called or is scheduled to be * called, so should not be called again. */ @@ -142,28 +174,56 @@ struct _HazeConnectionPrivate #define PC_GET_BASE_CONN(pc) \ (ACCOUNT_GET_TP_BASE_CONNECTION (purple_connection_get_account (pc))) +static gboolean +protocol_info_supports_avatar (PurplePluginProtocolInfo *prpl_info) +{ + return (prpl_info->icon_spec.format != NULL); +} + +static gboolean +protocol_info_supports_blocking (PurplePluginProtocolInfo *prpl_info) +{ + return (prpl_info->add_deny != NULL); +} + +static gboolean +protocol_info_supports_mail_notification (PurplePluginProtocolInfo *prpl_info) +{ + return ((prpl_info->options & OPT_PROTO_MAIL_CHECK) != 0); +} + +static void +add_optional_connection_interfaces (GPtrArray *ifaces, + PurplePluginProtocolInfo *prpl_info) +{ + if (protocol_info_supports_avatar (prpl_info)) + g_ptr_array_add (ifaces, + TP_IFACE_CONNECTION_INTERFACE_AVATARS); + + if (protocol_info_supports_blocking (prpl_info)) + g_ptr_array_add (ifaces, + TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING); + + if (protocol_info_supports_mail_notification (prpl_info)) + g_ptr_array_add (ifaces, + TP_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION); +} + static void connected_cb (PurpleConnection *pc) { TpBaseConnection *base_conn = PC_GET_BASE_CONN (pc); HazeConnection *conn = HAZE_CONNECTION (base_conn); PurplePluginProtocolInfo *prpl_info = HAZE_CONNECTION_GET_PRPL_INFO (conn); + GPtrArray *ifaces; - if (prpl_info->icon_spec.format != NULL) - { - static const gchar *avatar_ifaces[] = { - TP_IFACE_CONNECTION_INTERFACE_AVATARS, - NULL }; - tp_base_connection_add_interfaces (base_conn, avatar_ifaces); - } + ifaces = g_ptr_array_new (); + add_optional_connection_interfaces (ifaces, prpl_info); + g_ptr_array_add (ifaces, NULL); - if (prpl_info->add_deny != NULL) - { - static const gchar *blocking_ifaces[] = { - TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING, - NULL }; - tp_base_connection_add_interfaces (base_conn, blocking_ifaces); - } + tp_base_connection_add_interfaces (base_conn, + (const gchar **) ifaces->pdata); + g_ptr_array_unref (ifaces); tp_base_contact_list_set_list_received ( (TpBaseContactList *) conn->contact_list); @@ -289,7 +349,8 @@ haze_report_disconnect_reason (PurpleConnection *gc, priv->disconnecting = TRUE; map_purple_error_to_tp (reason, - (base_conn->status == TP_CONNECTION_STATUS_CONNECTING), + (tp_base_connection_get_status (base_conn) == + TP_CONNECTION_STATUS_CONNECTING), &tp_reason, &tp_error_name); details = tp_asv_new ("debug-message", G_TYPE_STRING, text, NULL); tp_base_connection_disconnect_with_dbus_error (base_conn, tp_error_name, @@ -314,7 +375,8 @@ disconnected_cb (PurpleConnection *pc) priv->disconnecting = TRUE; - if(base_conn->status != TP_CONNECTION_STATUS_DISCONNECTED) + if (tp_base_connection_get_status (base_conn) != + TP_CONNECTION_STATUS_DISCONNECTED) { /* Because we have report_disconnect_reason, if status is not already * DISCONNECTED, we know that it was requested. */ @@ -436,16 +498,25 @@ _haze_connection_password_manager_prompt_cb (GObject *source, { DEBUG ("Simple password manager failed: %s", error->message); - if (base_conn->status != TP_CONNECTION_STATUS_DISCONNECTED) + if (priv->password_request) + { + haze_request_password_cb (priv->password_request, NULL); + /* no need to call purple_account_disconnect(): the prpl will take + * the account offline. If we're lucky it'll use an + * AUTHENTICATION_FAILED-type message. + */ + } + else if (tp_base_connection_get_status (base_conn) != + TP_CONNECTION_STATUS_DISCONNECTED) { tp_base_connection_disconnect_with_dbus_error (base_conn, tp_error_get_dbus_name (error->code), NULL, TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED); + /* no need to call purple_account_disconnect because _connect + * was never called ... + */ } - /* no need to call purple_account_disconnect because _connect - * was never called */ - g_error_free (error); return; } @@ -453,11 +524,18 @@ _haze_connection_password_manager_prompt_cb (GObject *source, g_free (priv->password); priv->password = g_strdup (password->str); - purple_account_set_password (self->account, priv->password); + if (priv->password_request) + { + haze_request_password_cb (priv->password_request, priv->password); + } + else + { + purple_account_set_password (self->account, priv->password); - purple_account_set_enabled(self->account, UI_ID, TRUE); - purple_account_connect (self->account); - priv->connect_called = TRUE; + purple_account_set_enabled(self->account, UI_ID, TRUE); + purple_account_connect (self->account); + priv->connect_called = TRUE; + } } static gboolean @@ -469,13 +547,16 @@ _haze_connection_start_connecting (TpBaseConnection *base, TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); const gchar *password; + TpHandle self_handle; g_return_val_if_fail (self->account != NULL, FALSE); - base->self_handle = tp_handle_ensure (contact_handles, + self_handle = tp_handle_ensure (contact_handles, purple_account_get_username (self->account), NULL, error); - if (!base->self_handle) - return FALSE; + if (self_handle == 0) + return FALSE; + + tp_base_connection_set_self_handle (base, self_handle); tp_base_connection_change_status(base, TP_CONNECTION_STATUS_CONNECTING, TP_CONNECTION_STATUS_REASON_REQUESTED); @@ -506,6 +587,30 @@ _haze_connection_start_connecting (TpBaseConnection *base, return TRUE; } +void +haze_connection_request_password (PurpleAccount *account, + void *user_data) +{ + HazeConnection *self = ACCOUNT_GET_HAZE_CONNECTION (account); + HazeConnectionPrivate *priv = self->priv; + + priv->password_request = user_data; + + /* pop up auth channel */ + tp_simple_password_manager_prompt_async (self->password_manager, + _haze_connection_password_manager_prompt_cb, + self); +} + +void +haze_connection_cancel_password_request (PurpleAccount *account) +{ + HazeConnection *self = ACCOUNT_GET_HAZE_CONNECTION (account); + HazeConnectionPrivate *priv = self->priv; + + priv->password_request = NULL; +} + static void _haze_connection_shut_down (TpBaseConnection *base) { @@ -557,16 +662,6 @@ _haze_connection_create_channel_managers (TpBaseConnection *base) g_object_new (HAZE_TYPE_IM_CHANNEL_FACTORY, "connection", self, NULL)); g_ptr_array_add (channel_managers, self->im_factory); -#ifdef ENABLE_MEDIA - /* Instantiate the media manager only if the protocol support calls */ - if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC (self->priv->prpl_info, initiate_media)) - { - self->media_manager = HAZE_MEDIA_MANAGER ( - g_object_new (HAZE_TYPE_MEDIA_MANAGER, "connection", self, NULL)); - g_ptr_array_add (channel_managers, self->media_manager); - } -#endif - self->password_manager = tp_simple_password_manager_new ( TP_BASE_CONNECTION (self)); g_ptr_array_add (channel_managers, self->password_manager); @@ -711,8 +806,6 @@ haze_connection_finalize (GObject *object) tp_contacts_mixin_finalize (object); tp_presence_mixin_finalize (object); - haze_connection_capabilities_finalize (object); - g_strfreev (self->acceptable_avatar_mime_types); g_free (priv->username); g_free (priv->password); @@ -740,11 +833,15 @@ haze_connection_class_init (HazeConnectionClass *klass) { NULL } }; static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { TP_IFACE_CONNECTION_INTERFACE_ALIASING, + haze_connection_aliasing_properties_getter, + NULL, + NULL }, /* initialized a bit later */ { TP_IFACE_CONNECTION_INTERFACE_AVATARS, haze_connection_avatars_properties_getter, NULL, NULL }, /* initialized a bit later */ - { HAZE_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, + { TP_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, haze_connection_mail_properties_getter, NULL, mail_props, @@ -768,8 +865,8 @@ haze_connection_class_init (HazeConnectionClass *klass) haze_connection_get_unique_connection_name; base_class->start_connecting = _haze_connection_start_connecting; base_class->shut_down = _haze_connection_shut_down; - base_class->interfaces_always_present = - haze_connection_get_guaranteed_interfaces(); + base_class->get_interfaces_always_present = + haze_connection_get_interfaces_always_present; param_spec = g_param_spec_boxed ("parameters", "gchar * => GValue", "Connection parameters (password, etc.)", @@ -797,7 +894,8 @@ haze_connection_class_init (HazeConnectionClass *klass) G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_PRPL_INFO, param_spec); - prop_interfaces[0].props = haze_connection_avatars_properties; + prop_interfaces[0].props = haze_connection_aliasing_properties; + prop_interfaces[1].props = haze_connection_avatars_properties; klass->properties_class.interfaces = prop_interfaces; tp_dbus_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (HazeConnectionClass, properties_class)); @@ -809,7 +907,6 @@ haze_connection_class_init (HazeConnectionClass *klass) haze_connection_presence_class_init (object_class); haze_connection_aliasing_class_init (object_class); haze_connection_avatars_class_init (object_class); - haze_connection_capabilities_class_init (object_class); } static void diff --git a/src/connection.h b/src/connection.h index c69a7c1..0eef922 100644 --- a/src/connection.h +++ b/src/connection.h @@ -22,17 +22,13 @@ */ #include <glib-object.h> -#include <telepathy-glib/base-connection.h> -#include <telepathy-glib/contacts-mixin.h> -#include <telepathy-glib/presence-mixin.h> -#include <telepathy-glib/simple-password-manager.h> +#include <telepathy-glib/telepathy-glib.h> #include <libpurple/account.h> #include <libpurple/prpl.h> #include "contact-list.h" #include "im-channel-factory.h" -#include "media-manager.h" G_BEGIN_DECLS @@ -54,7 +50,6 @@ struct _HazeConnection { HazeContactList *contact_list; HazeImChannelFactory *im_factory; - HazeMediaManager *media_manager; TpSimplePasswordManager *password_manager; TpContactsMixin contacts; @@ -62,11 +57,6 @@ struct _HazeConnection { gchar **acceptable_avatar_mime_types; - GHashTable *client_caps; - - /* Part of the hack for Jabber media caps */ - gulong status_changed_id; - HazeConnectionPrivate *priv; }; @@ -108,9 +98,15 @@ GType haze_connection_get_type (void); const gchar *haze_get_fallback_group (void); -const gchar **haze_connection_get_implemented_interfaces (void); +GPtrArray * haze_connection_dup_implemented_interfaces ( + PurplePluginProtocolInfo *prpl_info); + const gchar **haze_connection_get_guaranteed_interfaces (void); +void haze_connection_request_password (PurpleAccount *account, + gpointer user_data); +void haze_connection_cancel_password_request (PurpleAccount *account); + G_END_DECLS #endif /* #ifndef __HAZE_CONNECTION_H__*/ diff --git a/src/contact-list.c b/src/contact-list.c index fbbc1f9..7397c1d 100644 --- a/src/contact-list.c +++ b/src/contact-list.c @@ -19,15 +19,14 @@ * */ +#include <config.h> +#include "contact-list.h" + #include <string.h> -#include <telepathy-glib/dbus.h> -#include <telepathy-glib/gtypes.h> -#include <telepathy-glib/interfaces.h> -#include <telepathy-glib/intset.h> +#include <telepathy-glib/telepathy-glib.h> #include "connection.h" -#include "contact-list.h" #include "debug.h" typedef struct _PublishRequestData PublishRequestData; @@ -193,10 +192,7 @@ haze_contact_list_dup_contacts (TpBaseContactList *cl) purple_buddy_get_name (sl_iter->data), NULL, NULL); if (G_LIKELY (handle != 0)) - { - tp_handle_set_add (handles, handle); - tp_handle_unref (contact_repo, handle); - } + tp_handle_set_add (handles, handle); } g_slist_free (buddies); @@ -311,8 +307,6 @@ buddy_added_cb (PurpleBuddy *buddy, gpointer unused) group_name = purple_group_get_name (purple_buddy_get_group (buddy)); tp_base_contact_list_one_contact_groups_changed ( (TpBaseContactList *) contact_list, handle, &group_name, 1, NULL, 0); - - tp_handle_unref (contact_repo, handle); } static void @@ -330,7 +324,8 @@ buddy_removed_cb (PurpleBuddy *buddy, gpointer unused) /* Every buddy gets removed after disconnection, because the PurpleAccount * gets deleted. So let's ignore removals when we're offline. */ - if (base_conn->status == TP_CONNECTION_STATUS_DISCONNECTED) + if (tp_base_connection_get_status (base_conn) == + TP_CONNECTION_STATUS_DISCONNECTED) return; contact_list = conn->contact_list; @@ -362,8 +357,6 @@ buddy_removed_cb (PurpleBuddy *buddy, gpointer unused) tp_base_contact_list_one_contact_removed ( (TpBaseContactList *) contact_list, handle); } - - tp_handle_unref (contact_repo, handle); } @@ -385,18 +378,11 @@ static void remove_pending_publish_request (HazeContactList *self, TpHandle handle) { - HazeConnection *conn = self->priv->conn; - TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn); - TpHandleRepoIface *handle_repo = - tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); - gpointer h = GUINT_TO_POINTER (handle); gboolean removed; removed = g_hash_table_remove (self->priv->pending_publish_requests, h); g_assert (removed); - - tp_handle_unref (handle_repo, handle); } void @@ -1037,7 +1023,7 @@ haze_contact_list_set_contact_groups_async (TpBaseContactList *cl, for (i = 0; i < n_names; i++) { - if (tp_strdiff (group_name, names[i])) + if (!tp_strdiff (group_name, names[i])) { desired = TRUE; break; diff --git a/src/contact-list.h b/src/contact-list.h index 59b75c2..647a40d 100644 --- a/src/contact-list.h +++ b/src/contact-list.h @@ -22,7 +22,9 @@ #include <glib-object.h> -#include <telepathy-glib/base-contact-list.h> +#include <libpurple/account.h> + +#include <telepathy-glib/telepathy-glib.h> G_BEGIN_DECLS diff --git a/src/debug.c b/src/debug.c index 976332b..6269edf 100644 --- a/src/debug.c +++ b/src/debug.c @@ -18,14 +18,14 @@ * */ +#include <config.h> #include "debug.h" #include <string.h> #include <stdarg.h> #include <libpurple/debug.h> -#include <telepathy-glib/debug.h> -#include <telepathy-glib/debug-sender.h> +#include <telepathy-glib/telepathy-glib.h> typedef enum diff --git a/src/im-channel-factory.c b/src/im-channel-factory.c index b4d0901..3147d98 100644 --- a/src/im-channel-factory.c +++ b/src/im-channel-factory.c @@ -265,28 +265,24 @@ new_im_channel (HazeImChannelFactory *self, TpHandle initiator, gpointer request_token) { - TpBaseConnection *conn; HazeIMChannel *chan; - char *object_path; GSList *requests = NULL; g_assert (HAZE_IS_IM_CHANNEL_FACTORY (self)); - conn = (TpBaseConnection *) self->priv->conn; - g_assert (!g_hash_table_lookup (self->priv->channels, GINT_TO_POINTER (handle))); - object_path = g_strdup_printf ("%s/ImChannel%u", conn->object_path, handle); - chan = g_object_new (HAZE_TYPE_IM_CHANNEL, "connection", self->priv->conn, - "object-path", object_path, "handle", handle, "initiator-handle", initiator, + "requested", (handle != initiator), NULL); + tp_base_channel_register (TP_BASE_CHANNEL (chan)); - DEBUG ("Created IM channel with object path %s", object_path); + DEBUG ("Created IM channel with object path %s", + tp_base_channel_get_object_path (TP_BASE_CHANNEL (chan))); g_signal_connect (chan, "closed", G_CALLBACK (im_channel_closed_cb), self); @@ -301,8 +297,6 @@ new_im_channel (HazeImChannelFactory *self, TP_EXPORTABLE_CHANNEL (chan), requests); g_slist_free (requests); - g_free (object_path); - return chan; } @@ -453,14 +447,6 @@ haze_create_conversation (PurpleConversation *conv) static void haze_destroy_conversation (PurpleConversation *conv) { - PurpleAccount *account = purple_conversation_get_account (conv); - - HazeImChannelFactory *im_factory = - ACCOUNT_GET_HAZE_CONNECTION (account)->im_factory; - TpBaseConnection *base_conn = TP_BASE_CONNECTION (im_factory->priv->conn); - TpHandleRepoIface *contact_repo = - tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); - HazeConversationUiData *ui_data; DEBUG ("(PurpleConversation *)%p destroyed", conv); @@ -472,7 +458,6 @@ haze_destroy_conversation (PurpleConversation *conv) ui_data = PURPLE_CONV_GET_HAZE_UI_DATA (conv); - tp_handle_unref (contact_repo, ui_data->contact_handle); if (ui_data->resend_typing_timeout_id) g_source_remove (ui_data->resend_typing_timeout_id); @@ -583,8 +568,9 @@ haze_im_channel_factory_request (HazeImChannelFactory *self, goto error; } - chan = get_im_channel (self, handle, base_conn->self_handle, - request_token, &created); + chan = get_im_channel (self, handle, + tp_base_connection_get_self_handle (base_conn), request_token, + &created); g_assert (chan != NULL); if (!created) diff --git a/src/im-channel.c b/src/im-channel.c index 11a174d..f4fa174 100644 --- a/src/im-channel.c +++ b/src/im-channel.c @@ -19,139 +19,92 @@ * */ +#include <config.h> +#include "im-channel.h" + #include <telepathy-glib/telepathy-glib.h> #include <telepathy-glib/telepathy-glib-dbus.h> -#include "im-channel.h" #include "connection.h" #include "debug.h" -/* properties */ -enum -{ - PROP_CONNECTION = 1, - PROP_OBJECT_PATH, - PROP_CHANNEL_TYPE, - PROP_HANDLE_TYPE, - PROP_HANDLE, - PROP_TARGET_ID, - PROP_INTERFACES, - PROP_INITIATOR_HANDLE, - PROP_INITIATOR_ID, - PROP_REQUESTED, - PROP_CHANNEL_PROPERTIES, - PROP_CHANNEL_DESTROYED, - - LAST_PROPERTY -}; - struct _HazeIMChannelPrivate { - HazeConnection *conn; - char *object_path; - TpHandle handle; - TpHandle initiator; - PurpleConversation *conv; - - gboolean closed; gboolean dispose_has_run; }; -static void channel_iface_init (gpointer, gpointer); static void destroyable_iface_init (gpointer g_iface, gpointer iface_data); static void chat_state_iface_init (gpointer g_iface, gpointer iface_data); -G_DEFINE_TYPE_WITH_CODE(HazeIMChannel, haze_im_channel, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init); +G_DEFINE_TYPE_WITH_CODE(HazeIMChannel, haze_im_channel, TP_TYPE_BASE_CHANNEL, G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT, tp_message_mixin_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DESTROYABLE, destroyable_iface_init); + + /* For some reason we reimplement ChatState rather than having the + * TpMessageMixin do it :-( */ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CHAT_STATE, - chat_state_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, - tp_dbus_properties_mixin_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL)) + chat_state_iface_init)) static void -haze_im_channel_close (TpSvcChannel *iface, - DBusGMethodInvocation *context) +haze_im_channel_close (TpBaseChannel *base) { - HazeIMChannel *self = HAZE_IM_CHANNEL (iface); - HazeIMChannelPrivate *priv = self->priv; + HazeIMChannel *self = HAZE_IM_CHANNEL (base); - if (priv->closed) - { - DEBUG ("Already closed"); - goto out; - } - - /* requires support from TpChannelManager */ + /* The IM factory will resurrect the channel if we have pending + * messages. When we're resurrected, we want the initiator + * to be the contact who sent us those messages, if it isn't already */ if (tp_message_mixin_has_pending_messages ((GObject *) self, NULL)) { - if (priv->initiator != priv->handle) - { - TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( - (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT); - - g_assert (priv->initiator != 0); - g_assert (priv->handle != 0); - - tp_handle_unref (contact_repo, priv->initiator); - priv->initiator = priv->handle; - tp_handle_ref (contact_repo, priv->initiator); - } - + DEBUG ("Not really closing, I still have pending messages"); tp_message_mixin_set_rescued ((GObject *) self); + tp_base_channel_reopened (base, + tp_base_channel_get_target_handle (base)); } else { - purple_conversation_destroy (priv->conv); - priv->conv = NULL; - priv->closed = TRUE; + tp_clear_pointer (&self->priv->conv, purple_conversation_destroy); + tp_base_channel_destroyed (base); } +} - tp_svc_channel_emit_closed (iface); +static PurpleAccount * +haze_im_channel_get_account (HazeIMChannel *self) +{ + TpBaseChannel *base = TP_BASE_CHANNEL (self); + TpBaseConnection *base_conn = tp_base_channel_get_connection (base); + HazeConnection *conn = HAZE_CONNECTION (base_conn); -out: - tp_svc_channel_return_from_close(context); + return conn->account; } static gboolean _chat_state_available (HazeIMChannel *chan) { PurplePluginProtocolInfo *prpl_info = - PURPLE_PLUGIN_PROTOCOL_INFO (chan->priv->conn->account->gc->prpl); + PURPLE_PLUGIN_PROTOCOL_INFO ( + haze_im_channel_get_account (chan)->gc->prpl); return (prpl_info->send_typing != NULL); } -static const char * const* -_haze_im_channel_interfaces (HazeIMChannel *chan) +static GPtrArray * +haze_im_channel_get_interfaces (TpBaseChannel *base) { - static const char * const interfaces[] = { - TP_IFACE_CHANNEL_INTERFACE_CHAT_STATE, - TP_IFACE_CHANNEL_INTERFACE_DESTROYABLE, - NULL - }; - - if (_chat_state_available (chan)) - return interfaces; - else - return interfaces + 1; -} + HazeIMChannel *self = HAZE_IM_CHANNEL (base); + GPtrArray *interfaces; -static void -channel_iface_init (gpointer g_iface, gpointer iface_data) -{ - TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface; + interfaces = TP_BASE_CHANNEL_CLASS ( + haze_im_channel_parent_class)->get_interfaces (base); -#define IMPLEMENT(x) tp_svc_channel_implement_##x (\ - klass, haze_im_channel_##x) - IMPLEMENT(close); -#undef IMPLEMENT + if (_chat_state_available (self)) + g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_CHAT_STATE); + + g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_DESTROYABLE); + + return interfaces; } /** @@ -173,9 +126,8 @@ haze_im_channel_destroy (TpSvcChannelInterfaceDestroyable *iface, /* Clear out any pending messages */ tp_message_mixin_clear ((GObject *) self); - /* Close() and Destroy() have the same signature, so we can safely - * chain to the other function now */ - haze_im_channel_close ((TpSvcChannel *) self, context); + haze_im_channel_close (TP_BASE_CHANNEL (self)); + tp_svc_channel_interface_destroyable_return_from_destroy (context); } static void @@ -397,123 +349,21 @@ err: } static void -haze_im_channel_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - HazeIMChannel *chan = HAZE_IM_CHANNEL (object); - HazeIMChannelPrivate *priv = chan->priv; - 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_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_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_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_CONNECTION: - g_value_set_object (value, priv->conn); - break; - case PROP_INTERFACES: - g_value_set_boxed (value, _haze_im_channel_interfaces (chan)); - 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_TEXT, - "MessagePartSupportFlags", - TP_IFACE_CHANNEL_TYPE_TEXT, - "DeliveryReportingSupport", - TP_IFACE_CHANNEL_TYPE_TEXT, - "SupportedContentTypes", - TP_IFACE_CHANNEL_TYPE_TEXT, "MessageTypes", - NULL)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -haze_im_channel_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) +haze_im_channel_fill_immutable_properties (TpBaseChannel *chan, + GHashTable *properties) { - HazeIMChannel *chan = HAZE_IM_CHANNEL (object); - HazeIMChannelPrivate *priv = chan->priv; - - switch (property_id) { - case PROP_OBJECT_PATH: - g_free (priv->object_path); - priv->object_path = g_value_dup_string (value); - break; - case PROP_HANDLE: - /* we don't ref it here because we don't have access to the - * contact repo yet - instead we ref it in the constructor. - */ - priv->handle = g_value_get_uint (value); - break; - case PROP_INITIATOR_HANDLE: - /* similarly we can't ref this yet */ - priv->initiator = g_value_get_uint (value); - break; - case PROP_CHANNEL_TYPE: - case PROP_HANDLE_TYPE: - /* this property is writable in the interface, but not actually - * meaningfully changable on this channel, so we do nothing. - */ - break; - case PROP_CONNECTION: - priv->conn = g_value_get_object (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } + TpBaseChannelClass *cls = TP_BASE_CHANNEL_CLASS ( + haze_im_channel_parent_class); + + cls->fill_immutable_properties (chan, properties); + + tp_dbus_properties_mixin_fill_properties_hash ( + G_OBJECT (chan), properties, + TP_IFACE_CHANNEL_TYPE_TEXT, "MessagePartSupportFlags", + TP_IFACE_CHANNEL_TYPE_TEXT, "DeliveryReportingSupport", + TP_IFACE_CHANNEL_TYPE_TEXT, "SupportedContentTypes", + TP_IFACE_CHANNEL_TYPE_TEXT, "MessageTypes", + NULL); } static const TpChannelTextMessageType supported_message_types[] = { @@ -534,31 +384,21 @@ haze_im_channel_constructor (GType type, guint n_props, GObject *obj; HazeIMChannel *chan; HazeIMChannelPrivate *priv; - TpHandleRepoIface *contact_handles; + TpBaseChannel *base; TpBaseConnection *conn; - TpDBusDaemon *bus; obj = G_OBJECT_CLASS (haze_im_channel_parent_class)-> constructor (type, n_props, props); chan = HAZE_IM_CHANNEL (obj); + base = TP_BASE_CHANNEL (obj); priv = chan->priv; - conn = (TpBaseConnection *) (priv->conn); - - contact_handles = tp_base_connection_get_handles (conn, - TP_HANDLE_TYPE_CONTACT); - tp_handle_ref (contact_handles, priv->handle); - g_assert (priv->initiator != 0); - tp_handle_ref (contact_handles, priv->initiator); + conn = tp_base_channel_get_connection (base); tp_message_mixin_init (obj, G_STRUCT_OFFSET (HazeIMChannel, messages), conn); tp_message_mixin_implement_sending (obj, haze_im_channel_send, 3, supported_message_types, 0, 0, supported_content_types); - bus = tp_base_connection_get_dbus_daemon (conn); - tp_dbus_daemon_register_object (bus, priv->object_path, obj); - - priv->closed = FALSE; priv->dispose_has_run = FALSE; return obj; @@ -569,128 +409,46 @@ haze_im_channel_dispose (GObject *obj) { HazeIMChannel *chan = HAZE_IM_CHANNEL (obj); HazeIMChannelPrivate *priv = chan->priv; - TpBaseConnection *conn = (TpBaseConnection *) priv->conn; - TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (conn, - TP_HANDLE_TYPE_CONTACT); if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; - if (priv->handle != 0) - tp_handle_unref (contact_handles, priv->handle); + tp_clear_pointer (&priv->conv, purple_conversation_destroy); - if (priv->initiator != 0) - tp_handle_unref (contact_handles, priv->initiator); + tp_message_mixin_finalize (obj); - if (!priv->closed) - { - purple_conversation_destroy (priv->conv); - priv->conv = NULL; - tp_svc_channel_emit_closed (obj); - priv->closed = TRUE; - } + G_OBJECT_CLASS (haze_im_channel_parent_class)->dispose (obj); +} - g_free (priv->object_path); - tp_message_mixin_finalize (obj); +static gchar * +haze_im_channel_get_object_path_suffix (TpBaseChannel *chan) +{ + return g_strdup_printf ("IMChannel%u", + tp_base_channel_get_target_handle (chan)); } static void haze_im_channel_class_init (HazeIMChannelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - GParamSpec *param_spec; - - static gboolean properties_mixin_initialized = FALSE; - 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 } - }; - + TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (klass); g_type_class_add_private (klass, sizeof (HazeIMChannelPrivate)); - object_class->get_property = haze_im_channel_get_property; - object_class->set_property = haze_im_channel_set_property; object_class->constructor = haze_im_channel_constructor; object_class->dispose = haze_im_channel_dispose; - 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_object ("connection", "HazeConnection object", - "Haze connection object that owns this IM channel object.", - HAZE_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); - - param_spec = g_param_spec_string ("target-id", "Other person's username", - "The username of the other person in the conversation", - 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's ID", - "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); - - - if (!properties_mixin_initialized) - { - properties_mixin_initialized = TRUE; - klass->properties_class.interfaces = prop_interfaces; - tp_dbus_properties_mixin_class_init (object_class, - G_STRUCT_OFFSET (HazeIMChannelClass, properties_class)); + base_class->channel_type = TP_IFACE_CHANNEL_TYPE_TEXT; + base_class->get_interfaces = haze_im_channel_get_interfaces; + base_class->target_handle_type = TP_HANDLE_TYPE_CONTACT; + base_class->close = haze_im_channel_close; + base_class->fill_immutable_properties = + haze_im_channel_fill_immutable_properties; + base_class->get_object_path_suffix = + haze_im_channel_get_object_path_suffix; - tp_message_mixin_init_dbus_properties (object_class); - } + tp_message_mixin_init_dbus_properties (object_class); } static void @@ -706,14 +464,16 @@ haze_im_channel_start (HazeIMChannel *self) const char *recipient; HazeIMChannelPrivate *priv = self->priv; TpHandleRepoIface *contact_handles; - TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn; + TpBaseChannel *base = TP_BASE_CHANNEL (self); + TpBaseConnection *base_conn = tp_base_channel_get_connection (base); + HazeConnection *conn = HAZE_CONNECTION (base_conn); contact_handles = tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); - recipient = tp_handle_inspect(contact_handles, priv->handle); + recipient = tp_handle_inspect (contact_handles, + tp_base_channel_get_target_handle (base)); priv->conv = purple_conversation_new (PURPLE_CONV_TYPE_IM, - priv->conn->account, - recipient); + conn->account, recipient); } static TpMessage * @@ -722,7 +482,8 @@ _make_message (HazeIMChannel *self, PurpleMessageFlags flags, time_t mtime) { - TpBaseConnection *base_conn = (TpBaseConnection *) self->priv->conn; + TpBaseChannel *base = TP_BASE_CHANNEL (self); + TpBaseConnection *base_conn = tp_base_channel_get_connection (base); TpMessage *message = tp_cm_message_new (base_conn, 2); TpChannelTextMessageType type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL; time_t now = time (NULL); @@ -732,7 +493,8 @@ _make_message (HazeIMChannel *self, else if (purple_message_meify (text_plain, -1)) type = TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION; - tp_cm_message_set_sender (message, self->priv->handle); + tp_cm_message_set_sender (message, + tp_base_channel_get_target_handle (base)); tp_message_set_uint32 (message, 0, "message-type", type); /* FIXME: the second half of this test shouldn't be necessary but prpl-jabber @@ -754,11 +516,13 @@ static TpMessage * _make_delivery_report (HazeIMChannel *self, char *text_plain) { - TpBaseConnection *base_conn = (TpBaseConnection *) self->priv->conn; + TpBaseChannel *base = TP_BASE_CHANNEL (self); + TpBaseConnection *base_conn = tp_base_channel_get_connection (base); TpMessage *report = tp_cm_message_new (base_conn, 2); /* "MUST be the intended recipient of the original message" */ - tp_cm_message_set_sender (report, self->priv->handle); + tp_cm_message_set_sender (report, + tp_base_channel_get_target_handle (base)); tp_message_set_uint32 (report, 0, "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT); /* FIXME: we don't know that the failure is temporary */ @@ -781,6 +545,7 @@ haze_im_channel_receive (HazeIMChannel *self, PurpleMessageFlags flags, time_t mtime) { + TpBaseChannel *base = TP_BASE_CHANNEL (self); gchar *line_broken, *text_plain; /* Replaces newline characters with <br>, which then get turned back into @@ -802,7 +567,7 @@ haze_im_channel_receive (HazeIMChannel *self, _make_delivery_report (self, text_plain)); else DEBUG ("channel %u: ignoring message %s with flags %u", - self->priv->handle, text_plain, flags); + tp_base_channel_get_target_handle (base), text_plain, flags); g_free (text_plain); } diff --git a/src/im-channel.h b/src/im-channel.h index 6427828..3486d05 100644 --- a/src/im-channel.h +++ b/src/im-channel.h @@ -35,13 +35,13 @@ typedef struct _HazeIMChannelClass HazeIMChannelClass; struct _HazeIMChannelClass { - GObjectClass parent_class; + TpBaseChannelClass parent_class; TpDBusPropertiesMixinClass properties_class; }; struct _HazeIMChannel { - GObject parent; + TpBaseChannel parent; TpMessageMixin messages; @@ -28,6 +28,7 @@ #include <errno.h> #include <signal.h> +#include <dbus/dbus.h> #include <glib.h> #include <libpurple/account.h> @@ -38,15 +39,11 @@ #include <libpurple/prefs.h> #include <libpurple/util.h> -#ifdef ENABLE_MEDIA -#include <libpurple/mediamanager.h> -#endif - #ifdef HAVE_PURPLE_DBUS_UNINIT #include <libpurple/dbus-server.h> #endif -#include <telepathy-glib/run.h> +#include <telepathy-glib/telepathy-glib.h> #include "defines.h" #include "debug.h" @@ -55,10 +52,6 @@ #include "request.h" #include "util.h" -#ifdef ENABLE_MEDIA -#include "media-backend.h" -#endif - /* Copied verbatim from nullclient, modulo changing whitespace. */ #define PURPLE_GLIB_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR) #define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL) @@ -141,9 +134,7 @@ haze_ui_init (void) purple_accounts_set_ui_ops (haze_get_account_ui_ops ()); purple_conversations_set_ui_ops (haze_get_conv_ui_ops ()); purple_connections_set_ui_ops (haze_get_connection_ui_ops ()); -#ifdef ENABLE_LEAKY_REQUEST_STUBS purple_request_set_ui_ops (haze_request_get_ui_ops ()); -#endif purple_notify_set_ui_ops (haze_notify_get_ui_ops ()); purple_privacy_set_ui_ops (haze_get_privacy_ui_ops ()); } @@ -213,11 +204,6 @@ init_libpurple (void) PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_MICRO_VERSION); set_libpurple_preferences (); - -#ifdef ENABLE_MEDIA - purple_media_manager_set_backend_type (purple_media_manager_get (), - HAZE_TYPE_MEDIA_BACKEND); -#endif } static TpBaseConnectionManager * @@ -254,6 +240,10 @@ main(int argc, { int ret = 0; + if (!dbus_threads_init_default ()) + g_error ("Unable to initialize libdbus for thread-safety " + "(out of memory?)"); + g_set_prgname(UI_ID); haze_debug_set_flags_from_env (); diff --git a/src/media-backend.c b/src/media-backend.c deleted file mode 100644 index ec40af9..0000000 --- a/src/media-backend.c +++ /dev/null @@ -1,615 +0,0 @@ -/* - * media-backend.c - Source for HazeMediaBackend - * Copyright © 2006-2009 Collabora Ltd. - * Copyright © 2006-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 "media-backend.h" - -#include <libpurple/media/backend-iface.h> -#include <telepathy-glib/dbus.h> -#include <telepathy-glib/errors.h> -#include <telepathy-glib/svc-media-interfaces.h> - -#include <string.h> - -#include "debug.h" - -static void media_backend_iface_init(PurpleMediaBackendIface *iface); -static void session_handler_iface_init (gpointer g_iface, - gpointer iface_data); -static void haze_backend_state_changed_cb (PurpleMedia *media, - PurpleMediaState state, - const gchar *sid, - const gchar *name, - HazeMediaBackend *backend); - -G_DEFINE_TYPE_WITH_CODE (HazeMediaBackend, - haze_media_backend, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (PURPLE_TYPE_MEDIA_BACKEND, - media_backend_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_SESSION_HANDLER, - session_handler_iface_init); - ) - -/* properties */ -enum -{ - PROP_CONFERENCE_TYPE = 1, - PROP_MEDIA, - PROP_OBJECT_PATH, - PROP_STREAMS, - LAST_PROPERTY -}; - -/* private structure */ -struct _HazeMediaBackendPrivate -{ - gchar *conference_type; - gchar *object_path; - gpointer media; - GPtrArray *streams; - - guint next_stream_id; - gboolean ready; -}; - -static void -haze_media_backend_init (HazeMediaBackend *self) -{ - HazeMediaBackendPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - HAZE_TYPE_MEDIA_BACKEND, HazeMediaBackendPrivate); - - self->priv = priv; - - priv->next_stream_id = 1; - priv->streams = g_ptr_array_sized_new (1); -} - -static void -haze_media_backend_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - HazeMediaBackend *backend = HAZE_MEDIA_BACKEND (object); - HazeMediaBackendPrivate *priv = backend->priv; - - switch (property_id) - { - case PROP_CONFERENCE_TYPE: - g_value_set_string (value, priv->conference_type); - break; - case PROP_MEDIA: - g_value_set_object (value, priv->media); - break; - case PROP_OBJECT_PATH: - g_value_set_string (value, priv->object_path); - break; - case PROP_STREAMS: - g_value_set_boxed (value, priv->streams); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -haze_media_backend_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - HazeMediaBackend *backend = HAZE_MEDIA_BACKEND (object); - HazeMediaBackendPrivate *priv = backend->priv; - - switch (property_id) - { - case PROP_CONFERENCE_TYPE: - g_free (priv->conference_type); - priv->conference_type = g_value_dup_string (value); - break; - case PROP_MEDIA: - g_assert (priv->media == NULL); - priv->media = g_value_get_object (value); - - g_object_add_weak_pointer(G_OBJECT(priv->media), &priv->media); - g_signal_connect (priv->media, "state-changed", - G_CALLBACK (haze_backend_state_changed_cb), backend); - break; - case PROP_OBJECT_PATH: - g_assert (priv->object_path == NULL); - priv->object_path = g_value_dup_string (value); - - if (priv->object_path != NULL) - { - TpDBusDaemon *dbus_daemon = tp_dbus_daemon_dup (NULL); - - g_return_if_fail (dbus_daemon != NULL); - tp_dbus_daemon_register_object (dbus_daemon, - priv->object_path, G_OBJECT (backend)); - g_object_unref (dbus_daemon); - } - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void haze_media_backend_dispose (GObject *object); -static void haze_media_backend_finalize (GObject *object); - -static void -haze_media_backend_class_init (HazeMediaBackendClass *haze_media_backend_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (haze_media_backend_class); - GParamSpec *param_spec; - - g_type_class_add_private (haze_media_backend_class, - sizeof (HazeMediaBackendPrivate)); - - object_class->get_property = haze_media_backend_get_property; - object_class->set_property = haze_media_backend_set_property; - - object_class->dispose = haze_media_backend_dispose; - object_class->finalize = haze_media_backend_finalize; - - g_object_class_override_property(object_class, PROP_CONFERENCE_TYPE, - "conference-type"); - g_object_class_override_property(object_class, PROP_MEDIA, "media"); - - param_spec = g_param_spec_string ("object-path", "D-Bus object path", - "The D-Bus object path used for this " - "object on the bus.", - NULL, - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); - - param_spec = g_param_spec_boxed ("streams", "Streams", - "List of streams handled by this backend.", - G_TYPE_PTR_ARRAY, - G_PARAM_READABLE | - G_PARAM_STATIC_NAME | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_STREAMS, param_spec); -} - -void -haze_media_backend_dispose (GObject *object) -{ - DEBUG ("called"); - - if (G_OBJECT_CLASS (haze_media_backend_parent_class)->dispose) - G_OBJECT_CLASS (haze_media_backend_parent_class)->dispose (object); -} - -void -haze_media_backend_finalize (GObject *object) -{ - HazeMediaBackend *self = HAZE_MEDIA_BACKEND (object); - HazeMediaBackendPrivate *priv = self->priv; - - g_free (priv->conference_type); - g_free (priv->object_path); - - if (priv->streams != NULL) - g_ptr_array_free (priv->streams, TRUE); - - G_OBJECT_CLASS (haze_media_backend_parent_class)->finalize (object); -} - -static HazeMediaStream * -get_stream_by_name (HazeMediaBackend *self, - const gchar *sid) -{ - HazeMediaBackendPrivate *priv = self->priv; - guint i; - - for (i = 0; i < priv->streams->len; ++i) - { - HazeMediaStream *stream = g_ptr_array_index (priv->streams, i); - - if (!strcmp (sid, stream->name)) - return stream; - } - - return NULL; -} - -HazeMediaStream * -haze_media_backend_get_stream_by_name (HazeMediaBackend *self, - const gchar *sid) -{ - return get_stream_by_name (self, sid); -} - -static void -haze_backend_state_changed_cb (PurpleMedia *media, - PurpleMediaState state, - const gchar *sid, - const gchar *name, - HazeMediaBackend *backend) -{ - HazeMediaBackendPrivate *priv = backend->priv; - - if (state == PURPLE_MEDIA_STATE_END && sid != NULL && name == NULL) - { - HazeMediaStream *stream = get_stream_by_name (backend, sid); - - if (stream != NULL) - { - g_ptr_array_remove_fast (priv->streams, stream); - g_object_unref (stream); - } - } -} - -static void -_emit_new_stream (HazeMediaBackend *self, - HazeMediaStream *stream) -{ - gchar *object_path; - guint id, media_type; - - g_object_get (stream, - "object-path", &object_path, - "id", &id, - "media-type", &media_type, - NULL); - - /* all of the streams are bidirectional from farsight's point of view, it's - * just in the signalling they change */ - DEBUG ("emitting MediaSessionHandler:NewStreamHandler signal for %s stream %d", - media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video", id); - tp_svc_media_session_handler_emit_new_stream_handler (self, - object_path, id, media_type, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL); - - g_free (object_path); -} - -static gboolean -haze_media_backend_add_stream (PurpleMediaBackend *self, - const gchar *sid, const gchar *who, - PurpleMediaSessionType type, gboolean initiator, - const gchar *transmitter, - guint num_params, GParameter *params) -{ - HazeMediaBackendPrivate *priv = HAZE_MEDIA_BACKEND (self)->priv; - HazeMediaStream *stream; - gchar *object_path; - guint media_type, id, stun_port = 3478; /* default stun port */ - const gchar *nat_traversal = NULL, *stun_server = NULL; - TpDBusDaemon *dbus_daemon = tp_dbus_daemon_dup (NULL); - - DEBUG ("called"); - - g_return_val_if_fail (dbus_daemon != NULL, FALSE); - - id = priv->next_stream_id++; - - object_path = g_strdup_printf ("%s/MediaStream%u", - priv->object_path, id); - - if (type & PURPLE_MEDIA_AUDIO) - media_type = TP_MEDIA_STREAM_TYPE_AUDIO; - else - media_type = TP_MEDIA_STREAM_TYPE_VIDEO; - - if (!strcmp (transmitter, "nice")) - { - guint i; - - for (i = 0; i < num_params; ++i) - { - if (!strcmp (params[i].name, "compatibility-mode") && - G_VALUE_HOLDS (¶ms[i].value, G_TYPE_UINT)) - { - guint mode = g_value_get_uint (¶ms[i].value); - - switch (mode) - { - case 0: /* NICE_COMPATIBILITY_DRAFT19 */ - nat_traversal = "ice-udp"; - break; - case 1: /* NICE_COMPATIBILITY_GOOGLE */ - nat_traversal = "gtalk-p2p"; - break; - case 2: /* NICE_COMPATIBILITY_MSN */ - nat_traversal = "wlm-8.5"; - break; - case 3: /* NICE_COMPATIBILITY_WLM2009 */ - nat_traversal = "wlm-2009"; - break; - default: - g_assert_not_reached (); - } - } - else if (!strcmp (params[i].name, "stun-ip") && - G_VALUE_HOLDS (¶ms[i].value, G_TYPE_STRING)) - { - stun_server = g_value_get_string (¶ms[i].value); - } - else if (!strcmp (params[i].name, "stun-port") && - G_VALUE_HOLDS (¶ms[i].value, G_TYPE_UINT)) - { - stun_port = g_value_get_uint (¶ms[i].value); - } - } - - if (nat_traversal == NULL) - nat_traversal = "ice-udp"; - } - else if (!strcmp (transmitter, "rawudp")) - { - nat_traversal = "none"; - } - else - { - g_assert_not_reached (); - } - - stream = haze_media_stream_new (object_path, dbus_daemon, priv->media, - sid, who, media_type, id, initiator, nat_traversal, NULL, FALSE); - - if (stun_server != NULL) - haze_media_stream_add_stun_server (stream, stun_server, stun_port); - - g_free (object_path); - - DEBUG ("%p: created new MediaStream %p for sid '%s'", - self, stream, sid); - - g_ptr_array_add (priv->streams, stream); - - if (priv->ready) - _emit_new_stream (HAZE_MEDIA_BACKEND (self), stream); - - g_object_unref (dbus_daemon); - - return TRUE; -} - -static void -haze_media_backend_add_remote_candidates (PurpleMediaBackend *self, - const gchar *sid, - const gchar *who, - GList *remote_candidates) -{ - HazeMediaStream *stream; - - DEBUG ("called"); - - stream = get_stream_by_name (HAZE_MEDIA_BACKEND (self), sid); - - if (stream != NULL) - haze_media_stream_add_remote_candidates (stream, remote_candidates); - else - DEBUG ("Couldn't find stream"); -} - -static gboolean -haze_media_backend_codecs_ready (PurpleMediaBackend *self, - const gchar *sid) -{ - HazeMediaStream *stream; - gboolean ready = FALSE; - - DEBUG ("called"); - - if (sid != NULL) - { - stream = get_stream_by_name (HAZE_MEDIA_BACKEND (self), sid); - - if (stream != NULL) - g_object_get (stream, "codecs-ready", &ready, NULL); - - return ready; - } - else - { - HazeMediaBackendPrivate *priv = HAZE_MEDIA_BACKEND (self)->priv; - guint i; - - for (i = 0; i < priv->streams->len; ++i) - { - stream = g_ptr_array_index (priv->streams, i); - - if (stream != NULL) - g_object_get (stream, "codecs-ready", &ready, NULL); - - if (!ready) - return FALSE; - } - - return TRUE; - } -} - -static GList * -haze_media_backend_get_codecs (PurpleMediaBackend *self, - const gchar *sid) -{ - HazeMediaStream *stream; - GList *ret = NULL; - - DEBUG ("called"); - - stream = get_stream_by_name (HAZE_MEDIA_BACKEND (self), sid); - - if (stream != NULL) - ret = haze_media_stream_get_codecs (stream); - - return ret; -} - -static GList * -haze_media_backend_get_local_candidates (PurpleMediaBackend *self, - const gchar *sid, - const gchar *who) -{ - HazeMediaStream *stream; - GList *ret = NULL; - - DEBUG ("called"); - - stream = get_stream_by_name (HAZE_MEDIA_BACKEND (self), sid); - - if (stream != NULL) - ret = haze_media_stream_get_local_candidates (stream); - - return ret; -} - -static gboolean -haze_media_backend_set_remote_codecs (PurpleMediaBackend *self, - const gchar *sid, - const gchar *who, - GList *codecs) -{ - HazeMediaStream *stream; - - DEBUG ("called"); - - stream = get_stream_by_name (HAZE_MEDIA_BACKEND (self), sid); - - if (stream != NULL) - haze_media_stream_set_remote_codecs (stream, codecs); - else - DEBUG ("Couldn't find stream"); - - return TRUE; -} - -static gboolean -haze_media_backend_set_send_codec (PurpleMediaBackend *self, - const gchar *sid, - PurpleMediaCodec *codec) -{ - return FALSE; -} - -static void -haze_media_backend_ready (TpSvcMediaSessionHandler *iface, - DBusGMethodInvocation *context) -{ - HazeMediaBackend *self = HAZE_MEDIA_BACKEND (iface); - HazeMediaBackendPrivate *priv = self->priv; - - if (!priv->ready) - { - guint i; - - DEBUG ("emitting NewStreamHandler for each stream"); - - priv->ready = TRUE; - - for (i = 0; i < priv->streams->len; i++) - _emit_new_stream (self, g_ptr_array_index (priv->streams, i)); - } - - tp_svc_media_session_handler_return_from_ready (context); -} - -static void -haze_media_backend_error (TpSvcMediaSessionHandler *iface, - guint errno, - const gchar *message, - DBusGMethodInvocation *context) -{ - HazeMediaBackend *self = HAZE_MEDIA_BACKEND (iface); - HazeMediaBackendPrivate *priv; - GPtrArray *tmp; - guint i; - - g_assert (HAZE_IS_MEDIA_BACKEND (self)); - - priv = self->priv; - - if (priv->media == NULL) - { - /* This could also be because someone called Error() before the - * SessionHandler was announced. But the fact that the SessionHandler is - * actually also the Channel, and thus this method is available before - * NewSessionHandler is emitted, is an implementation detail. So the - * error message describes the only legitimate situation in which this - * could arise. - */ - GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE, "call has already ended" }; - - DEBUG ("no session, returning an error."); - dbus_g_method_return_error (context, &e); - return; - } - - DEBUG ("Media.SessionHandler::Error called, error %u (%s) -- " - "emitting error on each stream", errno, message); - - purple_media_end (priv->media, NULL, NULL); - - /* Calling haze_media_stream_error () on all the streams will ultimately - * cause them all to emit 'closed'. In response to 'closed', stream_close_cb - * unrefs them, and removes them from priv->streams. So, we copy the stream - * list to avoid it being modified from underneath us. - */ - tmp = g_ptr_array_sized_new (priv->streams->len); - - for (i = 0; i < priv->streams->len; i++) - g_ptr_array_add (tmp, g_ptr_array_index (priv->streams, i)); - - for (i = 0; i < tmp->len; i++) - { - HazeMediaStream *stream = g_ptr_array_index (tmp, i); - - haze_media_stream_error (stream, errno, message, NULL); - } - - g_ptr_array_free (tmp, TRUE); - - tp_svc_media_session_handler_return_from_error (context); -} - -static void -media_backend_iface_init (PurpleMediaBackendIface *iface) -{ -#define IMPLEMENT(x) iface->x = haze_media_backend_##x - IMPLEMENT(add_stream); - IMPLEMENT(add_remote_candidates); - IMPLEMENT(codecs_ready); - IMPLEMENT(get_codecs); - IMPLEMENT(get_local_candidates); - IMPLEMENT(set_remote_codecs); - IMPLEMENT(set_send_codec); -#undef IMPLEMENT -} - -static void -session_handler_iface_init (gpointer g_iface, gpointer iface_data) -{ - TpSvcMediaSessionHandlerClass *klass = - (TpSvcMediaSessionHandlerClass *) g_iface; - -#define IMPLEMENT(x) tp_svc_media_session_handler_implement_##x (\ - klass, haze_media_backend_##x) - IMPLEMENT(error); - IMPLEMENT(ready); -#undef IMPLEMENT -} diff --git a/src/media-backend.h b/src/media-backend.h deleted file mode 100644 index d673c55..0000000 --- a/src/media-backend.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * media-backend.h - Header for HazeMediaBackend - * Copyright (C) 2006, 2009 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 __HAZE_MEDIA_BACKEND__ -#define __HAZE_MEDIA_BACKEND__ - -#include <glib-object.h> - -#include "media-stream.h" - -G_BEGIN_DECLS - -typedef struct _HazeMediaBackend HazeMediaBackend; -typedef struct _HazeMediaBackendClass HazeMediaBackendClass; -typedef struct _HazeMediaBackendPrivate HazeMediaBackendPrivate; - -struct _HazeMediaBackendClass { - GObjectClass parent_class; -}; - -struct _HazeMediaBackend { - GObject parent; - - HazeMediaBackendPrivate *priv; -}; - -GType haze_media_backend_get_type (void); -HazeMediaStream *haze_media_backend_get_stream_by_name ( - HazeMediaBackend *self, - const gchar *sid); - -/* TYPE MACROS */ -#define HAZE_TYPE_MEDIA_BACKEND \ - (haze_media_backend_get_type ()) -#define HAZE_MEDIA_BACKEND(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), HAZE_TYPE_MEDIA_BACKEND, \ - HazeMediaBackend)) -#define HAZE_MEDIA_BACKEND_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), HAZE_TYPE_MEDIA_BACKEND, \ - HazeMediaBackendClass)) -#define HAZE_IS_MEDIA_BACKEND(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj), HAZE_TYPE_MEDIA_BACKEND)) -#define HAZE_IS_MEDIA_BACKEND_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass), HAZE_TYPE_MEDIA_BACKEND)) -#define HAZE_MEDIA_BACKEND_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), HAZE_TYPE_MEDIA_BACKEND, \ - HazeMediaBackendClass)) - -G_END_DECLS - -#endif /* #ifndef __HAZE_MEDIA_BACKEND__ */ diff --git a/src/media-channel.c b/src/media-channel.c deleted file mode 100644 index c81974e..0000000 --- a/src/media-channel.c +++ /dev/null @@ -1,1784 +0,0 @@ -/* - * media-channel.c - Source for HazeMediaChannel - * Copyright (C) 2006, 2009 Collabora Ltd. - * Copyright (C) 2006 Nokia Corporation - * - * Copied heavily from telepathy-gabble - * - * 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 "media-channel.h" - -#include <libpurple/media/backend-iface.h> -#include <libpurple/mediamanager.h> -#include <telepathy-glib/dbus.h> -#include <telepathy-glib/interfaces.h> -#include <telepathy-glib/channel-iface.h> -#include <telepathy-glib/gtypes.h> -#include <telepathy-glib/svc-channel.h> -#include <telepathy-glib/svc-properties-interface.h> -#include <telepathy-glib/svc-media-interfaces.h> - -#include "connection.h" -#include "debug.h" -#include "media-backend.h" -#include "media-stream.h" - -static void channel_iface_init (gpointer, gpointer); -static void media_signalling_iface_init (gpointer, gpointer); -static void streamed_media_iface_init (gpointer, gpointer); -static gboolean haze_media_channel_add_member (GObject *obj, - TpHandle handle, - const gchar *message, - GError **error); -static gboolean haze_media_channel_remove_member (GObject *obj, - TpHandle handle, const gchar *message, guint reason, GError **error); - -G_DEFINE_TYPE_WITH_CODE (HazeMediaChannel, haze_media_channel, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, - tp_group_mixin_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA, - streamed_media_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MEDIA_SIGNALLING, - media_signalling_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)); - -static const gchar *haze_media_channel_interfaces[] = { - TP_IFACE_CHANNEL_INTERFACE_GROUP, - TP_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING, - NULL -}; - -/* properties */ -enum -{ - PROP_OBJECT_PATH = 1, - PROP_CHANNEL_TYPE, - PROP_HANDLE_TYPE, - PROP_HANDLE, - PROP_TARGET_ID, - PROP_INITIAL_PEER, - PROP_PEER, - PROP_REQUESTED, - PROP_CONNECTION, - PROP_CREATOR, - PROP_CREATOR_ID, - PROP_INTERFACES, - PROP_CHANNEL_DESTROYED, - PROP_CHANNEL_PROPERTIES, - PROP_INITIAL_AUDIO, - PROP_INITIAL_VIDEO, - PROP_MEDIA, - LAST_PROPERTY -}; - -struct _HazeMediaChannelPrivate -{ - HazeConnection *conn; - gchar *object_path; - TpHandle creator; - TpHandle initial_peer; - - PurpleMedia *media; - - guint next_stream_id; - - /* list of PendingStreamRequest* in no particular order */ - GList *pending_stream_requests; - - TpLocalHoldState hold_state; - TpLocalHoldStateReason hold_state_reason; - - TpChannelCallStateFlags call_state; - - gboolean initial_audio; - gboolean initial_video; - - gboolean ready; - gboolean media_ended; - gboolean closed; - gboolean dispose_has_run; -}; - -static void -haze_media_channel_init (HazeMediaChannel *self) -{ - HazeMediaChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - HAZE_TYPE_MEDIA_CHANNEL, HazeMediaChannelPrivate); - - self->priv = priv; - - priv->next_stream_id = 1; -} - -/** - * make_stream_list: - * - * Creates an array of MediaStreamInfo structs. - */ -static GPtrArray * -make_stream_list (HazeMediaChannel *self, - guint len, - HazeMediaStream **streams) -{ - HazeMediaChannelPrivate *priv = self->priv; - GPtrArray *ret; - guint i; - GType info_type = TP_STRUCT_TYPE_MEDIA_STREAM_INFO; - - ret = g_ptr_array_sized_new (len); - - for (i = 0; i < len; i++) - { - GValue entry = { 0, }; - guint id; - TpHandle peer; - TpMediaStreamType type; - TpMediaStreamState connection_state; - CombinedStreamDirection combined_direction; - - g_object_get (streams[i], - "id", &id, - "media-type", &type, - "connection-state", &connection_state, - "combined-direction", &combined_direction, - NULL); - - peer = priv->initial_peer; - - g_value_init (&entry, info_type); - g_value_take_boxed (&entry, - dbus_g_type_specialized_construct (info_type)); - - dbus_g_type_struct_set (&entry, - 0, id, - 1, peer, - 2, type, - 3, connection_state, - 4, COMBINED_DIRECTION_GET_DIRECTION (combined_direction), - 5, COMBINED_DIRECTION_GET_PENDING_SEND (combined_direction), - G_MAXUINT); - - g_ptr_array_add (ret, g_value_get_boxed (&entry)); - } - - return ret; -} - -typedef struct { - /* number of streams requested == number of content objects */ - guint len; - /* array of @len borrowed pointers */ - guint *types; - /* accumulates borrowed pointers to streams. Initially @len NULL pointers; - * when the stream for contents[i] is created, it is stored at streams[i]. - */ - HazeMediaStream **streams; - /* number of non-NULL elements in streams (0 <= satisfied <= contents) */ - guint satisfied; - /* succeeded_cb(context, GPtrArray<TP_STRUCT_TYPE_MEDIA_STREAM_INFO>) - * will be called if the stream request succeeds. - */ - GFunc succeeded_cb; - /* failed_cb(context, GError *) will be called if the stream request fails. - */ - GFunc failed_cb; - gpointer context; -} PendingStreamRequest; - -static PendingStreamRequest * -pending_stream_request_new (const GArray *types, - GFunc succeeded_cb, - GFunc failed_cb, - gpointer context) -{ - PendingStreamRequest *p = g_slice_new0 (PendingStreamRequest); - - g_assert (succeeded_cb); - g_assert (failed_cb); - - p->len = types->len; - p->types = g_memdup (types->data, types->len * sizeof (gpointer)); - p->streams = g_new0 (HazeMediaStream *, types->len); - p->satisfied = 0; - p->succeeded_cb = succeeded_cb; - p->failed_cb = failed_cb; - p->context = context; - - return p; -} - -static gboolean -pending_stream_request_maybe_satisfy (PendingStreamRequest *p, - HazeMediaChannel *channel, - guint type, - HazeMediaStream *stream) -{ - guint i; - - for (i = 0; i < p->len; i++) - { - if (p->types[i] == type) - { - g_assert (p->streams[i] == NULL); - p->streams[i] = stream; - - if (++p->satisfied == p->len && p->context != NULL) - { - GPtrArray *ret = make_stream_list (channel, p->len, p->streams); - - p->succeeded_cb (p->context, ret); - g_ptr_array_foreach (ret, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (ret, TRUE); - p->context = NULL; - return TRUE; - } - } - } - - return FALSE; -} - -static void -pending_stream_request_free (gpointer data) -{ - PendingStreamRequest *p = data; - - if (p->context != NULL) - { - GError e = { TP_ERROR, TP_ERROR_CANCELLED, - "The session terminated before the requested streams could be added" - }; - - p->failed_cb (p->context, &e); - } - - g_free (p->types); - g_free (p->streams); - - g_slice_free (PendingStreamRequest, p); -} - -static void -stream_direction_changed_cb (HazeMediaStream *stream, - GParamSpec *pspec, - HazeMediaChannel *chan) -{ - guint id; - CombinedStreamDirection combined; - TpMediaStreamDirection direction; - TpMediaStreamPendingSend pending_send; - - g_object_get (stream, - "id", &id, - "combined-direction", &combined, - NULL); - - direction = COMBINED_DIRECTION_GET_DIRECTION (combined); - pending_send = COMBINED_DIRECTION_GET_PENDING_SEND (combined); - - DEBUG ("direction: %u, pending_send: %u", direction, pending_send); - - tp_svc_channel_type_streamed_media_emit_stream_direction_changed ( - chan, id, direction, pending_send); -} - -static void -media_error_cb (PurpleMedia *media, - const gchar *error, - HazeMediaChannel *chan) -{ - g_assert (HAZE_MEDIA_CHANNEL(chan)->priv != NULL); - DEBUG ("Media error on %s: %s", chan->priv->object_path, error); -} - -static void -media_state_changed_cb (PurpleMedia *media, - PurpleMediaState state, - gchar *sid, gchar *name, - HazeMediaChannel *chan) -{ - HazeMediaChannelPrivate *priv = chan->priv; - - DEBUG ("%s %s %s", - state == PURPLE_MEDIA_STATE_NEW ? "NEW" : - state == PURPLE_MEDIA_STATE_CONNECTED ? "CONNECTED" : - state == PURPLE_MEDIA_STATE_END ? "END" : - "UNKNOWN", sid, name); - - if (state == PURPLE_MEDIA_STATE_NEW) - { - if (sid != NULL && name != NULL) - { - HazeMediaBackend *backend; - HazeMediaStream *stream; - TpMediaStreamType type; - guint id; - - g_object_get (priv->media, "backend", &backend, NULL); - stream = haze_media_backend_get_stream_by_name (backend, sid); - g_object_unref (backend); - - g_object_get (G_OBJECT (stream), "id", &id, NULL); - type = haze_media_stream_get_media_type (stream); - - /* if any RequestStreams call was waiting for a stream to be created for - * that content, return from it successfully */ - { - GList *iter = priv->pending_stream_requests; - - while (iter != NULL) - { - if (pending_stream_request_maybe_satisfy (iter->data, - chan, type, stream)) - { - GList *dead = iter; - - pending_stream_request_free (dead->data); - - iter = dead->next; - priv->pending_stream_requests = g_list_delete_link ( - priv->pending_stream_requests, dead); - } - else - { - iter = iter->next; - } - } - } - - g_signal_connect (stream, "notify::combined-direction", - (GCallback) stream_direction_changed_cb, chan); - - tp_svc_channel_type_streamed_media_emit_stream_added ( - chan, id, priv->initial_peer, type); - - stream_direction_changed_cb (stream, NULL, chan); - } - } - - if (sid != NULL && name == NULL) - { - TpMediaStreamState tp_state; - HazeMediaBackend *backend; - HazeMediaStream *stream; - - if (state == PURPLE_MEDIA_STATE_NEW) - tp_state = TP_MEDIA_STREAM_STATE_CONNECTING; - else if (state == PURPLE_MEDIA_STATE_CONNECTED) - tp_state = TP_MEDIA_STREAM_STATE_CONNECTED; - else if (state == PURPLE_MEDIA_STATE_END) - tp_state = TP_MEDIA_STREAM_STATE_DISCONNECTED; - else - { - DEBUG ("Invalid state %d", state); - return; - } - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - stream = haze_media_backend_get_stream_by_name (backend, sid); - g_object_unref (backend); - - if (stream != NULL) - { - guint id; - g_object_get (stream, "id", &id, NULL); - tp_svc_channel_type_streamed_media_emit_stream_state_changed (chan, - id, tp_state); - } - } - - if (state == PURPLE_MEDIA_STATE_END) - { - if (sid != NULL && name == NULL) - { - HazeMediaBackend *backend; - HazeMediaStream *stream; - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - stream = haze_media_backend_get_stream_by_name (backend, sid); - g_object_unref (backend); - - if (stream != NULL) - { - guint id; - g_object_get (stream, "id", &id, NULL); - tp_svc_channel_type_streamed_media_emit_stream_removed ( - chan, id); - } - } - else if (sid == NULL && name == NULL) - { - TpGroupMixin *mixin = TP_GROUP_MIXIN (chan); - guint terminator; - TpHandle peer; - TpIntset *set; - - priv->media_ended = TRUE; - - peer = priv->initial_peer; - - /* - * Primarily, sessions will be ended with hangup or reject. Any that - * aren't are because of local errors so set the terminator to self. - */ - terminator = mixin->self_handle; - - set = tp_intset_new (); - - /* remove us and the peer from the member list */ - tp_intset_add (set, mixin->self_handle); - tp_intset_add (set, peer); - - tp_group_mixin_change_members ((GObject *) chan, - "Media session ended", NULL, set, NULL, NULL, - terminator, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); - - tp_intset_destroy (set); - - /* any contents that we were waiting for have now lost */ - g_list_foreach (priv->pending_stream_requests, - (GFunc) pending_stream_request_free, NULL); - g_list_free (priv->pending_stream_requests); - priv->pending_stream_requests = NULL; - - if (!priv->closed) - { - DEBUG ("calling media channel close from state changed cb"); - haze_media_channel_close (chan); - } - } - } -} - -static void -media_stream_info_cb(PurpleMedia *media, - PurpleMediaInfoType type, - gchar *sid, - gchar *name, - gboolean local, - HazeMediaChannel *chan) -{ - HazeMediaChannelPrivate *priv = chan->priv; - TpBaseConnection *conn = (TpBaseConnection *)priv->conn; - - if (type == PURPLE_MEDIA_INFO_ACCEPT) - { - TpIntset *set; - TpHandle actor; - - if (local == FALSE) - actor = priv->initial_peer; - else - actor = conn->self_handle; - - set = tp_intset_new_containing (actor); - - /* add the peer to the member list */ - tp_group_mixin_change_members (G_OBJECT (chan), "", set, NULL, NULL, - NULL, actor, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); - - if (sid != NULL && name == NULL && purple_media_is_initiator ( - media, sid, name) == FALSE) - { - HazeMediaBackend *backend; - HazeMediaStream *stream; - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - stream = haze_media_backend_get_stream_by_name (backend, sid); - g_object_unref (backend); - - g_object_set (stream, "combined-direction", - MAKE_COMBINED_DIRECTION ( - TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, 0), NULL); - } - } - else if (type == PURPLE_MEDIA_INFO_REJECT || - type == PURPLE_MEDIA_INFO_HANGUP) - { - TpGroupMixin *mixin = TP_GROUP_MIXIN (chan); - guint terminator; - TpIntset *set; - - if (sid != NULL) - return; - - if (local == TRUE) - terminator = conn->self_handle; - else - /* This will need to get the handle from name for multi-user calls */ - terminator = priv->initial_peer; - - set = tp_intset_new (); - - if (name != NULL) - /* Remove participant */ - tp_intset_add (set, priv->initial_peer); - else - /* Remove us */ - tp_intset_add (set, mixin->self_handle); - - tp_group_mixin_change_members ((GObject *) chan, - NULL, NULL, set, NULL, NULL, terminator, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE); - - tp_intset_destroy (set); - } -} - -static void -_latch_to_session (HazeMediaChannel *chan) -{ - HazeMediaChannelPrivate *priv = chan->priv; - HazeMediaBackend *backend; - gchar *object_path; - - g_assert (priv->media != NULL); - - DEBUG ("%p: Latching onto session %p", chan, priv->media); - - g_signal_connect(G_OBJECT(priv->media), "error", - G_CALLBACK(media_error_cb), chan); - g_signal_connect(G_OBJECT(priv->media), "state-changed", - G_CALLBACK(media_state_changed_cb), chan); - g_signal_connect(G_OBJECT(priv->media), "stream-info", - G_CALLBACK(media_stream_info_cb), chan); - - object_path = g_strdup_printf ("%s/MediaSession0", priv->object_path); - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - g_object_set (G_OBJECT (backend), "object-path", object_path, NULL); - g_object_unref (backend); - - tp_svc_channel_interface_media_signalling_emit_new_session_handler ( - G_OBJECT (chan), object_path, "rtp"); - - g_free (object_path); -} - -static GObject * -haze_media_channel_constructor (GType type, guint n_props, - GObjectConstructParam *props) -{ - GObject *obj; - HazeMediaChannelPrivate *priv; - TpBaseConnection *conn; - TpDBusDaemon *bus; - TpIntset *set; - TpHandleRepoIface *contact_handles; - - obj = G_OBJECT_CLASS (haze_media_channel_parent_class)-> - constructor (type, n_props, props); - - priv = HAZE_MEDIA_CHANNEL (obj)->priv; - conn = (TpBaseConnection *) priv->conn; - contact_handles = tp_base_connection_get_handles (conn, - TP_HANDLE_TYPE_CONTACT); - - /* register object on the bus */ - bus = tp_base_connection_get_dbus_daemon (conn); - tp_dbus_daemon_register_object (bus, priv->object_path, obj); - - tp_group_mixin_init (obj, G_STRUCT_OFFSET (HazeMediaChannel, group), - contact_handles, conn->self_handle); - - if (priv->media != NULL) - priv->creator = priv->initial_peer; - else - priv->creator = conn->self_handle; - - /* automatically add creator to channel, but also ref them again (because - * priv->creator is the InitiatorHandle) */ - g_assert (priv->creator != 0); - tp_handle_ref (contact_handles, priv->creator); - - set = tp_intset_new_containing (priv->creator); - tp_group_mixin_change_members (obj, "", set, NULL, NULL, NULL, 0, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE); - tp_intset_destroy (set); - - /* We implement the 0.17.6 properties correctly, and can include a message - * when ending a call. - */ - tp_group_mixin_change_flags (obj, - TP_CHANNEL_GROUP_FLAG_PROPERTIES | - TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE | - TP_CHANNEL_GROUP_FLAG_MESSAGE_REJECT | - TP_CHANNEL_GROUP_FLAG_MESSAGE_RESCIND, - 0); - - - if (priv->media != NULL) - { - /* This is an incoming call; make us local pending and don't set any - * group flags (all we can do is add or remove ourselves, which is always - * valid per the spec) - */ - set = tp_intset_new_containing (conn->self_handle); - tp_group_mixin_change_members (obj, "", NULL, NULL, set, NULL, - priv->initial_peer, TP_CHANNEL_GROUP_CHANGE_REASON_INVITED); - tp_intset_destroy (set); - - /* Set up signal callbacks */ - _latch_to_session (HAZE_MEDIA_CHANNEL (obj)); - } - - return obj; -} - -static void -haze_media_channel_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - HazeMediaChannel *chan = HAZE_MEDIA_CHANNEL (object); - HazeMediaChannelPrivate *priv = chan->priv; - 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_STREAMED_MEDIA); - break; - case PROP_HANDLE_TYPE: - /* This is used to implement TargetHandleType, which is immutable. If - * the peer was known at channel-creation time, this will be Contact; - * otherwise, it must be None even if we subsequently learn who the peer - * is. - */ - if (priv->initial_peer != 0) - g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); - else - g_value_set_uint (value, TP_HANDLE_TYPE_NONE); - break; - case PROP_INITIAL_PEER: - case PROP_HANDLE: - /* As above: TargetHandle is immutable, so non-0 only if the peer handle - * was known at creation time. - */ - g_value_set_uint (value, priv->initial_peer); - break; - case PROP_TARGET_ID: - /* As above. */ - if (priv->initial_peer != 0) - { - TpHandleRepoIface *repo = tp_base_connection_get_handles ( - base_conn, TP_HANDLE_TYPE_CONTACT); - const gchar *target_id = tp_handle_inspect (repo, priv->initial_peer); - - g_value_set_string (value, target_id); - } - else - { - g_value_set_static_string (value, ""); - } - - break; - case PROP_PEER: - { - TpHandle peer = 0; - - if (priv->initial_peer != 0) - peer = priv->initial_peer; - - g_value_set_uint (value, peer); - break; - } - case PROP_CONNECTION: - g_value_set_object (value, priv->conn); - break; - case PROP_CREATOR: - g_value_set_uint (value, priv->creator); - break; - case PROP_CREATOR_ID: - { - TpHandleRepoIface *repo = tp_base_connection_get_handles ( - base_conn, TP_HANDLE_TYPE_CONTACT); - - g_value_set_string (value, tp_handle_inspect (repo, priv->creator)); - } - break; - case PROP_REQUESTED: - g_value_set_boolean (value, (priv->creator == base_conn->self_handle)); - break; - case PROP_INTERFACES: - g_value_set_boxed (value, haze_media_channel_interfaces); - 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_STREAMED_MEDIA, "InitialAudio", - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "InitialVideo", - NULL)); - break; - case PROP_MEDIA: - g_value_set_object (value, priv->media); - break; - case PROP_INITIAL_AUDIO: - g_value_set_boolean (value, priv->initial_audio); - break; - case PROP_INITIAL_VIDEO: - g_value_set_boolean (value, priv->initial_video); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -haze_media_channel_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - HazeMediaChannel *chan = HAZE_MEDIA_CHANNEL (object); - HazeMediaChannelPrivate *priv = chan->priv; - - switch (property_id) { - case PROP_OBJECT_PATH: - g_free (priv->object_path); - priv->object_path = g_value_dup_string (value); - break; - case PROP_HANDLE_TYPE: - case PROP_HANDLE: - case PROP_CHANNEL_TYPE: - /* these properties are writable in the interface, but not actually - * meaningfully changable on this channel, so we do nothing */ - break; - case PROP_CONNECTION: - priv->conn = g_value_get_object (value); - break; - case PROP_CREATOR: - priv->creator = g_value_get_uint (value); - break; - case PROP_INITIAL_PEER: - priv->initial_peer = g_value_get_uint (value); - - if (priv->initial_peer != 0) - { - TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn; - TpHandleRepoIface *repo = tp_base_connection_get_handles (base_conn, - TP_HANDLE_TYPE_CONTACT); - tp_handle_ref (repo, priv->initial_peer); - } - - break; - case PROP_MEDIA: - g_assert (priv->media == NULL); - priv->media = g_value_dup_object (value); - break; - case PROP_INITIAL_AUDIO: - priv->initial_audio = g_value_get_boolean (value); - break; - case PROP_INITIAL_VIDEO: - priv->initial_video = g_value_get_boolean (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void haze_media_channel_dispose (GObject *object); -static void haze_media_channel_finalize (GObject *object); - -static void -haze_media_channel_class_init (HazeMediaChannelClass *haze_media_channel_class) -{ - 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", "creator", NULL }, - { "InitiatorID", "creator-id", NULL }, - { NULL } - }; - static TpDBusPropertiesMixinPropImpl streamed_media_props[] = { - { "InitialAudio", "initial-audio", NULL }, - { "InitialVideo", "initial-video", NULL }, - { NULL } - }; - static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { - { TP_IFACE_CHANNEL, - tp_dbus_properties_mixin_getter_gobject_properties, - NULL, - channel_props, - }, - { TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, - tp_dbus_properties_mixin_getter_gobject_properties, - NULL, - streamed_media_props, - }, - { NULL } - }; - GObjectClass *object_class = G_OBJECT_CLASS (haze_media_channel_class); - GParamSpec *param_spec; - - g_type_class_add_private (haze_media_channel_class, - sizeof (HazeMediaChannelPrivate)); - - object_class->constructor = haze_media_channel_constructor; - - object_class->get_property = haze_media_channel_get_property; - object_class->set_property = haze_media_channel_set_property; - - object_class->dispose = haze_media_channel_dispose; - object_class->finalize = haze_media_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_string ("target-id", "Target ID", - "The string that would result from inspecting TargetHandle", - 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 ("initial-peer", "Other participant", - "The TpHandle representing the other participant in the channel if known " - "at construct-time; 0 if the other participant was unknown at the time " - "of channel creation", - 0, G_MAXUINT32, 0, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_INITIAL_PEER, param_spec); - - param_spec = g_param_spec_uint ("peer", "Other participant", - "The TpHandle representing the other participant in the channel if " - "currently known; 0 if this is an anonymous channel on which " - "RequestStreams has not yet been called.", - 0, G_MAXUINT32, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_PEER, param_spec); - - param_spec = g_param_spec_object ("connection", "HazeConnection object", - "Haze connection object that owns this media channel object.", - HAZE_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_uint ("creator", "Channel creator", - "The TpHandle representing the contact who created 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_CREATOR, param_spec); - - param_spec = g_param_spec_string ("creator-id", "Creator ID", - "The ID obtained by inspecting the creator handle.", - NULL, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_CREATOR_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 ("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_object ("media", "PurpleMedia object", - "Purple media associated with this media channel object.", - PURPLE_TYPE_MEDIA, - 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_MEDIA, param_spec); - - param_spec = g_param_spec_boolean ("initial-audio", "InitialAudio", - "Whether the channel initially contained an audio stream", - FALSE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_INITIAL_AUDIO, - param_spec); - - param_spec = g_param_spec_boolean ("initial-video", "InitialVideo", - "Whether the channel initially contained an video stream", - FALSE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_INITIAL_VIDEO, - param_spec); - - haze_media_channel_class->dbus_props_class.interfaces = prop_interfaces; - tp_dbus_properties_mixin_class_init (object_class, - G_STRUCT_OFFSET (HazeMediaChannelClass, dbus_props_class)); - - tp_group_mixin_class_init (object_class, - G_STRUCT_OFFSET (HazeMediaChannelClass, group_class), - haze_media_channel_add_member, NULL); - tp_group_mixin_class_set_remove_with_reason_func (object_class, - haze_media_channel_remove_member); - tp_group_mixin_class_allow_self_removal (object_class); - - tp_group_mixin_init_dbus_properties (object_class); -} - -void -haze_media_channel_dispose (GObject *object) -{ - HazeMediaChannel *self = HAZE_MEDIA_CHANNEL (object); - HazeMediaChannelPrivate *priv = self->priv; - TpBaseConnection *conn = (TpBaseConnection *) priv->conn; - TpHandleRepoIface *contact_handles = tp_base_connection_get_handles ( - conn, TP_HANDLE_TYPE_CONTACT); - - if (priv->dispose_has_run) - return; - - DEBUG ("called"); - - priv->dispose_has_run = TRUE; - - if (!priv->closed) - haze_media_channel_close (self); - - g_assert (priv->closed); - - tp_handle_unref (contact_handles, priv->creator); - priv->creator = 0; - - if (priv->initial_peer != 0) - { - tp_handle_unref (contact_handles, priv->initial_peer); - priv->initial_peer = 0; - } - - if (priv->media != NULL) - g_object_unref (priv->media); - priv->media = NULL; - - if (G_OBJECT_CLASS (haze_media_channel_parent_class)->dispose) - G_OBJECT_CLASS (haze_media_channel_parent_class)->dispose (object); -} - -void -haze_media_channel_finalize (GObject *object) -{ - HazeMediaChannel *self = HAZE_MEDIA_CHANNEL (object); - HazeMediaChannelPrivate *priv = self->priv; - - g_free (priv->object_path); - - tp_group_mixin_finalize (object); - - G_OBJECT_CLASS (haze_media_channel_parent_class)->finalize (object); -} - - -/** - * haze_media_channel_close_async: - * - * Implements D-Bus method Close - * on interface org.freedesktop.Telepathy.Channel - */ -static void -haze_media_channel_close_async (TpSvcChannel *iface, - DBusGMethodInvocation *context) -{ - HazeMediaChannel *self = HAZE_MEDIA_CHANNEL (iface); - - DEBUG ("called"); - haze_media_channel_close (self); - tp_svc_channel_return_from_close (context); -} - -void -haze_media_channel_close (HazeMediaChannel *self) -{ - HazeMediaChannelPrivate *priv = self->priv; - - DEBUG ("called on %p", self); - - if (!priv->closed) - { - priv->closed = TRUE; - - if (priv->media && !priv->media_ended) - { - priv->media_ended = TRUE; - purple_media_stream_info (priv->media, - PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, FALSE); - } - - tp_svc_channel_emit_closed (self); - } -} - - -/** - * haze_media_channel_get_channel_type - * - * Implements D-Bus method GetChannelType - * on interface org.freedesktop.Telepathy.Channel - */ -static void -haze_media_channel_get_channel_type (TpSvcChannel *iface, - DBusGMethodInvocation *context) -{ - tp_svc_channel_return_from_get_channel_type (context, - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); -} - - -/** - * haze_media_channel_get_handle - * - * Implements D-Bus method GetHandle - * on interface org.freedesktop.Telepathy.Channel - */ -static void -haze_media_channel_get_handle (TpSvcChannel *iface, - DBusGMethodInvocation *context) -{ - HazeMediaChannel *self = HAZE_MEDIA_CHANNEL (iface); - - if (self->priv->initial_peer == 0) - tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_NONE, 0); - else - tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT, - self->priv->initial_peer); -} - - -/** - * haze_media_channel_get_interfaces - * - * Implements D-Bus method GetInterfaces - * on interface org.freedesktop.Telepathy.Channel - */ -static void -haze_media_channel_get_interfaces (TpSvcChannel *iface, - DBusGMethodInvocation *context) -{ - tp_svc_channel_return_from_get_interfaces (context, - haze_media_channel_interfaces); -} - -/** - * haze_media_channel_list_streams - * - * Implements D-Bus method ListStreams - * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia - */ -static void -haze_media_channel_list_streams (TpSvcChannelTypeStreamedMedia *iface, - DBusGMethodInvocation *context) -{ - HazeMediaChannel *self = HAZE_MEDIA_CHANNEL (iface); - HazeMediaChannelPrivate *priv; - GPtrArray *ret; - - g_assert (HAZE_IS_MEDIA_CHANNEL (self)); - - priv = self->priv; - - /* If the session has not yet started, return an empty array. */ - if (priv->media == NULL) - { - ret = g_ptr_array_new (); - } - else - { - HazeMediaBackend *backend; - GPtrArray *streams; - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - g_object_get (G_OBJECT (backend), "streams", &streams, NULL); - - ret = make_stream_list (self, streams->len, - (HazeMediaStream **) streams->pdata); - - g_ptr_array_unref (streams); - g_object_unref (backend); - } - - tp_svc_channel_type_streamed_media_return_from_list_streams (context, ret); - g_ptr_array_foreach (ret, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (ret, TRUE); -} - -/** - * haze_media_channel_remove_streams - * - * Implements DBus method RemoveStreams - * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia - */ -static void -haze_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *iface, - const GArray * streams, - DBusGMethodInvocation *context) -{ - HazeMediaChannel *obj = HAZE_MEDIA_CHANNEL (iface); - HazeMediaChannelPrivate *priv; - HazeMediaBackend *backend; - GPtrArray *backend_streams; - guint i, j; - GPtrArray *media_ids; - const gchar *target_id; - - g_assert (HAZE_IS_MEDIA_CHANNEL (obj)); - - priv = obj->priv; - - g_object_get (obj, "target-id", &target_id, NULL); - - if ((purple_prpl_get_media_caps (priv->conn->account, target_id) & - PURPLE_MEDIA_CAPS_MODIFY_SESSION) == 0) - { - TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn); - gchar *name; - GError *e; - - g_object_get (base_conn, "protocol", &name, NULL); - g_set_error (&e, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, - "Streams can't be removed in Haze's \"%s\" protocol's calls", name); - g_free (name); - - DEBUG ("%s", e->message); - dbus_g_method_return_error (context, e); - - g_error_free(e); - return; - } - - media_ids = g_ptr_array_new (); - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - g_object_get (G_OBJECT (backend), "streams", &backend_streams, NULL); - g_object_unref (backend); - - for (i = 0; i < streams->len; ++i) - { - guint id = g_array_index (streams, guint, i); - - for (j = 0; j < backend_streams->len; j++) - { - HazeMediaStream *stream = g_ptr_array_index (backend_streams, j); - guint stream_id; - - g_object_get (G_OBJECT (stream), "id", &stream_id, NULL); - - if (id == stream_id) - { - g_ptr_array_add (media_ids, stream->name); - break; - } - } - - if (j >= backend_streams->len) - { - GError e = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT, - "Requested stream wasn't found" }; - DEBUG ("%s", e.message); - dbus_g_method_return_error (context, &e); - g_ptr_array_free (media_ids, TRUE); - return; - } - } - - for (i = 0; i < media_ids->len; ++i) - { - gchar *id = g_ptr_array_index (media_ids, i); - for (j = i + 1; j < media_ids->len; ++j) - { - if (id == g_ptr_array_index (media_ids, j)) - { - g_ptr_array_remove_index (media_ids, j); - --j; - } - } - } - - for (i = 0; i < media_ids->len; ++i) - { - purple_media_end (priv->media, g_ptr_array_index (media_ids, i), NULL); - } - - g_ptr_array_unref (backend_streams); - g_ptr_array_free (media_ids, TRUE); - tp_svc_channel_type_streamed_media_return_from_remove_streams (context); -} - -/** - * haze_media_channel_request_stream_direction - * - * Implements D-Bus method RequestStreamDirection - * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia - */ -static void -haze_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *iface, - guint stream_id, - guint stream_direction, - DBusGMethodInvocation *context) -{ - /* Libpurple doesn't have API for this yet */ - GError e = { TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, - "Stream direction can't be set Haze calls" }; - DEBUG ("%s", e.message); - dbus_g_method_return_error (context, &e); -} - - -static gboolean -init_media_cb (PurpleMediaManager *manager, - PurpleMedia *media, - PurpleAccount *account, - const gchar *username, - HazeMediaChannel *self) -{ - HazeMediaChannelPrivate *priv = self->priv; - TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn); - TpHandleRepoIface *contact_repo = - tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); - TpHandle contact = tp_handle_ensure (contact_repo, username, NULL, NULL); - - if (priv->conn->account != account || priv->initial_peer != contact) - return TRUE; - - g_assert (priv->media == NULL); - priv->media = g_object_ref (media); - if (priv->media != NULL) - { - _latch_to_session (self); - } - - g_signal_handlers_disconnect_by_func (manager, init_media_cb, self); - - return TRUE; -} - -static gboolean -_haze_media_channel_request_contents (HazeMediaChannel *chan, - TpHandle peer, - const GArray *media_types, - GError **error) -{ - HazeMediaChannelPrivate *priv = chan->priv; - gboolean want_audio, want_video; - guint idx; - TpHandleRepoIface *contact_handles; - const gchar *contact_id; - guint audio_count = 0, video_count = 0; - - DEBUG ("called"); - - want_audio = want_video = FALSE; - - for (idx = 0; idx < media_types->len; idx++) - { - guint media_type = g_array_index (media_types, guint, idx); - - if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO) - { - want_audio = TRUE; - } - else if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO) - { - want_video = TRUE; - } - else - { - g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, - "given media type %u is invalid", media_type); - return FALSE; - } - } - - /* existing call; the recipient and the mode has already been decided */ - if (priv->media != NULL) - { - PurpleMediaCaps caps; - const gchar *target_id; - g_object_get (chan, "target-id", &target_id, NULL); - caps = purple_prpl_get_media_caps (priv->conn->account, target_id); - - /* Check if the contact supports modifying the session */ - if ((caps & PURPLE_MEDIA_CAPS_MODIFY_SESSION) == 0) - { - TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn); - gchar *name; - - g_object_get (base_conn, "protocol", &name, NULL); - g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, - "Streams can't be added in Haze's \"%s\" protocol's calls", - name); - - g_free (name); - return FALSE; - } - - /* Check if contact supports the desired media type */ - if ((want_audio == FALSE || caps & PURPLE_MEDIA_CAPS_AUDIO) && - (want_video == FALSE || caps & PURPLE_MEDIA_CAPS_VIDEO)) - { - g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, - "Member does not have the desired audio/video capabilities"); - return FALSE; - } - } - - /* if we've got here, we're good to make the streams */ - - contact_handles = tp_base_connection_get_handles ( - TP_BASE_CONNECTION (priv->conn), TP_HANDLE_TYPE_CONTACT); - contact_id = tp_handle_inspect (contact_handles, peer); - - /* Be ready to retrieve the newly created media object */ - if (priv->media == NULL) - g_signal_connect (purple_media_manager_get (), "init-media", - G_CALLBACK (init_media_cb), chan); - - for (idx = 0; idx < media_types->len; idx++) - { - guint media_type = g_array_index (media_types, guint, idx); - - if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO) - ++audio_count; - else if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO) - ++video_count; - } - - while (audio_count > 0 || video_count > 0) - { - PurpleMediaSessionType type = PURPLE_MEDIA_NONE; - - if (audio_count > 0) - { - type |= PURPLE_MEDIA_AUDIO; - --audio_count; - } - - if (video_count > 0) - { - type |= PURPLE_MEDIA_VIDEO; - --video_count; - } - - if (purple_prpl_initiate_media (priv->conn->account, - contact_id, type) == FALSE) - return FALSE; - } - - return TRUE; -} - -static void -media_channel_request_streams (HazeMediaChannel *self, - TpHandle contact_handle, - const GArray *types, - GFunc succeeded_cb, - GFunc failed_cb, - gpointer context) -{ - HazeMediaChannelPrivate *priv = self->priv; - PendingStreamRequest *psr = NULL; - GError *error = NULL; - - if (types->len == 0) - { - GPtrArray *empty = g_ptr_array_sized_new (0); - - DEBUG ("no streams to request"); - succeeded_cb (context, empty); - g_ptr_array_free (empty, TRUE); - - return; - } - - if (priv->media != NULL) - { - TpHandle peer; - - peer = priv->initial_peer; - - if (peer != contact_handle) - { - g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, - "cannot add streams for %u: this channel's peer is %u", - contact_handle, peer); - goto error; - } - } - - /* - * Pending stream requests can be completed before request_contents returns. - * Add the pending stream request up here so it isn't missed. - */ - psr = pending_stream_request_new (types, succeeded_cb, failed_cb, - context); - priv->pending_stream_requests = g_list_prepend (priv->pending_stream_requests, - psr); - - if (!_haze_media_channel_request_contents (self, contact_handle, - types, &error)) - goto error; - - return; - -error: - if (psr != NULL) - { - priv->pending_stream_requests = g_list_remove ( - priv->pending_stream_requests, psr); - pending_stream_request_free (psr); - } - - DEBUG ("returning error %u: %s", error->code, error->message); - failed_cb (context, error); - g_error_free (error); -} - -/** - * haze_media_channel_request_streams - * - * Implements D-Bus method RequestStreams - * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia - */ -static void -haze_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface, - guint contact_handle, - const GArray *types, - DBusGMethodInvocation *context) -{ - HazeMediaChannel *self = HAZE_MEDIA_CHANNEL (iface); - TpBaseConnection *base_conn = (TpBaseConnection *) self->priv->conn; - TpHandleRepoIface *contact_handles = tp_base_connection_get_handles ( - base_conn, TP_HANDLE_TYPE_CONTACT); - GError *error = NULL; - - if (!tp_handle_is_valid (contact_handles, contact_handle, &error)) - { - DEBUG ("that's not a handle, sonny! (%u)", contact_handle); - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - else - { - /* FIXME: disallow this if we've put the peer on hold? */ - - media_channel_request_streams (self, contact_handle, types, - (GFunc) tp_svc_channel_type_streamed_media_return_from_request_streams, - (GFunc) dbus_g_method_return_error, - context); - } -} - -/** - * haze_media_channel_request_initial_streams: - * @chan: an outgoing call, which must have just been constructed. - * @succeeded_cb: called with arguments @user_data and a GPtrArray of - * TP_STRUCT_TYPE_MEDIA_STREAM_INFO if the request succeeds. - * @failed_cb: called with arguments @user_data and a GError * if the request - * fails. - * @user_data: context for the callbacks. - * - * Request streams corresponding to the values of InitialAudio and InitialVideo - * in the channel request. - */ -void -haze_media_channel_request_initial_streams (HazeMediaChannel *chan, - GFunc succeeded_cb, - GFunc failed_cb, - gpointer user_data) -{ - HazeMediaChannelPrivate *priv = chan->priv; - GArray *types = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); - guint media_type; - - /* This has to be an outgoing call... */ - g_assert (priv->creator == priv->conn->parent.self_handle); - - if (priv->initial_audio) - { - media_type = TP_MEDIA_STREAM_TYPE_AUDIO; - g_array_append_val (types, media_type); - } - - if (priv->initial_video) - { - media_type = TP_MEDIA_STREAM_TYPE_VIDEO; - g_array_append_val (types, media_type); - } - - media_channel_request_streams (chan, priv->initial_peer, types, - succeeded_cb, failed_cb, user_data); - - g_array_free (types, TRUE); -} - -static gboolean -haze_media_channel_add_member (GObject *obj, - TpHandle handle, - const gchar *message, - GError **error) -{ - HazeMediaChannel *chan = HAZE_MEDIA_CHANNEL (obj); - HazeMediaChannelPrivate *priv = chan->priv; - TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); - TpIntset *set; - - /* did we create this channel? */ - if (priv->creator == mixin->self_handle) - { - /* yes: check we don't have a peer already, and if not add this one to - * remote pending (but don't send an invitation yet). - */ - if (priv->media != NULL) - { - TpHandle peer; - - peer = priv->initial_peer; - - if (peer != handle) - { - g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, - "handle %u cannot be added: this channel's peer is %u", - handle, peer); - return FALSE; - } - } - - /* make the peer remote pending */ - set = tp_intset_new_containing (handle); - tp_group_mixin_change_members (obj, "", NULL, NULL, NULL, set, - mixin->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_INVITED); - tp_intset_destroy (set); - - /* and remove CanAdd, since it was only here to allow this deprecated - * API. */ - tp_group_mixin_change_flags (obj, 0, TP_CHANNEL_GROUP_FLAG_CAN_ADD); - - return TRUE; - } - else - { - /* no: has a session been created, is the handle being added ours, - * and are we in local pending? (call answer) */ - if (priv->media && - handle == mixin->self_handle && - tp_handle_set_is_member (mixin->local_pending, handle)) - { - /* is the call on hold? */ - if (priv->hold_state != TP_LOCAL_HOLD_STATE_UNHELD) - { - g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, - "Can't answer a call while it's on hold"); - return FALSE; - } - - /* make us a member */ - set = tp_intset_new_containing (handle); - tp_group_mixin_change_members (obj, "", set, NULL, NULL, NULL, - handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); - tp_intset_destroy (set); - - /* signal acceptance */ - purple_media_stream_info(priv->media, - PURPLE_MEDIA_INFO_ACCEPT, NULL, NULL, TRUE); - - return TRUE; - } - } - - g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, - "handle %u cannot be added in the current state", handle); - return FALSE; -} - -static gboolean -haze_media_channel_remove_member (GObject *obj, - TpHandle handle, - const gchar *message, - guint reason, - GError **error) -{ - HazeMediaChannel *chan = HAZE_MEDIA_CHANNEL (obj); - HazeMediaChannelPrivate *priv = chan->priv; - TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); - - /* We don't set CanRemove, and did allow self removal. So tp-glib should - * ensure this. - */ - g_assert (handle == mixin->self_handle); - - /* Closing up might make HazeMediaManager release its ref. */ - g_object_ref (chan); - - if (priv->media == NULL) - { - haze_media_channel_close (chan); - } - else - { - switch (reason) - { - /* Should one of these trigger reject? */ - case TP_CHANNEL_GROUP_CHANGE_REASON_NONE: - case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE: - case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY: - case TP_CHANNEL_GROUP_CHANGE_REASON_ERROR: - case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER: - purple_media_stream_info(priv->media, - PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); - break; - default: - /* The remaining options don't make sense */ - g_object_unref (chan); - return FALSE; - } - } - - /* Remove CanAdd if it was there for the deprecated anonymous channel - * semantics, since the channel will go away RSN. */ - tp_group_mixin_change_flags (obj, 0, TP_CHANNEL_GROUP_FLAG_CAN_ADD); - - g_object_unref (chan); - - return TRUE; -} - -/** - * haze_media_channel_get_session_handlers - * - * Implements D-Bus method GetSessionHandlers - * on interface org.freedesktop.Telepathy.Channel.Interface.MediaSignalling - */ -static void -haze_media_channel_get_session_handlers ( - TpSvcChannelInterfaceMediaSignalling *iface, - DBusGMethodInvocation *context) -{ - HazeMediaChannel *self = HAZE_MEDIA_CHANNEL (iface); - HazeMediaChannelPrivate *priv; - GPtrArray *ret; - GType info_type = TP_STRUCT_TYPE_MEDIA_SESSION_HANDLER_INFO; - - g_assert (HAZE_IS_MEDIA_CHANNEL (self)); - - priv = self->priv; - - if (priv->media) - { - GValue handler = { 0, }; - HazeMediaBackend *backend; - gchar *object_path; - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - g_object_get (G_OBJECT (backend), "object-path", &object_path, NULL); - g_object_unref (backend); - - g_value_init (&handler, info_type); - g_value_take_boxed (&handler, - dbus_g_type_specialized_construct (info_type)); - - dbus_g_type_struct_set (&handler, - 0, object_path, - 1, "rtp", - G_MAXUINT); - - g_free (object_path); - - ret = g_ptr_array_sized_new (1); - g_ptr_array_add (ret, g_value_get_boxed (&handler)); - } - else - { - ret = g_ptr_array_sized_new (0); - } - - tp_svc_channel_interface_media_signalling_return_from_get_session_handlers ( - context, ret); - g_ptr_array_foreach (ret, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (ret, TRUE); -} - -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, haze_media_channel_##x##suffix) - IMPLEMENT(close,_async); - IMPLEMENT(get_channel_type,); - IMPLEMENT(get_handle,); - IMPLEMENT(get_interfaces,); -#undef IMPLEMENT -} - -static void -streamed_media_iface_init (gpointer g_iface, gpointer iface_data) -{ - TpSvcChannelTypeStreamedMediaClass *klass = - (TpSvcChannelTypeStreamedMediaClass *) g_iface; - -#define IMPLEMENT(x) tp_svc_channel_type_streamed_media_implement_##x (\ - klass, haze_media_channel_##x) - IMPLEMENT(list_streams); - IMPLEMENT(remove_streams); - IMPLEMENT(request_stream_direction); - IMPLEMENT(request_streams); -#undef IMPLEMENT -} - -static void -media_signalling_iface_init (gpointer g_iface, gpointer iface_data) -{ - TpSvcChannelInterfaceMediaSignallingClass *klass = - (TpSvcChannelInterfaceMediaSignallingClass *) g_iface; - -#define IMPLEMENT(x) tp_svc_channel_interface_media_signalling_implement_##x (\ - klass, haze_media_channel_##x) - IMPLEMENT(get_session_handlers); -#undef IMPLEMENT -} diff --git a/src/media-channel.h b/src/media-channel.h deleted file mode 100644 index 60081d1..0000000 --- a/src/media-channel.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * media-channel.h - Header for HazeMediaChannel - * Copyright (C) 2006, 2009 Collabora Ltd. - * Copyright (C) 2006 Nokia Corporation - * - * Copied heavily from telepathy-gabble - * - * 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 __HAZE_MEDIA_CHANNEL_H__ -#define __HAZE_MEDIA_CHANNEL_H__ - -#include <glib-object.h> - -#include <telepathy-glib/dbus-properties-mixin.h> -#include <telepathy-glib/group-mixin.h> - -G_BEGIN_DECLS - -typedef struct _HazeMediaChannel HazeMediaChannel; -typedef struct _HazeMediaChannelPrivate HazeMediaChannelPrivate; -typedef struct _HazeMediaChannelClass HazeMediaChannelClass; - -struct _HazeMediaChannelClass { - GObjectClass parent_class; - - TpGroupMixinClass group_class; - TpDBusPropertiesMixinClass dbus_props_class; -}; - -struct _HazeMediaChannel { - GObject parent; - - TpGroupMixin group; - - HazeMediaChannelPrivate *priv; -}; - -GType haze_media_channel_get_type (void); - -/* TYPE MACROS */ -#define HAZE_TYPE_MEDIA_CHANNEL \ - (haze_media_channel_get_type ()) -#define HAZE_MEDIA_CHANNEL(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), HAZE_TYPE_MEDIA_CHANNEL,\ - HazeMediaChannel)) -#define HAZE_MEDIA_CHANNEL_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), HAZE_TYPE_MEDIA_CHANNEL,\ - HazeMediaChannelClass)) -#define HAZE_IS_MEDIA_CHANNEL(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj), HAZE_TYPE_MEDIA_CHANNEL)) -#define HAZE_IS_MEDIA_CHANNEL_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass), HAZE_TYPE_MEDIA_CHANNEL)) -#define HAZE_MEDIA_CHANNEL_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), HAZE_TYPE_MEDIA_CHANNEL, \ - HazeMediaChannelClass)) - -void haze_media_channel_request_initial_streams (HazeMediaChannel *chan, - GFunc succeeded_cb, - GFunc failed_cb, - gpointer user_data); - -void haze_media_channel_close (HazeMediaChannel *self); - -G_END_DECLS - -#endif /* #ifndef __HAZE_MEDIA_CHANNEL_H__*/ diff --git a/src/media-manager.c b/src/media-manager.c deleted file mode 100644 index 3df771c..0000000 --- a/src/media-manager.c +++ /dev/null @@ -1,579 +0,0 @@ -/* - * media-manager.c - Source for HazeMediaManager - * Copyright (C) 2006, 2009 Collabora Ltd. - * - * Copied heavily from telepathy-gabble - * - * 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 "media-manager.h" - -#include <libpurple/mediamanager.h> -#include <telepathy-glib/channel-manager.h> -#include <telepathy-glib/dbus.h> -#include <telepathy-glib/gtypes.h> -#include <telepathy-glib/interfaces.h> - -#include "connection.h" -#include "debug.h" -#include "media-channel.h" - -static void channel_manager_iface_init (gpointer, gpointer); -static void haze_media_manager_close_all (HazeMediaManager *self); -static void haze_media_manager_constructed (GObject *object); - -G_DEFINE_TYPE_WITH_CODE (HazeMediaManager, haze_media_manager, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, - channel_manager_iface_init)); - -/* properties */ -enum -{ - PROP_CONNECTION = 1, - LAST_PROPERTY -}; - -struct _HazeMediaManagerPrivate -{ - HazeConnection *conn; - gulong status_changed_id; - - GPtrArray *channels; - guint channel_index; - - gboolean dispose_has_run; -}; - -static void -haze_media_manager_init (HazeMediaManager *self) -{ - HazeMediaManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - HAZE_TYPE_MEDIA_MANAGER, HazeMediaManagerPrivate); - - self->priv = priv; - - priv->channels = g_ptr_array_sized_new (1); - priv->channel_index = 0; - - priv->conn = NULL; - priv->dispose_has_run = FALSE; -} - -static void -haze_media_manager_dispose (GObject *object) -{ - HazeMediaManager *self = HAZE_MEDIA_MANAGER (object); - HazeMediaManagerPrivate *priv = self->priv; - - if (priv->dispose_has_run) - return; - - DEBUG ("dispose called"); - priv->dispose_has_run = TRUE; - - haze_media_manager_close_all (self); - g_assert (priv->channels->len == 0); - g_ptr_array_free (priv->channels, TRUE); - - if (G_OBJECT_CLASS (haze_media_manager_parent_class)->dispose) - G_OBJECT_CLASS (haze_media_manager_parent_class)->dispose (object); -} - -static void -haze_media_manager_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - HazeMediaManager *self = HAZE_MEDIA_MANAGER (object); - HazeMediaManagerPrivate *priv = self->priv; - - 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 -haze_media_manager_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - HazeMediaManager *self = HAZE_MEDIA_MANAGER (object); - HazeMediaManagerPrivate *priv = self->priv; - - 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 void -haze_media_manager_class_init (HazeMediaManagerClass *haze_media_manager_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (haze_media_manager_class); - GParamSpec *param_spec; - - g_type_class_add_private (haze_media_manager_class, - sizeof (HazeMediaManagerPrivate)); - - object_class->constructed = haze_media_manager_constructed; - object_class->dispose = haze_media_manager_dispose; - - object_class->get_property = haze_media_manager_get_property; - object_class->set_property = haze_media_manager_set_property; - - param_spec = g_param_spec_object ("connection", "HazeConnection object", - "Haze connection object that owns this media channel manager object.", - HAZE_TYPE_CONNECTION, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); - -} - -/** - * media_channel_closed_cb: - * - * Signal callback for when a media channel is closed. Removes the references - * that #HazeMediaManager holds to them. - */ -static void -media_channel_closed_cb (HazeMediaChannel *chan, gpointer user_data) -{ - HazeMediaManager *self = HAZE_MEDIA_MANAGER (user_data); - HazeMediaManagerPrivate *priv = self->priv; - - tp_channel_manager_emit_channel_closed_for_object (self, - TP_EXPORTABLE_CHANNEL (chan)); - - DEBUG ("removing media channel %p with ref count %d", - chan, G_OBJECT (chan)->ref_count); - - g_ptr_array_remove (priv->channels, chan); - g_object_unref (chan); -} - -/** - * new_media_channel - * - * Creates a new empty HazeMediaChannel. - */ -static HazeMediaChannel * -new_media_channel (HazeMediaManager *self, - PurpleMedia *media, - TpHandle peer, - gboolean initial_audio, - gboolean initial_video) -{ - HazeMediaManagerPrivate *priv; - TpBaseConnection *conn; - - HazeMediaChannel *chan; - gchar *object_path; - - g_assert (HAZE_IS_MEDIA_MANAGER (self)); - - priv = self->priv; - conn = (TpBaseConnection *) priv->conn; - - object_path = g_strdup_printf ("%s/MediaChannel%u", - conn->object_path, priv->channel_index); - priv->channel_index += 1; - - chan = g_object_new (HAZE_TYPE_MEDIA_CHANNEL, - "connection", priv->conn, - "object-path", object_path, - "media", media, - "initial-peer", peer, - "initial-audio", initial_audio, - "initial-video", initial_video, - NULL); - - DEBUG ("object path %s", object_path); - - g_signal_connect (chan, "closed", (GCallback) media_channel_closed_cb, self); - - g_ptr_array_add (priv->channels, chan); - - g_free (object_path); - - return chan; -} - -static void -haze_media_manager_close_all (HazeMediaManager *self) -{ - HazeMediaManagerPrivate *priv = self->priv; - GPtrArray *tmp = g_ptr_array_sized_new (priv->channels->len); - guint i; - - for (i = 0; i < priv->channels->len; i++) - g_ptr_array_add (tmp, g_ptr_array_index (priv->channels, i)); - - DEBUG ("closing channels"); - - for (i = 0; i < tmp->len; i++) - { - HazeMediaChannel *chan = g_ptr_array_index (tmp, i); - - DEBUG ("closing %p", chan); - haze_media_channel_close (chan); - } - - if (priv->status_changed_id != 0) - { - g_signal_handler_disconnect (priv->conn, - priv->status_changed_id); - priv->status_changed_id = 0; - } -} - -static gboolean -init_media_cb (PurpleMediaManager *manager, - PurpleMedia *media, - PurpleAccount *account, - const gchar *username, - HazeMediaManager *self) -{ - HazeMediaManagerPrivate *priv = self->priv; - TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn); - TpHandleRepoIface *contact_repo = - tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); - TpHandle contact = tp_handle_ensure (contact_repo, username, NULL, NULL); - HazeMediaChannel *chan; - - if (purple_media_is_initiator (media, NULL, NULL) == TRUE) - return TRUE; - - chan = new_media_channel (self, media, contact, FALSE, FALSE); - tp_channel_manager_emit_new_channel (self, - TP_EXPORTABLE_CHANNEL (chan), NULL); - DEBUG ("called"); - return TRUE; -} - -static void -connection_status_changed_cb (HazeConnection *conn, - guint status, - guint reason, - HazeMediaManager *self) -{ - switch (status) - { - case TP_CONNECTION_STATUS_CONNECTING: - g_signal_connect (purple_media_manager_get (), "init-media", - G_CALLBACK (init_media_cb), self); - break; - - case TP_CONNECTION_STATUS_DISCONNECTED: - g_signal_handlers_disconnect_by_func (purple_media_manager_get (), - G_CALLBACK (init_media_cb), self); - haze_media_manager_close_all (self); - break; - } -} - -static void -haze_media_manager_constructed (GObject *object) -{ - void (*chain_up) (GObject *) = - G_OBJECT_CLASS (haze_media_manager_parent_class)->constructed; - HazeMediaManager *self = HAZE_MEDIA_MANAGER (object); - HazeMediaManagerPrivate *priv = self->priv; - - if (chain_up != NULL) - chain_up (object); - - priv->status_changed_id = g_signal_connect (priv->conn, - "status-changed", (GCallback) connection_status_changed_cb, object); -} - -static void -haze_media_manager_foreach_channel (TpChannelManager *manager, - TpExportableChannelFunc foreach, - gpointer user_data) -{ - HazeMediaManager *self = HAZE_MEDIA_MANAGER (manager); - HazeMediaManagerPrivate *priv = self->priv; - guint i; - - for (i = 0; i < priv->channels->len; i++) - { - TpExportableChannel *channel = TP_EXPORTABLE_CHANNEL ( - g_ptr_array_index (priv->channels, i)); - - foreach (channel, user_data); - } -} - -static const gchar * const media_channel_fixed_properties[] = { - TP_IFACE_CHANNEL ".ChannelType", - TP_IFACE_CHANNEL ".TargetHandleType", - NULL -}; - -static const gchar * const named_channel_allowed_properties[] = { - TP_IFACE_CHANNEL ".TargetHandle", - TP_IFACE_CHANNEL ".TargetID", - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio", - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo", - NULL -}; - -/* not advertised in foreach_channel_class - can only be requested with - * RequestChannel, not with CreateChannel/EnsureChannel */ -static const gchar * const anon_channel_allowed_properties[] = { - NULL -}; - -static GHashTable * -haze_media_manager_channel_class (void) -{ - return tp_asv_new ( - TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING, - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, - TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT, - TP_HANDLE_TYPE_CONTACT, - NULL); -} - -static void -haze_media_manager_foreach_channel_class (TpChannelManager *manager, - TpChannelManagerChannelClassFunc func, - gpointer user_data) -{ - GHashTable *table = haze_media_manager_channel_class (); - - func (manager, table, named_channel_allowed_properties, user_data); - - g_hash_table_destroy (table); -} - -typedef enum -{ - METHOD_REQUEST, - METHOD_CREATE, - METHOD_ENSURE, -} RequestMethod; - -typedef struct -{ - HazeMediaManager *self; - HazeMediaChannel *channel; - gpointer request_token; -} MediaChannelRequest; - -static MediaChannelRequest * -media_channel_request_new (HazeMediaManager *self, - HazeMediaChannel *channel, - gpointer request_token) -{ - MediaChannelRequest *mcr = g_slice_new0 (MediaChannelRequest); - - mcr->self = self; - mcr->channel = channel; - mcr->request_token = request_token; - - return mcr; -} - -static void -media_channel_request_free (MediaChannelRequest *mcr) -{ - g_slice_free (MediaChannelRequest, mcr); -} - -static void -media_channel_request_succeeded_cb (MediaChannelRequest *mcr, - GPtrArray *streams) -{ - GSList *request_tokens; - - request_tokens = g_slist_prepend (NULL, mcr->request_token); - tp_channel_manager_emit_new_channel (mcr->self, - TP_EXPORTABLE_CHANNEL (mcr->channel), request_tokens); - g_slist_free (request_tokens); - - media_channel_request_free (mcr); -} - -static void -media_channel_request_failed_cb (MediaChannelRequest *mcr, - GError *error) -{ - tp_channel_manager_emit_request_failed (mcr->self, mcr->request_token, - error->domain, error->code, error->message); - - media_channel_request_free (mcr); -} - -static gboolean -haze_media_manager_requestotron (TpChannelManager *manager, - gpointer request_token, - GHashTable *request_properties, - RequestMethod method) -{ - HazeMediaManager *self = HAZE_MEDIA_MANAGER (manager); - HazeMediaManagerPrivate *priv = self->priv; - TpHandleType handle_type; - TpHandle handle; - HazeMediaChannel *channel = NULL; - GError *error = NULL; - gboolean initial_audio, initial_video; - - /* Supported modes of operation: - * - CreateChannel({THT: Contact, TH: n}): - * channel has TargetHandle=n - * n is not in the group interface at all - * call is started when caller calls RequestStreams. - * - EnsureChannel({THT: Contact, TH: n}): - * look for a channel whose peer is n, and return that if found with - * whatever properties and group membership it has; - * otherwise the same as into CreateChannel - */ - - if (tp_strdiff (tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL ".ChannelType"), - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA)) - return FALSE; - - handle_type = tp_asv_get_uint32 (request_properties, - TP_IFACE_CHANNEL ".TargetHandleType", NULL); - - handle = tp_asv_get_uint32 (request_properties, - TP_IFACE_CHANNEL ".TargetHandle", NULL); - - initial_audio = tp_asv_get_boolean (request_properties, - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio", NULL); - initial_video = tp_asv_get_boolean (request_properties, - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo", NULL); - - switch (handle_type) - { - case TP_HANDLE_TYPE_NONE: - /* already checked by TpBaseConnection */ - g_assert (handle == 0); - - g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, - "A valid Contact handle must be provided when requesting a media " - "channel"); - - goto error; - case TP_HANDLE_TYPE_CONTACT: - /* validity already checked by TpBaseConnection */ - g_assert (handle != 0); - - if (tp_channel_manager_asv_has_unknown_properties (request_properties, - media_channel_fixed_properties, named_channel_allowed_properties, - &error)) - goto error; - - if (method == METHOD_ENSURE) - { - guint i; - TpHandle peer = 0; - - for (i = 0; i < priv->channels->len; i++) - { - channel = g_ptr_array_index (priv->channels, i); - g_object_get (channel, "peer", &peer, NULL); - - if (peer == handle) - { - /* Per the spec, we ignore InitialAudio and InitialVideo when - * looking for an existing channel. - */ - tp_channel_manager_emit_request_already_satisfied (self, - request_token, TP_EXPORTABLE_CHANNEL (channel)); - return TRUE; - } - } - } - - channel = new_media_channel (self, NULL, handle, - initial_audio, initial_video); - break; - default: - return FALSE; - } - - g_assert (channel != NULL); - - haze_media_channel_request_initial_streams (channel, - (GFunc) media_channel_request_succeeded_cb, - (GFunc) media_channel_request_failed_cb, - media_channel_request_new (self, channel, 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; -} - -static gboolean -haze_media_manager_request_channel (TpChannelManager *manager, - gpointer request_token, - GHashTable *request_properties) -{ - return haze_media_manager_requestotron (manager, request_token, - request_properties, METHOD_REQUEST); -} - - -static gboolean -haze_media_manager_create_channel (TpChannelManager *manager, - gpointer request_token, - GHashTable *request_properties) -{ - return haze_media_manager_requestotron (manager, request_token, - request_properties, METHOD_CREATE); -} - -static gboolean -haze_media_manager_ensure_channel (TpChannelManager *manager, - gpointer request_token, - GHashTable *request_properties) -{ - return haze_media_manager_requestotron (manager, request_token, - request_properties, METHOD_ENSURE); -} - -static void -channel_manager_iface_init (gpointer g_iface, - gpointer iface_data) -{ - TpChannelManagerIface *iface = g_iface; - - iface->foreach_channel = haze_media_manager_foreach_channel; - iface->foreach_channel_class = haze_media_manager_foreach_channel_class; - iface->request_channel = haze_media_manager_request_channel; - iface->create_channel = haze_media_manager_create_channel; - iface->ensure_channel = haze_media_manager_ensure_channel; -} diff --git a/src/media-manager.h b/src/media-manager.h deleted file mode 100644 index 72a0409..0000000 --- a/src/media-manager.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * media-manager.h - Header for HazeMediaManager - * Copyright (C) 2006, 2009 Collabora Ltd. - * - * Copied heavily from telepathy-gabble - * - * 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 __MEDIA_MANAGER_H__ -#define __MEDIA_MANAGER_H__ - -#include <glib-object.h> - -G_BEGIN_DECLS - -typedef struct _HazeMediaManager HazeMediaManager; -typedef struct _HazeMediaManagerClass HazeMediaManagerClass; -typedef struct _HazeMediaManagerPrivate HazeMediaManagerPrivate; - -struct _HazeMediaManagerClass { - GObjectClass parent_class; -}; - -struct _HazeMediaManager { - GObject parent; - - HazeMediaManagerPrivate *priv; -}; - -GType haze_media_manager_get_type (void); - -/* TYPE MACROS */ -#define HAZE_TYPE_MEDIA_MANAGER \ - (haze_media_manager_get_type ()) -#define HAZE_MEDIA_MANAGER(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), HAZE_TYPE_MEDIA_MANAGER,\ - HazeMediaManager)) -#define HAZE_MEDIA_MANAGER_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), HAZE_TYPE_MEDIA_MANAGER,\ - HazeMediaManagerClass)) -#define HAZE_IS_MEDIA_MANAGER(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj), HAZE_TYPE_MEDIA_MANAGER)) -#define HAZE_IS_MEDIA_MANAGER_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass), HAZE_TYPE_MEDIA_MANAGER)) -#define HAZE_MEDIA_MANAGER_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), HAZE_TYPE_MEDIA_MANAGER,\ - HazeMediaManagerClass)) - -G_END_DECLS - -#endif /* #ifndef __MEDIA_MANAGER_H__ */ - diff --git a/src/media-stream.c b/src/media-stream.c deleted file mode 100644 index 7b4f29f..0000000 --- a/src/media-stream.c +++ /dev/null @@ -1,1399 +0,0 @@ -/* - * media-stream.c - Source for HazeMediaStream - * Copyright © 2006-2009 Collabora Ltd. - * Copyright © 2006-2009 Nokia Corporation - * - * Copied heavily from telepathy-gabble. - * - * 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 "media-stream.h" - -#include <libpurple/media/backend-iface.h> -#include <string.h> -#include <telepathy-glib/dbus.h> -#include <telepathy-glib/errors.h> -#include <telepathy-glib/interfaces.h> -#include <telepathy-glib/properties-mixin.h> -#include <telepathy-glib/svc-media-interfaces.h> - -#define DEBUG_FLAG HAZE_DEBUG_MEDIA - -#include "debug.h" - -static void stream_handler_iface_init (gpointer, gpointer); - -G_DEFINE_TYPE_WITH_CODE (HazeMediaStream, - haze_media_stream, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, - tp_dbus_properties_mixin_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_STREAM_HANDLER, - stream_handler_iface_init) - ) - -/* properties */ -enum -{ - PROP_OBJECT_PATH = 1, - PROP_DBUS_DAEMON, - PROP_NAME, - PROP_PEER, - PROP_ID, - PROP_MEDIA_TYPE, - PROP_CONNECTION_STATE, - PROP_READY, - PROP_PLAYING, - PROP_COMBINED_DIRECTION, - PROP_LOCAL_HOLD, - PROP_MEDIA, - PROP_CODECS_READY, - PROP_STUN_SERVERS, - PROP_RELAY_INFO, - PROP_NAT_TRAVERSAL, - PROP_CREATED_LOCALLY, - LAST_PROPERTY -}; - -/* private structure */ - -struct _HazeMediaStreamPrivate -{ - PurpleMedia *media; - - gchar *object_path; - TpDBusDaemon *dbus_daemon; - guint id; - guint media_type; - - GList *codecs; - GList *remote_codecs; - GList *local_candidates; - GList *remote_candidates; - - /* Whether we're waiting for a codec intersection from the streaming - * implementation. If FALSE, SupportedCodecs is a no-op. - */ - gboolean awaiting_intersection; - - guint remote_candidate_count; - - gchar *nat_traversal; - /* GPtrArray(GValueArray(STRING, UINT)) */ - GPtrArray *stun_servers; - /* GPtrArray(GHashTable(string => GValue)) */ - GPtrArray *relay_info; - - gboolean on_hold; - - gboolean closed; - gboolean dispose_has_run; - gboolean local_hold; - gboolean ready; - gboolean sending; - gboolean created_locally; -}; - -HazeMediaStream * -haze_media_stream_new (const gchar *object_path, - TpDBusDaemon *dbus_daemon, - PurpleMedia *media, - const gchar *name, - const gchar *peer, - guint media_type, - guint id, - gboolean created_locally, - const gchar *nat_traversal, - const GPtrArray *relay_info, - gboolean local_hold) -{ - GPtrArray *empty = NULL; - HazeMediaStream *result; - - g_return_val_if_fail (PURPLE_IS_MEDIA (media), NULL); - - if (relay_info == NULL) - { - empty = g_ptr_array_sized_new (0); - relay_info = empty; - } - - result = g_object_new (HAZE_TYPE_MEDIA_STREAM, - "object-path", object_path, - "dbus-daemon", dbus_daemon, - "media", media, - "name", name, - "peer", peer, - "media-type", media_type, - "id", id, - "created-locally", created_locally, - "nat-traversal", nat_traversal, - "relay-info", relay_info, - "local-hold", local_hold, - NULL); - - if (empty != NULL) - g_ptr_array_free (empty, TRUE); - - return result; -} - -TpMediaStreamType -haze_media_stream_get_media_type (HazeMediaStream *self) -{ - return self->priv->media_type; -} - -static void -haze_media_stream_init (HazeMediaStream *self) -{ - HazeMediaStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - HAZE_TYPE_MEDIA_STREAM, HazeMediaStreamPrivate); - - self->priv = priv; - - priv->stun_servers = g_ptr_array_sized_new (1); -} - -static GObject * -haze_media_stream_constructor (GType type, guint n_props, - GObjectConstructParam *props) -{ - GObject *obj; - HazeMediaStream *stream; - HazeMediaStreamPrivate *priv; - - /* call base class constructor */ - obj = G_OBJECT_CLASS (haze_media_stream_parent_class)-> - constructor (type, n_props, props); - stream = HAZE_MEDIA_STREAM (obj); - priv = stream->priv; - - g_assert (priv->media != NULL); - - /* go for the bus */ - tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj); - - if (priv->created_locally) - { - g_object_set (stream, "combined-direction", - MAKE_COMBINED_DIRECTION (TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, - 0), NULL); - } - else - { - priv->awaiting_intersection = TRUE; - g_object_set (stream, "combined-direction", - MAKE_COMBINED_DIRECTION (TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, - TP_MEDIA_STREAM_PENDING_LOCAL_SEND), NULL); - } - - return obj; -} - -static void -haze_media_stream_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - HazeMediaStream *stream = HAZE_MEDIA_STREAM (object); - HazeMediaStreamPrivate *priv = stream->priv; - - switch (property_id) { - case PROP_OBJECT_PATH: - g_value_set_string (value, priv->object_path); - break; - case PROP_DBUS_DAEMON: - g_value_set_object (value, priv->dbus_daemon); - break; - case PROP_NAME: - g_value_set_string (value, stream->name); - break; - case PROP_PEER: - g_value_set_string (value, stream->peer); - break; - case PROP_ID: - g_value_set_uint (value, priv->id); - break; - case PROP_MEDIA_TYPE: - g_value_set_uint (value, priv->media_type); - break; - case PROP_CONNECTION_STATE: - g_value_set_uint (value, stream->connection_state); - break; - case PROP_READY: - g_value_set_boolean (value, priv->ready); - break; - case PROP_PLAYING: - g_value_set_boolean (value, stream->playing); - break; - case PROP_COMBINED_DIRECTION: - g_value_set_uint (value, stream->combined_direction); - break; - case PROP_LOCAL_HOLD: - g_value_set_boolean (value, priv->local_hold); - break; - case PROP_MEDIA: - g_value_set_object (value, priv->media); - break; - case PROP_CODECS_READY: - g_value_set_boolean (value, priv->codecs != NULL); - break; - case PROP_STUN_SERVERS: - g_value_set_boxed (value, priv->stun_servers); - break; - case PROP_NAT_TRAVERSAL: - g_value_set_string (value, priv->nat_traversal); - break; - case PROP_CREATED_LOCALLY: - g_value_set_boolean (value, priv->created_locally); - break; - case PROP_RELAY_INFO: - g_value_set_boxed (value, priv->relay_info); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -haze_media_stream_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - HazeMediaStream *stream = HAZE_MEDIA_STREAM (object); - HazeMediaStreamPrivate *priv = stream->priv; - - switch (property_id) { - case PROP_OBJECT_PATH: - g_free (priv->object_path); - priv->object_path = g_value_dup_string (value); - break; - case PROP_DBUS_DAEMON: - g_assert (priv->dbus_daemon == NULL); /* construct-only */ - priv->dbus_daemon = g_value_dup_object (value); - break; - case PROP_NAME: - g_free (stream->name); - stream->name = g_value_dup_string (value); - break; - case PROP_PEER: - g_free (stream->peer); - stream->peer = g_value_dup_string (value); - break; - case PROP_ID: - priv->id = g_value_get_uint (value); - break; - case PROP_MEDIA_TYPE: - priv->media_type = g_value_get_uint (value); - break; - case PROP_CONNECTION_STATE: - DEBUG ("stream %s connection state %d", - stream->name, stream->connection_state); - stream->connection_state = g_value_get_uint (value); - break; - case PROP_READY: - priv->ready = g_value_get_boolean (value); - break; - case PROP_PLAYING: - break; - case PROP_COMBINED_DIRECTION: - DEBUG ("changing combined direction from %u to %u", - stream->combined_direction, g_value_get_uint (value)); - stream->combined_direction = g_value_get_uint (value); - break; - case PROP_MEDIA: - g_assert (priv->media == NULL); - priv->media = g_value_dup_object (value); - break; - case PROP_NAT_TRAVERSAL: - g_assert (priv->nat_traversal == NULL); - priv->nat_traversal = g_value_dup_string (value); - break; - case PROP_CREATED_LOCALLY: - priv->created_locally = g_value_get_boolean (value); - break; - case PROP_RELAY_INFO: - g_assert (priv->relay_info == NULL); - priv->relay_info = g_value_dup_boxed (value); - break; - case PROP_LOCAL_HOLD: - priv->local_hold = g_value_get_boolean (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void haze_media_stream_dispose (GObject *object); -static void haze_media_stream_finalize (GObject *object); - -static void -haze_media_stream_class_init (HazeMediaStreamClass *haze_media_stream_class) -{ - static TpDBusPropertiesMixinPropImpl stream_handler_props[] = { - { "RelayInfo", "relay-info", NULL }, - { "STUNServers", "stun-servers", NULL }, - { "NATTraversal", "nat-traversal", NULL }, - { "CreatedLocally", "created-locally", NULL }, - { NULL } - }; - static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { - { TP_IFACE_MEDIA_STREAM_HANDLER, - tp_dbus_properties_mixin_getter_gobject_properties, - NULL, - stream_handler_props, - }, - { NULL } - }; - GObjectClass *object_class = G_OBJECT_CLASS (haze_media_stream_class); - GParamSpec *param_spec; - - g_type_class_add_private (haze_media_stream_class, - sizeof (HazeMediaStreamPrivate)); - - object_class->constructor = haze_media_stream_constructor; - - object_class->get_property = haze_media_stream_get_property; - object_class->set_property = haze_media_stream_set_property; - - object_class->dispose = haze_media_stream_dispose; - object_class->finalize = haze_media_stream_finalize; - - param_spec = g_param_spec_string ("object-path", "D-Bus object path", - "The D-Bus object path used for this " - "object on the bus.", - NULL, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); - - param_spec = g_param_spec_object ("dbus-daemon", "D-Bus connection", - "Connection to D-Bus", - TP_TYPE_DBUS_DAEMON, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec); - - param_spec = g_param_spec_string ("name", "Stream name", - "An opaque name for the stream used in the signalling.", NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_NAME, param_spec); - - param_spec = g_param_spec_string ("peer", "Peer name", - "The name for the peer used in the signalling.", NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_PEER, param_spec); - - param_spec = g_param_spec_uint ("id", "Stream ID", - "A stream number for the stream used in the " - "D-Bus API.", - 0, G_MAXUINT, 0, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_NAME | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_ID, param_spec); - - param_spec = g_param_spec_uint ("media-type", "Stream media type", - "A constant indicating which media type the stream carries.", - TP_MEDIA_STREAM_TYPE_AUDIO, TP_MEDIA_STREAM_TYPE_VIDEO, - TP_MEDIA_STREAM_TYPE_AUDIO, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_MEDIA_TYPE, param_spec); - - param_spec = g_param_spec_uint ("connection-state", "Stream connection state", - "An integer indicating the state of the" - "stream's connection.", - TP_MEDIA_STREAM_STATE_DISCONNECTED, - TP_MEDIA_STREAM_STATE_CONNECTED, - TP_MEDIA_STREAM_STATE_DISCONNECTED, - G_PARAM_CONSTRUCT | - G_PARAM_READWRITE | - G_PARAM_STATIC_NAME | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_CONNECTION_STATE, - param_spec); - - param_spec = g_param_spec_boolean ("ready", "Ready?", - "A boolean signifying whether the user " - "is ready to handle signals from this " - "object.", - FALSE, - G_PARAM_CONSTRUCT | - G_PARAM_READWRITE | - G_PARAM_STATIC_NAME | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_READY, param_spec); - - param_spec = g_param_spec_boolean ("playing", "Set playing", - "A boolean signifying whether the stream " - "has been set playing yet.", - FALSE, - G_PARAM_CONSTRUCT | - G_PARAM_READWRITE | - G_PARAM_STATIC_NAME | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_PLAYING, param_spec); - - param_spec = g_param_spec_uint ("combined-direction", - "Combined direction", - "An integer indicating the directions the stream currently sends in, " - "and the peers who have been asked to send.", - TP_MEDIA_STREAM_DIRECTION_NONE, - MAKE_COMBINED_DIRECTION (TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, - TP_MEDIA_STREAM_PENDING_LOCAL_SEND | - TP_MEDIA_STREAM_PENDING_REMOTE_SEND), - TP_MEDIA_STREAM_DIRECTION_NONE, - G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_COMBINED_DIRECTION, - param_spec); - - param_spec = g_param_spec_boolean ("local-hold", "Local hold?", - "True if resources used for this stream have been freed.", FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK); - g_object_class_install_property (object_class, PROP_LOCAL_HOLD, param_spec); - - param_spec = g_param_spec_object ("media", "PurpleMedia object", - "Media object signalling this media stream.", - PURPLE_TYPE_MEDIA, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_NICK | - G_PARAM_STATIC_BLURB); - g_object_class_install_property (object_class, PROP_MEDIA, param_spec); - - param_spec = g_param_spec_boxed ("stun-servers", "STUN servers", - "Array of (STRING: address literal, UINT: port) pairs", - /* FIXME: use correct macro when available */ - tp_type_dbus_array_su (), - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_STUN_SERVERS, param_spec); - - param_spec = g_param_spec_boxed ("relay-info", "Relay info", - "Array of mappings containing relay server information", - TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_RELAY_INFO, param_spec); - - param_spec = g_param_spec_string ("nat-traversal", "NAT traversal", - "NAT traversal mechanism for this stream", NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_NAT_TRAVERSAL, - param_spec); - - param_spec = g_param_spec_boolean ("created-locally", "Created locally?", - "True if this stream was created by the local user", FALSE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_CREATED_LOCALLY, - param_spec); - - param_spec = g_param_spec_boolean ("codecs-ready", "Codecs ready", - "True if the codecs for this stream are ready to be used", FALSE, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); - g_object_class_install_property (object_class, PROP_CODECS_READY, - param_spec); - - haze_media_stream_class->dbus_props_class.interfaces = prop_interfaces; - tp_dbus_properties_mixin_class_init (object_class, - G_STRUCT_OFFSET (HazeMediaStreamClass, dbus_props_class)); -} - -void -haze_media_stream_dispose (GObject *object) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (object); - HazeMediaStreamPrivate *priv = self->priv; - - DEBUG ("called"); - - if (priv->dispose_has_run) - return; - - priv->dispose_has_run = TRUE; - - tp_clear_object (&priv->dbus_daemon); - - if (priv->local_candidates) - { - purple_media_candidate_list_free (priv->local_candidates); - priv->local_candidates = NULL; - } - - if (priv->remote_candidates) - { - purple_media_candidate_list_free (priv->remote_candidates); - priv->remote_candidates = NULL; - } - - if (priv->codecs) - { - purple_media_codec_list_free (priv->codecs); - priv->codecs = NULL; - } - - if (priv->remote_codecs) - { - purple_media_codec_list_free (priv->remote_codecs); - priv->remote_codecs = NULL; - } - - g_object_unref (priv->media); - priv->media = NULL; - - if (G_OBJECT_CLASS (haze_media_stream_parent_class)->dispose) - G_OBJECT_CLASS (haze_media_stream_parent_class)->dispose (object); -} - -void -haze_media_stream_finalize (GObject *object) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (object); - HazeMediaStreamPrivate *priv = self->priv; - - g_free (priv->object_path); - g_free (priv->nat_traversal); - - /* FIXME: use correct macro when available */ - if (priv->stun_servers != NULL) - g_boxed_free (tp_type_dbus_array_su (), priv->stun_servers); - - if (priv->relay_info != NULL) - g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, priv->relay_info); - - G_OBJECT_CLASS (haze_media_stream_parent_class)->finalize (object); -} - - -GList * -haze_media_stream_get_local_candidates (HazeMediaStream *self) -{ - HazeMediaStreamPrivate *priv = self->priv; - return g_list_copy (priv->local_candidates); -} - - -GList * -haze_media_stream_get_codecs (HazeMediaStream *self) -{ - HazeMediaStreamPrivate *priv = self->priv; - return purple_media_codec_list_copy (priv->codecs); -} - - -static void -pass_remote_candidates (HazeMediaStream *self) -{ - HazeMediaStreamPrivate *priv = self->priv; - GType transport_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT; - GType candidate_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE; - GList *iter = priv->remote_candidates; - - for (; iter; iter = g_list_next (iter)) - { - gchar *address, *username, *password, *candidate_id; - GValue candidate = { 0, }; - GPtrArray *transports; - GValue transport = { 0, }; - PurpleMediaCandidate *c = iter->data; - PurpleMediaCandidateType candidate_type; - guint type = TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL; - - g_value_init (&transport, transport_struct_type); - g_value_take_boxed (&transport, - dbus_g_type_specialized_construct (transport_struct_type)); - - address = purple_media_candidate_get_ip (c); - username = purple_media_candidate_get_username (c); - password = purple_media_candidate_get_password (c); - candidate_type = purple_media_candidate_get_candidate_type (c); - - if (candidate_type == PURPLE_MEDIA_CANDIDATE_TYPE_HOST) - type = TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL; - else if (candidate_type == PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX) - type = TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED; - else if (candidate_type == PURPLE_MEDIA_CANDIDATE_TYPE_RELAY) - type = TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY; - else - DEBUG ("Unknown candidate type"); - - dbus_g_type_struct_set (&transport, - 0, purple_media_candidate_get_component_id (c), - 1, address, - 2, purple_media_candidate_get_port (c), - 3, purple_media_candidate_get_protocol (c) == - PURPLE_MEDIA_NETWORK_PROTOCOL_UDP ? - TP_MEDIA_STREAM_BASE_PROTO_UDP : - TP_MEDIA_STREAM_BASE_PROTO_TCP, - 4, "RTP", - 5, "AVP", - 6, (double)purple_media_candidate_get_priority (c), - 7, type, - 8, username, - 9, password, - G_MAXUINT); - - g_free (password); - g_free (username); - g_free (address); - - transports = g_ptr_array_sized_new (1); - g_ptr_array_add (transports, g_value_get_boxed (&transport)); - - g_value_init (&candidate, candidate_struct_type); - g_value_take_boxed (&candidate, - dbus_g_type_specialized_construct (candidate_struct_type)); - - candidate_id = purple_media_candidate_get_foundation (c); - - dbus_g_type_struct_set (&candidate, - 0, candidate_id, - 1, transports, - G_MAXUINT); - - DEBUG ("passing 1 remote candidate to stream engine: %s", candidate_id); - - tp_svc_media_stream_handler_emit_add_remote_candidate ( - self, candidate_id, transports); - - g_free (candidate_id); - } -} - - -void -haze_media_stream_add_remote_candidates (HazeMediaStream *self, - GList *remote_candidates) -{ - HazeMediaStreamPrivate *priv = self->priv; - - priv->remote_candidates = g_list_concat ( - priv->remote_candidates, - purple_media_candidate_list_copy (remote_candidates)); - - if (priv->ready == TRUE) - pass_remote_candidates (self); -} - - -static void -pass_remote_codecs (HazeMediaStream *self) -{ - HazeMediaStreamPrivate *priv = self->priv; - GType codec_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC; - GPtrArray *codecs = g_ptr_array_new (); - GList *iter = priv->remote_codecs; - - for (; iter; iter = g_list_next (iter)) - { - GValue codec = { 0, }; - PurpleMediaCodec *c = iter->data; - gchar *name; - GList *codec_params; - GHashTable *params; - - g_value_init (&codec, codec_struct_type); - g_value_take_boxed (&codec, - dbus_g_type_specialized_construct (codec_struct_type)); - - name = purple_media_codec_get_encoding_name (c); - codec_params = purple_media_codec_get_optional_parameters (c); - params = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, g_free); - - for (; codec_params; codec_params = g_list_next (codec_params)) - { - PurpleKeyValuePair *pair = codec_params->data; - g_hash_table_insert (params, pair->key, pair->value); - } - - DEBUG ("new remote %s codec: %u '%s' %u %u %u", - priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video", - purple_media_codec_get_id (c), name, priv->media_type, - purple_media_codec_get_clock_rate (c), - purple_media_codec_get_channels (c)); - - dbus_g_type_struct_set (&codec, - 0, purple_media_codec_get_id (c), - 1, name, - 2, priv->media_type, - 3, purple_media_codec_get_clock_rate (c), - 4, purple_media_codec_get_channels (c), - 5, params, - G_MAXUINT); - - g_free (name); - g_hash_table_destroy (params); - - g_ptr_array_add (codecs, g_value_get_boxed (&codec)); - } - - DEBUG ("passing %d remote codecs to stream-engine", codecs->len); - - tp_svc_media_stream_handler_emit_set_remote_codecs (self, codecs); -} - - -void -haze_media_stream_set_remote_codecs (HazeMediaStream *self, - GList *remote_codecs) -{ - HazeMediaStreamPrivate *priv = self->priv; - GList *iter = priv->remote_codecs; - - for (; iter; iter = g_list_delete_link (iter, iter)) - g_object_unref (iter->data); - - priv->remote_codecs = purple_media_codec_list_copy (remote_codecs); - - if (priv->ready == TRUE) - pass_remote_codecs (self); -} - -void -haze_media_stream_add_stun_server (HazeMediaStream *self, - const gchar *stun_ip, - guint stun_port) -{ - HazeMediaStreamPrivate *priv = self->priv; - GValueArray *va; - GValue ip = {0}, port = {0}; - - if (stun_ip == NULL || stun_ip[0] == 0) - { - DEBUG ("Invalid STUN address passed: %s", stun_ip); - return; - } - else if (stun_port > 65535) - { - DEBUG ("Invalid STUN port passed: %d", stun_port); - return; - } - - g_value_init (&ip, G_TYPE_STRING); - g_value_set_string (&ip, stun_ip); - g_value_init (&port, G_TYPE_UINT); - g_value_set_uint (&port, stun_port); - - va = g_value_array_new (2); - g_value_array_append (va, &ip); - g_value_array_append (va, &port); - - g_ptr_array_add (priv->stun_servers, va); -} - - -/** - * haze_media_stream_codec_choice - * - * Implements D-Bus method CodecChoice - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_codec_choice (TpSvcMediaStreamHandler *iface, - guint codec_id, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - - g_assert (HAZE_IS_MEDIA_STREAM (self)); - - tp_svc_media_stream_handler_return_from_codec_choice (context); -} - - -gboolean -haze_media_stream_error (HazeMediaStream *self, - guint err_no, - const gchar *message, - GError **error) -{ - g_assert (HAZE_IS_MEDIA_STREAM (self)); - - DEBUG ( "Media.StreamHandler::Error called, error %u (%s) -- emitting signal", - err_no, message); - - purple_media_error (self->priv->media, message); - - return TRUE; -} - - -/** - * haze_media_stream_error - * - * Implements D-Bus method Error - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_error_async (TpSvcMediaStreamHandler *iface, - guint errno, - const gchar *message, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - GError *error = NULL; - - if (haze_media_stream_error (self, errno, message, &error)) - { - tp_svc_media_stream_handler_return_from_error (context); - } - else - { - dbus_g_method_return_error (context, error); - g_error_free (error); - } -} - - -/** - * haze_media_stream_hold: - * - * Tell streaming clients that the stream is going on hold, so they should - * stop streaming and free up any resources they are currently holding - * (e.g. close hardware devices); or that the stream is coming off hold, - * so they should reacquire those resources. - */ -void -haze_media_stream_hold (HazeMediaStream *self, - gboolean hold) -{ - tp_svc_media_stream_handler_emit_set_stream_held (self, hold); -} - - -/** - * haze_media_stream_hold_state: - * - * Called by streaming clients when the stream's hold state has been changed - * successfully in response to SetStreamHeld. - */ -static void -haze_media_stream_hold_state (TpSvcMediaStreamHandler *iface, - gboolean hold_state, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - HazeMediaStreamPrivate *priv = self->priv; - - DEBUG ("%p: %s", self, hold_state ? "held" : "unheld"); - priv->local_hold = hold_state; - - g_object_notify ((GObject *) self, "local-hold"); - - tp_svc_media_stream_handler_return_from_hold_state (context); -} - - -/** - * haze_media_stream_unhold_failure: - * - * Called by streaming clients when an attempt to reacquire the necessary - * hardware or software resources to unhold the stream, in response to - * SetStreamHeld, has failed. - */ -static void -haze_media_stream_unhold_failure (TpSvcMediaStreamHandler *iface, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - HazeMediaStreamPrivate *priv = self->priv; - - DEBUG ("%p", self); - - priv->local_hold = TRUE; - -// maybe emit unhold failed here? - g_object_notify ((GObject *) self, "local-hold"); - - tp_svc_media_stream_handler_return_from_unhold_failure (context); -} - - -/** - * haze_media_stream_native_candidates_prepared - * - * Implements D-Bus method NativeCandidatesPrepared - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_native_candidates_prepared (TpSvcMediaStreamHandler *iface, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - HazeMediaStreamPrivate *priv; - PurpleMediaBackend *backend; - - g_assert (HAZE_IS_MEDIA_STREAM (self)); - - priv = self->priv; - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - g_signal_emit_by_name (backend, "candidates-prepared", - self->name, self->peer); - g_object_unref (backend); - - tp_svc_media_stream_handler_return_from_native_candidates_prepared (context); -} - - -/** - * haze_media_stream_new_active_candidate_pair - * - * Implements D-Bus method NewActiveCandidatePair - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_new_active_candidate_pair (TpSvcMediaStreamHandler *iface, - const gchar *native_candidate_id, - const gchar *remote_candidate_id, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - HazeMediaStreamPrivate *priv; - PurpleMediaBackend *backend; - GList *l_iter; - - /* - * This appears to be called for each pair of components. - * I'm not sure how to go about differentiating between the two - * components as the ids are the same. - */ - - DEBUG ("called (%s, %s)", native_candidate_id, remote_candidate_id); - - g_assert (HAZE_IS_MEDIA_STREAM (self)); - - priv = self->priv; - l_iter = priv->local_candidates; - g_object_get (priv->media, "backend", &backend, NULL); - - for (; l_iter; l_iter = g_list_next (l_iter)) - { - PurpleMediaCandidate *lc = l_iter->data; - GList *r_iter = priv->remote_candidates; - - for (; r_iter; r_iter = g_list_next (r_iter)) - { - PurpleMediaCandidate *rc = r_iter->data; - - if (purple_media_candidate_get_component_id (lc) == - purple_media_candidate_get_component_id (rc)) - { - gchar *l_name = purple_media_candidate_get_foundation (lc); - gchar *r_name = purple_media_candidate_get_foundation (rc); - - if (!strcmp (l_name, native_candidate_id) && - !strcmp (r_name, remote_candidate_id)) - { - DEBUG ("Emitting new active candidate pair %d: %s - %s", - purple_media_candidate_get_component_id (lc), - l_name, r_name); - - g_signal_emit_by_name (backend, "active-candidate-pair", - self->name, self->peer, lc, rc); - } - - g_free (l_name); - g_free (r_name); - } - } - } - - g_object_unref (backend); - tp_svc_media_stream_handler_return_from_new_active_candidate_pair (context); -} - - -/** - * haze_media_stream_new_native_candidate - * - * Implements D-Bus method NewNativeCandidate - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface, - const gchar *candidate_id, - const GPtrArray *transports, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - HazeMediaStreamPrivate *priv; - PurpleMediaBackend *backend; - guint i; - - g_assert (HAZE_IS_MEDIA_STREAM (self)); - - priv = self->priv; - - g_object_get (G_OBJECT (priv->media), "backend", &backend, NULL); - - for (i = 0; i < transports->len; i++) - { - GValueArray *transport; - guint component, type, proto; - PurpleMediaCandidate *c; - PurpleMediaCandidateType candidate_type = - PURPLE_MEDIA_CANDIDATE_TYPE_HOST; - PurpleMediaNetworkProtocol protocol = PURPLE_MEDIA_NETWORK_PROTOCOL_UDP; - - transport = g_ptr_array_index (transports, i); - component = g_value_get_uint (g_value_array_get_nth (transport, 0)); - type = g_value_get_uint (g_value_array_get_nth (transport, 7)); - proto = g_value_get_uint (g_value_array_get_nth (transport, 3)); - - if (type == TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL) - candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST; - else if (type == TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED) - candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX; - else if (type == TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY) - candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_RELAY; - else - DEBUG ("Unknown candidate type"); - - if (proto == TP_MEDIA_STREAM_BASE_PROTO_UDP) - protocol = PURPLE_MEDIA_NETWORK_PROTOCOL_UDP; - else if (proto == TP_MEDIA_STREAM_BASE_PROTO_TCP) - protocol = PURPLE_MEDIA_NETWORK_PROTOCOL_TCP; - else - DEBUG ("Unknown network protocol"); - - c = purple_media_candidate_new (candidate_id, component, candidate_type, - protocol, - /* address */ - g_value_get_string (g_value_array_get_nth (transport, 1)), - /* port */ - g_value_get_uint (g_value_array_get_nth (transport, 2))); - - g_object_set (c, "username", - g_value_get_string (g_value_array_get_nth (transport, 8)), NULL); - g_object_set (c, "password", - g_value_get_string (g_value_array_get_nth (transport, 9)), NULL); - g_object_set (c, "priority", - (guint)g_value_get_double ( - g_value_array_get_nth (transport, 6)), NULL); - - DEBUG ("new-candidate: %s %s %p", self->name, self->peer, c); - - priv->local_candidates = g_list_append (priv->local_candidates, c); - - g_signal_emit_by_name (backend, "new-candidate", self->name, self->peer, c); - } - - g_object_unref (backend); - - tp_svc_media_stream_handler_return_from_new_native_candidate (context); -} - -static void haze_media_stream_set_local_codecs (TpSvcMediaStreamHandler *, - const GPtrArray *codecs, DBusGMethodInvocation *); - -/** - * haze_media_stream_ready - * - * Implements D-Bus method Ready - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_ready (TpSvcMediaStreamHandler *iface, - const GPtrArray *codecs, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - HazeMediaStreamPrivate *priv; - - g_assert (HAZE_IS_MEDIA_STREAM (self)); - - priv = self->priv; - - DEBUG ("ready called"); - - if (priv->ready == FALSE) - { - g_object_set (self, "ready", TRUE, NULL); - - tp_svc_media_stream_handler_emit_set_stream_playing (self, TRUE); - - if (purple_media_get_session_type (priv->media, self->name) & - (PURPLE_MEDIA_SEND_AUDIO | PURPLE_MEDIA_SEND_VIDEO)) - { - g_object_set (self, "combined-direction", - MAKE_COMBINED_DIRECTION (TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, - 0), NULL); - tp_svc_media_stream_handler_emit_set_stream_sending (self, TRUE); - } - - /* If a new stream is added while the call's on hold, it will have - * local_hold set at construct time. So once tp-fs has called Ready(), we - * should let it know this stream's on hold. - */ - if (priv->local_hold) - haze_media_stream_hold (self, priv->local_hold); - } - else - { - DEBUG ("Ready called twice, running plain SetLocalCodecs instead"); - } - - /* set_local_codecs and ready return the same thing, so we can do... */ - haze_media_stream_set_local_codecs (iface, codecs, context); - pass_remote_codecs (self); - pass_remote_candidates (self); -} - -static void -convert_param (gchar *key, gchar *value, PurpleMediaCodec *codec) -{ - purple_media_codec_add_optional_parameter (codec, key, value); -} - -static gboolean -pass_local_codecs (HazeMediaStream *stream, - const GPtrArray *codecs, - gboolean ready, - GError **error) -{ - HazeMediaStreamPrivate *priv = stream->priv; - PurpleMediaSessionType type = PURPLE_MEDIA_AUDIO; - PurpleMediaCodec *c; - guint i; - - DEBUG ("putting list of %d supported codecs from stream-engine into cache", - codecs->len); - - if (priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO) - type = PURPLE_MEDIA_AUDIO; - else if (priv->media_type == TP_MEDIA_STREAM_TYPE_VIDEO) - type = PURPLE_MEDIA_VIDEO; - else - g_assert_not_reached (); - - for (i = 0; i < codecs->len; i++) - { - GType codec_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC; - - GValue codec = { 0, }; - guint id, clock_rate, channels; - gchar *name; - GHashTable *params; - - g_value_init (&codec, codec_struct_type); - g_value_set_static_boxed (&codec, g_ptr_array_index (codecs, i)); - - dbus_g_type_struct_get (&codec, - 0, &id, - 1, &name, - 3, &clock_rate, - 4, &channels, - 5, ¶ms, - G_MAXUINT); - - c = purple_media_codec_new (id, name, type, clock_rate); - g_object_set (c, "channels", channels, NULL); - - g_hash_table_foreach (params, (GHFunc)convert_param, c); - - DEBUG ("adding codec: %s", purple_media_codec_to_string (c)); - - priv->codecs = g_list_append (priv->codecs, c); - - g_signal_emit_by_name (priv->media, "codecs-changed", stream->name); - } - - return TRUE; -} - -/** - * haze_media_stream_set_local_codecs - * - * Implements D-Bus method SetLocalCodecs - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_set_local_codecs (TpSvcMediaStreamHandler *iface, - const GPtrArray *codecs, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - HazeMediaStreamPrivate *priv = self->priv; - GError *error = NULL; - - DEBUG ("called"); - - if (PURPLE_IS_MEDIA (priv->media) && - purple_media_is_initiator (priv->media, self->name, self->peer)) - { - if (!pass_local_codecs (self, codecs, self->priv->created_locally, - &error)) - { - DEBUG ("failed: %s", error->message); - - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - } - else - { - DEBUG ("ignoring local codecs, waiting for codec intersection"); - } - - tp_svc_media_stream_handler_return_from_set_local_codecs (context); -} - -/** - * haze_media_stream_stream_state - * - * Implements D-Bus method StreamState - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_stream_state (TpSvcMediaStreamHandler *iface, - guint connection_state, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - - g_object_set (self, "connection-state", connection_state, NULL); - - // emit connection state here - - tp_svc_media_stream_handler_return_from_stream_state (context); -} - - -/** - * haze_media_stream_supported_codecs - * - * Implements D-Bus method SupportedCodecs - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_supported_codecs (TpSvcMediaStreamHandler *iface, - const GPtrArray *codecs, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - HazeMediaStreamPrivate *priv = self->priv; - GError *error = NULL; - - DEBUG ("called"); - - if (priv->awaiting_intersection) - { - if (!pass_local_codecs (self, codecs, TRUE, &error)) - { - DEBUG ("failed: %s", error->message); - - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - priv->awaiting_intersection = FALSE; - } - else - { - /* If we created the stream, we don't need to send the intersection. If - * we didn't create it, but have already sent the intersection once, we - * don't need to send it again. In either case, extra calls to - * SupportedCodecs are in response to an incoming description-info, which - * can only change parameters and which XEP-0167 §10 says is purely - * advisory. - */ - DEBUG ("we already sent, or don't need to send, our codecs"); - } - - tp_svc_media_stream_handler_return_from_supported_codecs (context); -} - -/** - * haze_media_stream_codecs_updated - * - * Implements D-Bus method CodecsUpdated - * on interface org.freedesktop.Telepathy.Media.StreamHandler - */ -static void -haze_media_stream_codecs_updated (TpSvcMediaStreamHandler *iface, - const GPtrArray *codecs, - DBusGMethodInvocation *context) -{ - HazeMediaStream *self = HAZE_MEDIA_STREAM (iface); - GError *error = NULL; - - if (self->priv->codecs == NULL) - { - GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE, - "CodecsUpdated may only be called once an initial set of codecs " - "has been set" }; - - dbus_g_method_return_error (context, &e); - return; - } - - if (self->priv->awaiting_intersection) - { - /* When awaiting an intersection the initial set of codecs should be set - * by calling SupportedCodecs as that is the canonical set of codecs, - * updates are only meaningful afterwards */ - tp_svc_media_stream_handler_return_from_codecs_updated (context); - return; - } - - if (pass_local_codecs (self, codecs, self->priv->created_locally, &error)) - { - tp_svc_media_stream_handler_return_from_codecs_updated (context); - } - else - { - DEBUG ("failed: %s", error->message); - - dbus_g_method_return_error (context, error); - g_error_free (error); - } -} - -static void -stream_handler_iface_init (gpointer g_iface, gpointer iface_data) -{ - TpSvcMediaStreamHandlerClass *klass = - (TpSvcMediaStreamHandlerClass *) g_iface; - -#define IMPLEMENT(x,suffix) tp_svc_media_stream_handler_implement_##x (\ - klass, haze_media_stream_##x##suffix) - IMPLEMENT(codec_choice,); - IMPLEMENT(error,_async); - IMPLEMENT(hold_state,); - IMPLEMENT(native_candidates_prepared,); - IMPLEMENT(new_active_candidate_pair,); - IMPLEMENT(new_native_candidate,); - IMPLEMENT(ready,); - IMPLEMENT(set_local_codecs,); - IMPLEMENT(stream_state,); - IMPLEMENT(supported_codecs,); - IMPLEMENT(unhold_failure,); - IMPLEMENT(codecs_updated,); -#undef IMPLEMENT -} diff --git a/src/media-stream.h b/src/media-stream.h deleted file mode 100644 index 0d6f328..0000000 --- a/src/media-stream.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * media-stream.h - Header for HazeMediaStream - * Copyright (C) 2006, 2009 Collabora Ltd. - * Copyright (C) 2006 Nokia Corporation - * - * Copied heavily from telepathy-gabble. - * - * 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 __HAZE_MEDIA_STREAM_H__ -#define __HAZE_MEDIA_STREAM_H__ - -#include <glib-object.h> -#include <libpurple/media.h> -#include <telepathy-glib/telepathy-glib.h> - -G_BEGIN_DECLS - -typedef enum -{ - STREAM_SIG_STATE_NEW, - STREAM_SIG_STATE_SENT, - STREAM_SIG_STATE_ACKNOWLEDGED, - STREAM_SIG_STATE_REMOVING -} StreamSignallingState; - -typedef guint32 CombinedStreamDirection; - -typedef struct _HazeMediaStream HazeMediaStream; -typedef struct _HazeMediaStreamClass HazeMediaStreamClass; -typedef struct _HazeMediaStreamPrivate HazeMediaStreamPrivate; - -struct _HazeMediaStreamClass { - GObjectClass parent_class; - - TpDBusPropertiesMixinClass dbus_props_class; -}; - -struct _HazeMediaStream { - GObject parent; - - gchar *name; - gchar *peer; - - TpMediaStreamState connection_state; - - CombinedStreamDirection combined_direction; - gboolean playing; - - HazeMediaStreamPrivate *priv; -}; - -GType haze_media_stream_get_type (void); - -/* TYPE MACROS */ -#define HAZE_TYPE_MEDIA_STREAM \ - (haze_media_stream_get_type ()) -#define HAZE_MEDIA_STREAM(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), HAZE_TYPE_MEDIA_STREAM, \ - HazeMediaStream)) -#define HAZE_MEDIA_STREAM_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), HAZE_TYPE_MEDIA_STREAM, \ - HazeMediaStreamClass)) -#define HAZE_IS_MEDIA_STREAM(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj), HAZE_TYPE_MEDIA_STREAM)) -#define HAZE_IS_MEDIA_STREAM_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass), HAZE_TYPE_MEDIA_STREAM)) -#define HAZE_MEDIA_STREAM_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), HAZE_TYPE_MEDIA_STREAM, \ - HazeMediaStreamClass)) - -#define COMBINED_DIRECTION_GET_DIRECTION(d) \ - ((TpMediaStreamDirection) ((d) & TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)) -#define COMBINED_DIRECTION_GET_PENDING_SEND(d) \ - ((TpMediaStreamPendingSend) ((d) >> 2)) -#define MAKE_COMBINED_DIRECTION(d, p) \ - ((CombinedStreamDirection) ((d) | ((p) << 2))) - -gboolean haze_media_stream_error (HazeMediaStream *self, guint err_no, - const gchar *message, GError **error); - -void haze_media_stream_close (HazeMediaStream *close); -void haze_media_stream_hold (HazeMediaStream *stream, gboolean hold); -gboolean haze_media_stream_change_direction (HazeMediaStream *stream, - guint requested_dir, GError **error); -void haze_media_stream_accept_pending_local_send (HazeMediaStream *stream); - -HazeMediaStream *haze_media_stream_new (const gchar *object_path, - TpDBusDaemon *dbus_daemon, - PurpleMedia *media, - const gchar *name, - const gchar *peer, - guint media_type, - guint id, - gboolean created_locally, - const gchar *nat_traversal, - const GPtrArray *relay_info, - gboolean local_hold); -TpMediaStreamType haze_media_stream_get_media_type (HazeMediaStream *self); - -GList *haze_media_stream_get_local_candidates (HazeMediaStream *self); -GList *haze_media_stream_get_codecs (HazeMediaStream *self); -void haze_media_stream_add_remote_candidates (HazeMediaStream *self, - GList *remote_candidates); -void haze_media_stream_set_remote_codecs (HazeMediaStream *self, - GList *remote_codecs); -void haze_media_stream_add_stun_server (HazeMediaStream *self, - const gchar *stun_ip, - guint stun_port); - -G_END_DECLS - -#endif /* #ifndef __HAZE_MEDIA_STREAM_H__*/ diff --git a/src/notify.c b/src/notify.c index 7eb0185..9485b6b 100644 --- a/src/notify.c +++ b/src/notify.c @@ -18,7 +18,9 @@ * */ +#include <config.h> #include "notify.h" + #include "connection-mail.h" #include "debug.h" diff --git a/src/protocol.c b/src/protocol.c index cc01911..6e19d5f 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -32,6 +32,7 @@ #include <telepathy-glib/telepathy-glib-dbus.h> #include "connection.h" +#include "connection-avatars.h" #include "debug.h" G_DEFINE_TYPE (HazeProtocol, haze_protocol, TP_TYPE_BASE_PROTOCOL) @@ -89,6 +90,11 @@ static const HazeParameterMapping irc_mappings[] = { { NULL, NULL } }; +static const HazeParameterMapping sametime_mappings[] = { + { "usersplit1", "server" }, + { NULL, NULL } +}; + static const HazeParameterMapping jabber_mappings[] = { { "connect_server", "server" }, /* usersplit1 => domain is deliberately not in this map, because @@ -201,13 +207,15 @@ static const KnownProtocolInfo known_protocol_info[] = { { "local-xmpp", "prpl-bonjour", bonjour_mappings, "" /* ? */ }, { "msn", "prpl-msn", NULL, "x-msn" }, { "qq", "prpl-qq", NULL, "x-qq" /* ? */ }, - { "sametime", "prpl-meanwhile", NULL, "x-sametime" /* ? */ }, + { "sametime", "prpl-meanwhile", sametime_mappings, "x-sametime" /* ? */ }, { "sipe", "prpl-sipe", sipe_mappings, "" /* ? */ }, { "yahoo", "prpl-yahoo", yahoo_mappings, "x-yahoo" }, { "yahoojp", "prpl-yahoojp", yahoo_mappings, "x-yahoo" /* ? */ }, { "zephyr", "prpl-zephyr", encoding_to_charset, "x-zephyr" /* ? */ }, { "mxit", "prpl-loubserp-mxit", NULL, "x-mxit" /* ? */ }, { "sip", "prpl-simple", NULL, "x-sip" }, + { "skype-dbus", "prpl-bigbrownchunx-skype-dbus", NULL, "x-skype" }, + { "skype-x11", "prpl-bigbrownchunx-skype", NULL, "x-skype" }, { NULL, NULL, NULL } }; @@ -876,10 +884,32 @@ haze_protocol_identify_account (TpBaseProtocol *base, return ret; } -static GStrv -haze_protocol_get_interfaces (TpBaseProtocol *base) +static GPtrArray * +haze_protocol_get_interfaces_array (TpBaseProtocol *base) { - return g_new0 (gchar *, 1); + HazeProtocol *self = HAZE_PROTOCOL (base); + GPtrArray *interfaces; + GPtrArray *tmp; + guint i; + + interfaces = TP_BASE_PROTOCOL_CLASS ( + haze_protocol_parent_class)->get_interfaces_array (base); + + /* Claim to implement Avatars only if we support avatars for this + * protocol. */ + tmp = haze_connection_dup_implemented_interfaces (self->priv->prpl_info); + for (i = 0; i < tmp->len; i++) + { + if (!tp_strdiff (g_ptr_array_index (tmp, i), + TP_IFACE_CONNECTION_INTERFACE_AVATARS)) + { + g_ptr_array_add (interfaces, TP_IFACE_PROTOCOL_INTERFACE_AVATARS); + break; + } + } + g_ptr_array_unref (tmp); + + return interfaces; } static void @@ -894,16 +924,28 @@ haze_protocol_get_connection_details (TpBaseProtocol *base, if (connection_interfaces != NULL) { - *connection_interfaces = g_strdupv ( - (gchar **) haze_connection_get_implemented_interfaces ()); + GPtrArray *tmp, *ifaces; + guint i; + + tmp = haze_connection_dup_implemented_interfaces ( + self->priv->prpl_info); + + /* @connection_interfaces takes a NULL terminated (transfer full) + * gchar ** so we have to dup each string and append NULL. */ + ifaces = g_ptr_array_new (); + + for (i = 0; i < tmp->len; i++) + g_ptr_array_add (ifaces, g_strdup (g_ptr_array_index (tmp, i))); + + g_ptr_array_add (ifaces, NULL); + + *connection_interfaces = (gchar **) g_ptr_array_free (ifaces, FALSE); + g_ptr_array_unref (tmp); } if (channel_manager_types != NULL) { GType types[] = { HAZE_TYPE_IM_CHANNEL_FACTORY, -#ifdef ENABLE_MEDIA - HAZE_TYPE_MEDIA_MANAGER, -#endif G_TYPE_INVALID }; *channel_manager_types = g_memdup (types, sizeof (types)); @@ -954,6 +996,41 @@ haze_protocol_dup_authentication_types (TpBaseProtocol *base) } static void +haze_protocol_get_avatar_details (TpBaseProtocol *base, + GStrv *supported_mime_types, + guint *min_height, + guint *min_width, + guint *rec_height, + guint *rec_width, + guint *max_height, + guint *max_width, + guint *max_bytes) +{ + HazeProtocol *self = HAZE_PROTOCOL (base); + PurpleBuddyIconSpec *icon_spec; + + icon_spec = &(self->priv->prpl_info->icon_spec); + + if (icon_spec->format == NULL) + { + /* We don't support avatar for this protocol */ + *supported_mime_types = NULL; + *min_height = 0; + *min_width = 0; + *rec_height = 0; + *rec_width = 0; + *max_height = 0; + *max_width = 0; + *max_bytes = 0; + return; + } + + haze_connection_get_icon_spec_requirements (icon_spec, supported_mime_types, + min_height, min_width, rec_height, rec_width, max_height, max_width, + max_bytes); +} + +static void haze_protocol_class_init (HazeProtocolClass *cls) { GObjectClass *object_class = (GObjectClass *) cls; @@ -964,10 +1041,11 @@ haze_protocol_class_init (HazeProtocolClass *cls) base_class->new_connection = haze_protocol_new_connection; base_class->normalize_contact = haze_protocol_normalize_contact; base_class->identify_account = haze_protocol_identify_account; - base_class->get_interfaces = haze_protocol_get_interfaces; + base_class->get_interfaces_array = haze_protocol_get_interfaces_array; base_class->get_connection_details = haze_protocol_get_connection_details; base_class->dup_authentication_types = haze_protocol_dup_authentication_types; + base_class->get_avatar_details = haze_protocol_get_avatar_details; g_type_class_add_private (cls, sizeof (HazeProtocolPrivate)); object_class->get_property = haze_protocol_get_property; diff --git a/src/request.c b/src/request.c index 408678b..8aa7bc9 100644 --- a/src/request.c +++ b/src/request.c @@ -18,6 +18,8 @@ * */ +#include "config.h" + #include <glib-object.h> #include <libpurple/account.h> @@ -25,7 +27,9 @@ #include "debug.h" #include "request.h" +#include "connection.h" +#ifdef ENABLE_LEAKY_REQUEST_STUBS static gpointer haze_request_input (const char *title, const char *primary, @@ -95,7 +99,73 @@ haze_request_action (const char *title, return NULL; } +#endif + +struct fields_data { + PurpleAccount *account; + PurpleRequestFields *fields; + PurpleRequestField *password; + PurpleRequestFieldsCb ok_cb; + PurpleRequestFieldsCb cancel_cb; + void *user_data; +}; + +static void +haze_close_request (PurpleRequestType type, + void *ui_handle) +{ + struct fields_data *fd = ui_handle; + + haze_connection_cancel_password_request (fd->account); + purple_request_fields_destroy (fd->fields); + g_slice_free (struct fields_data, fd); +} + +void +haze_request_password_cb (gpointer user_data, + const gchar *password) +{ + struct fields_data *fd = user_data; + + if (password) + { + purple_request_field_string_set_value (fd->password, password); + if (fd->ok_cb) + { + (fd->ok_cb) (fd->user_data, fd->fields); + } + } + else + { + if (fd->cancel_cb) + { + (fd->cancel_cb) (fd->user_data, fd->fields); + } + } + purple_request_close (PURPLE_REQUEST_FIELDS, fd); +} + +static gboolean +haze_request_fields_destroy (gpointer user_data) +{ + struct fields_data *fd = user_data; + + if (fd->cancel_cb) + { + (fd->cancel_cb) (fd->user_data, fd->fields); + } + + purple_request_close (PURPLE_REQUEST_FIELDS, user_data); + + return FALSE; +} + +/* + * We must support purple_account_request_password() which boils down + * to purple_request_fields() with certain parameters. I'm not sure + * if this the best way of doing this, but it works. + */ static gpointer haze_request_fields (const char *title, const char *primary, @@ -110,14 +180,41 @@ haze_request_fields (const char *title, PurpleConversation *conv, void *user_data) { - DEBUG ("ignoring request:"); - DEBUG (" title: %s", (title ? title : "(null)")); - DEBUG (" primary: %s", (primary ? primary : "(null)")); - DEBUG (" secondary: %s", (secondary ? secondary : "(null)")); + struct fields_data *fd = g_slice_new0 (struct fields_data); - return NULL; + /* it is our responsibility to destroy this data */ + fd->account = account; + fd->fields = fields; + fd->cancel_cb = (PurpleRequestFieldsCb) cancel_cb; + fd->user_data = user_data; + + if (purple_request_fields_exists (fields, "password") && + purple_request_fields_exists (fields, "remember")) + { + + DEBUG ("triggering password request"); + + fd->password = purple_request_fields_get_field (fields, "password"); + fd->ok_cb = (PurpleRequestFieldsCb) ok_cb; + + haze_connection_request_password (account, fd); + + } + else + { + DEBUG ("ignoring request:"); + DEBUG (" title: %s", (title ? title : "(null)")); + DEBUG (" primary: %s", (primary ? primary : "(null)")); + DEBUG (" secondary: %s", (secondary ? secondary : "(null)")); + + /* Avoid leaking of "fields" and "user_data" */ + g_idle_add (haze_request_fields_destroy, fd); + } + + return fd; } +#ifdef ENABLE_LEAKY_REQUEST_STUBS static gpointer haze_request_file (const char *title, const char *filename, @@ -152,20 +249,19 @@ haze_request_folder (const char *title, return NULL; } - - -/* - void (*close_request)(PurpleRequestType type, void *ui_handle); -*/ +#endif static PurpleRequestUiOps request_uiops = { +#ifdef ENABLE_LEAKY_REQUEST_STUBS .request_input = haze_request_input, .request_choice = haze_request_choice, .request_action = haze_request_action, - .request_fields = haze_request_fields, .request_file = haze_request_file, - .request_folder = haze_request_folder + .request_folder = haze_request_folder, +#endif + .request_fields = haze_request_fields, + .close_request = haze_close_request }; PurpleRequestUiOps * diff --git a/src/request.h b/src/request.h index d896cc6..cc572d6 100644 --- a/src/request.h +++ b/src/request.h @@ -20,4 +20,7 @@ #include <libpurple/request.h> +void haze_request_password_cb (gpointer user_data, + const gchar *password); + PurpleRequestUiOps *haze_request_get_ui_ops (void); @@ -7,6 +7,7 @@ * notice and this notice are preserved. */ +#include <config.h> #include "util.h" #include <glib/gstdio.h> diff --git a/tests/Makefile.am b/tests/Makefile.am index 2841237..af9cb85 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -4,31 +4,4 @@ if WANT_TWISTED_TESTS SUBDIRS += twisted endif -%.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) -im.telepathy1.ConnectionManager.%.service: %.service.in - $(AM_V_GEN)sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" \ - -e "s|[@]abs_top_srcdir[@]|@abs_top_srcdir@|g" $< > $@ - -# D-Bus service file for testing -service_in_files = haze.service.in -service_files = im.telepathy1.ConnectionManager.haze.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) - -EXTRA_DIST = \ - $(service_in_files) \ - $(conf_in_files) \ - exec-with-log.sh - -CLEANFILES = \ - $(BUILT_SOURCES) \ - haze-testing.log +CLEANFILES = haze-testing.log diff --git a/tests/haze.service.in b/tests/haze.service.in deleted file mode 100644 index c815d4b..0000000 --- a/tests/haze.service.in +++ /dev/null @@ -1,3 +0,0 @@ -[D-BUS Service] -Name=im.telepathy1.ConnectionManager.haze -Exec=@abs_top_srcdir@/tests/exec-with-log.sh @abs_top_srcdir@ @abs_top_builddir@ diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am index 9bfc33f..f932229 100644 --- a/tests/twisted/Makefile.am +++ b/tests/twisted/Makefile.am @@ -1,3 +1,5 @@ +SUBDIRS = tools + TWISTED_TESTS = \ avatar-requirements.py \ simple-caps.py \ @@ -11,6 +13,8 @@ TWISTED_TESTS = \ roster/publish.py \ roster/removed-from-rp-subscribe.py \ roster/subscribe.py \ + sasl/close.py \ + sasl/telepathy-password.py \ text/destroy.py \ text/ensure.py \ text/initiate-requestotron.py \ @@ -20,39 +24,55 @@ TWISTED_TESTS = \ text/test-text.py -TESTS = - -TESTS_ENVIRONMENT = \ - PYTHONPATH=@abs_top_srcdir@/tests/twisted:@abs_top_builddir@/tests/twisted - check-local: check-twisted -check-twisted: - rm -f ../haze-testing.log - sh $(top_srcdir)/tools/with-session-bus.sh \ - --config-file=$(top_builddir)/tests/tmp-session-bus.conf \ - -- $(MAKE) check-TESTS \ - TESTS="$(TWISTED_TESTS)" \ - TESTS_ENVIRONMENT="$(TESTS_ENVIRONMENT) $(TEST_PYTHON)" +CHECK_TWISTED_SLEEP=0 + +check-twisted: $(BUILT_SOURCES) + $(MAKE) -C tools + if test "x$(CHECK_TWISTED_SLEEP)" = x0; then \ + haze_test_sleep= ; \ + else \ + haze_test_sleep=--sleep=$(CHECK_TWISTED_SLEEP); \ + fi; \ + HAZE_TEST_UNINSTALLED=1 \ + HAZE_ABS_TOP_SRCDIR=@abs_top_srcdir@ \ + HAZE_ABS_TOP_BUILDDIR=@abs_top_builddir@ \ + HAZE_TEST_SLEEP=$$haze_test_sleep \ + ./run-test.sh "$(TWISTED_TESTS)" EXTRA_DIST = \ $(TWISTED_TESTS) \ constants.py \ + gabbletest.py \ hazetest.py \ + run-test.sh.in \ + sasl/saslutil.py \ servicetest.py \ ns.py -if MEDIA_ENABLED -MEDIA_ENABLED_PYBOOL = True -else -MEDIA_ENABLED_PYBOOL = False -endif +haze-twisted-tests.list: Makefile + $(AM_V_GEN)echo $(TWISTED_TESTS) > $@ -config.py: Makefile - $(AM_V_GEN) { \ - echo "MEDIA_ENABLED = $(MEDIA_ENABLED_PYBOOL)"; \ - } > $@ +BUILT_SOURCES = \ + haze-twisted-tests.list \ + run-test.sh \ + $(NULL) -BUILT_SOURCES = config.py +# We don't really use hazetestsdir yet - we only support uninstalled testing +# so far - but I'm substituting it to keep the script more similar to Gabble's. +# ${pkglibexecdir}/tests is what GNOME's InstalledTests goal recommends. +run-test.sh: run-test.sh.in Makefile + $(AM_V_GEN)sed \ + -e 's![@]hazetestsdir[@]!${pkglibexecdir}/tests!' \ + -e 's![@]TEST_PYTHON[@]!$(TEST_PYTHON)!' \ + < $< > $@.tmp && \ + chmod +x $@.tmp && \ + mv $@.tmp $@ -CLEANFILES = *.pyc */*.pyc config.py +CLEANFILES = \ + $(BUILT_SOURCES) \ + haze-[1-9]*.log \ + *.pyc \ + */*.pyc \ + $(NULL) diff --git a/tests/twisted/avatar-requirements.py b/tests/twisted/avatar-requirements.py index 1515bd6..83d155e 100644 --- a/tests/twisted/avatar-requirements.py +++ b/tests/twisted/avatar-requirements.py @@ -50,4 +50,4 @@ def test(q, bus, conn, stream): assert rech == 0, rech if __name__ == '__main__': - exec_test(test) + exec_test(test, do_connect=False) diff --git a/tests/twisted/cm/protocols.py b/tests/twisted/cm/protocols.py index 9736021..25eca0a 100644 --- a/tests/twisted/cm/protocols.py +++ b/tests/twisted/cm/protocols.py @@ -24,6 +24,14 @@ def test(q, bus, conn, stream): protocol_iface = dbus.Interface(protocol, cs.PROTOCOL) protocol_props = dbus.Interface(protocol, cs.PROPERTIES_IFACE) flat_props = protocol_props.GetAll(cs.PROTOCOL) + protocol_avatar_props = protocol_props.GetAll(cs.PROTOCOL_IFACE_AVATARS) + + # Protocol is supposed to implement Interface.Avatars iff the + # connection implements Avatars as well. + if cs.CONN_IFACE_AVATARS in flat_props['ConnectionInterfaces']: + assertContains(cs.PROTOCOL_IFACE_AVATARS, props[cs.PROTOCOL + '.Interfaces']) + else: + assertDoesNotContain(cs.PROTOCOL_IFACE_AVATARS, props[cs.PROTOCOL + '.Interfaces']) parameters = props[cs.PROTOCOL + '.Parameters'] assertEquals(parameters, flat_props['Parameters']) @@ -90,6 +98,20 @@ def test(q, bus, conn, stream): protocol_iface.IdentifyAccount({ 'account': 'smcv', 'server': 'irc.debian.org'})) + + assertDoesNotContain(cs.CONN_IFACE_AVATARS, flat_props['ConnectionInterfaces']) + assertDoesNotContain(cs.CONN_IFACE_CONTACT_BLOCKING, flat_props['ConnectionInterfaces']) + assertDoesNotContain(cs.CONN_IFACE_MAIL_NOTIFICATION, flat_props['ConnectionInterfaces']) + + # Avatar not supported + assertEquals(0, protocol_avatar_props['MaximumAvatarBytes']) + assertEquals(0, protocol_avatar_props['MaximumAvatarHeight']) + assertEquals(0, protocol_avatar_props['MaximumAvatarWidth']) + assertEquals(0, protocol_avatar_props['MinimumAvatarHeight']) + assertEquals(0, protocol_avatar_props['MinimumAvatarWidth']) + assertEquals(0, protocol_avatar_props['RecommendedAvatarHeight']) + assertEquals(0, protocol_avatar_props['RecommendedAvatarWidth']) + assertEquals([], protocol_avatar_props['SupportedAvatarMIMETypes']) elif name == 'myspace': assertEquals('x-myspace', flat_props['VCardField']) assertEquals('im-myspace', flat_props['Icon']) @@ -135,6 +157,20 @@ def test(q, bus, conn, stream): 'embrace-and-extend': r'WORKGROUP\Bill', 'password': 'letmein'}) q.expect('dbus-error', name=cs.INVALID_ARGUMENT) + + assertContains(cs.CONN_IFACE_AVATARS, flat_props['ConnectionInterfaces']) + assertContains(cs.CONN_IFACE_CONTACT_BLOCKING, flat_props['ConnectionInterfaces']) + assertContains(cs.CONN_IFACE_MAIL_NOTIFICATION, flat_props['ConnectionInterfaces']) + + # libpurple currently says there's no max size + assertEquals(0, protocol_avatar_props['MaximumAvatarBytes']) + assertEquals(96, protocol_avatar_props['MaximumAvatarHeight']) + assertEquals(96, protocol_avatar_props['MaximumAvatarWidth']) + assertEquals(32, protocol_avatar_props['MinimumAvatarHeight']) + assertEquals(32, protocol_avatar_props['MinimumAvatarWidth']) + assertEquals(0, protocol_avatar_props['RecommendedAvatarHeight']) + assertEquals(0, protocol_avatar_props['RecommendedAvatarWidth']) + assertEquals(['image/png'], protocol_avatar_props['SupportedAvatarMIMETypes']) elif name == 'qq': assertEquals('x-qq', flat_props['VCardField']) assertEquals('im-qq', flat_props['Icon']) @@ -177,11 +213,6 @@ def test(q, bus, conn, stream): 'login': r'WORKGROUP\Bill', 'password': 'letmein'})) - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[1, 1]) - conn.Disconnect() - q.expect('dbus-signal', signal='StatusChanged', args=[2, 1]) - if __name__ == '__main__': exec_test(test) diff --git a/tests/twisted/connect/fail.py b/tests/twisted/connect/fail.py index db24cdb..a45b2b2 100644 --- a/tests/twisted/connect/fail.py +++ b/tests/twisted/connect/fail.py @@ -24,5 +24,5 @@ def test(q, bus, conn, stream): args=[cs.CONN_STATUS_DISCONNECTED, cs.CSR_NETWORK_ERROR]) if __name__ == '__main__': - exec_test(test, {'port': dbus.UInt32(14243)}) + exec_test(test, {'port': dbus.UInt32(14243)}, do_connect=False) diff --git a/tests/twisted/connect/success.py b/tests/twisted/connect/success.py index d9cdb4e..c16f049 100644 --- a/tests/twisted/connect/success.py +++ b/tests/twisted/connect/success.py @@ -4,23 +4,20 @@ Test connecting to a server. """ from hazetest import exec_test +import constants as cs def test(q, bus, conn, stream): conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[1, 1]) + q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED]) q.expect('stream-authenticated') # FIXME: unlike Gabble, Haze does not signal a presence update to # available during connect - #q.expect('dbus-signal', signal='PresenceUpdate', - # args=[{1L: (0L, {u'available': {}})}]) + #q.expect('dbus-signal', signal='PresencesChanged', + # args=[{1L: (cs.PRESENCE_AVAILABLE, 'available', '')}]) - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - - conn.Disconnect() - q.expect('dbus-signal', signal='StatusChanged', args=[2, 1]) - return True + q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) if __name__ == '__main__': - exec_test(test) + exec_test(test, do_connect=False) diff --git a/tests/twisted/connect/twice-to-same-account.py b/tests/twisted/connect/twice-to-same-account.py index fb2c25e..8fe3db2 100644 --- a/tests/twisted/connect/twice-to-same-account.py +++ b/tests/twisted/connect/twice-to-same-account.py @@ -13,19 +13,6 @@ from servicetest import ( import constants as cs def test(q, bus, conn, stream): - conn.Connect() - q.expect_many( - EventPattern('dbus-signal', signal='StatusChanged', args=[1, 1]), - EventPattern('stream-authenticated'), - ) - - # FIXME: unlike Gabble, Haze does not signal a presence update to - # available during connect - #q.expect('dbus-signal', signal='PresenceUpdate', - # args=[{1L: (0L, {u'available': {}})}]) - - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - haze = bus.get_object( tp_name_prefix + '.ConnectionManager.haze', tp_path_prefix + '/ConnectionManager/haze') diff --git a/tests/twisted/constants.py b/tests/twisted/constants.py index 3f4de31..44b7b3d 100644 --- a/tests/twisted/constants.py +++ b/tests/twisted/constants.py @@ -22,23 +22,26 @@ CHANNEL_IFACE_DESTROYABLE = CHANNEL + ".Interface.Destroyable1" CHANNEL_IFACE_DTMF = CHANNEL + ".Interface.DTMF1" CHANNEL_IFACE_GROUP = CHANNEL + ".Interface.Group1" CHANNEL_IFACE_HOLD = CHANNEL + ".Interface.Hold1" -CHANNEL_IFACE_MEDIA_SIGNALLING = CHANNEL + ".Interface.MediaSignalling" CHANNEL_IFACE_PASSWORD = CHANNEL + ".Interface.Password1" CHANNEL_IFACE_TUBE = CHANNEL + ".Interface.Tube1" CHANNEL_IFACE_SASL_AUTH = CHANNEL + ".Interface.SASLAuthentication1" CHANNEL_IFACE_CONFERENCE = CHANNEL + '.Interface.Conference1' +CHANNEL_IFACE_ROOM = CHANNEL + '.Interface.Room1' +CHANNEL_IFACE_ROOM_CONFIG = CHANNEL + '.Interface.RoomConfig1' +CHANNEL_IFACE_SUBJECT = CHANNEL + '.Interface.Subject1' +CHANNEL_IFACE_FILE_TRANSFER_METADATA = CHANNEL + '.Interface.FileTransfer.Metadata' -CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call.DRAFT" +CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call1" CHANNEL_TYPE_CONTACT_LIST = CHANNEL + ".Type.ContactList1" CHANNEL_TYPE_CONTACT_SEARCH = CHANNEL + ".Type.ContactSearch1" CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text" CHANNEL_TYPE_TUBES = CHANNEL + ".Type.Tubes" CHANNEL_TYPE_STREAM_TUBE = CHANNEL + ".Type.StreamTube1" CHANNEL_TYPE_DBUS_TUBE = CHANNEL + ".Type.DBusTube1" -CHANNEL_TYPE_STREAMED_MEDIA = CHANNEL + ".Type.StreamedMedia" CHANNEL_TYPE_FILE_TRANSFER = CHANNEL + ".Type.FileTransfer1" +CHANNEL_TYPE_ROOM_LIST = CHANNEL + ".Type.RoomList" CHANNEL_TYPE_SERVER_AUTHENTICATION = \ - CHANNEL + ".Type.ServerAuthentication.DRAFT" + CHANNEL + ".Type.ServerAuthentication1" CHANNEL_TYPE_SERVER_TLS_CONNECTION = \ CHANNEL + ".Type.ServerTLSConnection1" @@ -56,49 +59,96 @@ 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 = PREFIX + '.Call.Content.DRAFT' -CALL_CONTENT_IFACE_MEDIA = PREFIX + '.Call.Content.Interface.Media.DRAFT' +CALL_CONTENT = PREFIX + '.Call1.Content' +CALL_CONTENT_IFACE_MEDIA = CALL_CONTENT + '.Interface.Media1' +CALL_CONTENT_IFACE_DTMF = CALL_CONTENT + '.Interface.DTMF1' -CALL_CONTENT_CODECOFFER = \ - PREFIX + '.Call.Content.CodecOffer.DRAFT' +CALL_CONTENT_MEDIADESCRIPTION = CALL_CONTENT + '.MediaDescription1' -CALL_STREAM = PREFIX + '.Call.Stream.DRAFT' -CALL_STREAM_IFACE_MEDIA = \ - PREFIX + '.Call.Stream.Interface.Media.DRAFT' +CALL_STREAM = PREFIX + '.Call1.Stream' +CALL_STREAM_IFACE_MEDIA = CALL_STREAM + '.Interface.Media' -CALL_STREAM_ENDPOINT = PREFIX + '.Call.Stream.Endpoint.DRAFT' +CALL_STREAM_ENDPOINT = CALL_STREAM + '.Endpoint' CALL_MEDIA_TYPE_AUDIO = 0 CALL_MEDIA_TYPE_VIDEO = 1 -CALL_STREAM_TRANSPORT_RAW_UDP = 0 -CALL_STREAM_TRANSPORT_ICE = 1 -CALL_STREAM_TRANSPORT_GOOGLE = 2 +CALL_CONTENT_PACKETIZATION_RTP = 0 +CALL_CONTENT_PACKETIZATION_RAW = 1 +CALL_CONTENT_PACKETIZATION_MSN_WEBCAM = 2 + +CALL_STREAM_TRANSPORT_UNKNOWN = 0 +CALL_STREAM_TRANSPORT_RAW_UDP = 1 +CALL_STREAM_TRANSPORT_ICE = 2 +CALL_STREAM_TRANSPORT_GTALK_P2P = 3 +CALL_STREAM_TRANSPORT_WLM_2009 = 4 +CALL_STREAM_TRANSPORT_SHM = 5 +CALL_STREAM_TRANSPORT_MULTICAST = 6 CALL_STATE_UNKNOWN = 0 CALL_STATE_PENDING_INITIATOR = 1 -CALL_STATE_PENDING_RECEIVER = 2 -CALL_STATE_ACCEPTED = 3 -CALL_STATE_ENDED = 4 +CALL_STATE_INITIALISING = 2 +CALL_STATE_INITIALISED = 3 +CALL_STATE_ACCEPTED = 4 +CALL_STATE_ACTIVE = 5 +CALL_STATE_ENDED = 6 + +CALL_FLAG_LOCALLY_HELD = 1 +CALL_FLAG_LOCALLY_RINGING = 2 +CALL_FLAG_LOCALLY_QUEUED = 4 +CALL_FLAG_FORWARDED = 8 +CALL_FLAG_CLEARING = 16 CALL_MEMBER_FLAG_RINGING = 1 CALL_MEMBER_FLAG_HELD = 2 CALL_DISPOSITION_NONE = 0 -CALL_DISPOSITION_EARLY_MEDIA = 1 -CALL_DISPOSITION_INITIAL = 2 +CALL_DISPOSITION_INITIAL = 1 CALL_SENDING_STATE_NONE = 0 CALL_SENDING_STATE_PENDING_SEND = 1 CALL_SENDING_STATE_SENDING = 2 +CALL_SENDING_STATE_PENDING_STOP_SENDING = 3 + +CALL_STREAM_FLOW_STATE_STOPPED = 0 +CALL_STREAM_FLOW_STATE_PENDING_START = 1 +CALL_STREAM_FLOW_STATE_PENDING_STOP = 2 +CALL_STREAM_FLOW_STATE_STARTED = 3 + +CALL_STREAM_ENDPOINT_STATE_CONNECTING = 0 +CALL_STREAM_ENDPOINT_STATE_PROVISIONALLY_CONNECTED = 1 +CALL_STREAM_ENDPOINT_STATE_FULLY_CONNECTED = 2 +CALL_STREAM_ENDPOINT_STATE_EXHAUSTED_CANDIDATES = 3 +CALL_STREAM_ENDPOINT_STATE_FAILED = 4 + +CALL_STREAM_CANDIDATE_TYPE_HOST = 1 +CALL_STREAM_CANDIDATE_TYPE_SERVER_REFLEXIVE = 2 +CALL_STREAM_CANDIDATE_TYPE_RELAY = 4 + +CALL_STATE_CHANGE_REASON_UNKNOWN = 0 +CALL_STATE_CHANGE_REASON_PROGRESS_MADE = 1 +CALL_STATE_CHANGE_REASON_USER_REQUESTED = 2 +CALL_STATE_CHANGE_REASON_FORWARDED = 3 +CALL_STATE_CHANGE_REASON_REJECTED = 4 +CALL_STATE_CHANGE_REASON_NO_ANSWER = 5 +CALL_STATE_CHANGE_REASON_INVALID_CONTACT = 6 +CALL_STATE_CHANGE_REASON_PERMISSION_DENIED = 7 +CALL_STATE_CHANGE_REASON_BUSY = 8 +CALL_STATE_CHANGE_REASON_INTERNAL_ERROR = 9 +CALL_STATE_CHANGE_REASON_SERVICE_ERROR = 10 +CALL_STATE_CHANGE_REASON_NETWORK_ERROR = 11 +CALL_STATE_CHANGE_REASON_MEDIA_ERROR = 12 +CALL_STATE_CHANGE_REASON_CONNECTIVITY_ERROR = 13 + +CALL_STREAM_COMPONENT_UNKNOWN = 0 +CALL_STREAM_COMPONENT_DATA = 1 +CALL_STREAM_COMPONENT_CONTROL = 2 SUBSCRIPTION_STATE_UNKNOWN = 0 SUBSCRIPTION_STATE_NO = 1 @@ -114,12 +164,10 @@ CONTACT_LIST_STATE_SUCCESS = 3 CONN = PREFIX + ".Connection" CONN_IFACE_AVATARS = CONN + '.Interface.Avatars1' CONN_IFACE_ALIASING = CONN + '.Interface.Aliasing1' -CONN_IFACE_CAPS = CONN + '.Interface.Capabilities1' CONN_IFACE_CONTACTS = CONN + '.Interface.Contacts1' CONN_IFACE_CONTACT_CAPS = CONN + '.Interface.ContactCapabilities1' CONN_IFACE_CONTACT_INFO = CONN + ".Interface.ContactInfo1" CONN_IFACE_PRESENCE = CONN + '.Interface.Presence1' -CONN_IFACE_SIMPLE_PRESENCE = CONN + '.Interface.SimplePresence1' CONN_IFACE_REQUESTS = CONN + '.Interface.Requests' CONN_IFACE_LOCATION = CONN + '.Interface.Location1' CONN_IFACE_GABBLE_DECLOAK = CONN + '.Interface.Gabble.Decloak' @@ -128,8 +176,15 @@ CONN_IFACE_CONTACT_LIST = CONN + '.Interface.ContactList1' CONN_IFACE_CONTACT_GROUPS = CONN + '.Interface.ContactGroups1' CONN_IFACE_CLIENT_TYPES = CONN + '.Interface.ClientTypes1' CONN_IFACE_POWER_SAVING = CONN + '.Interface.PowerSaving1' +CONN_IFACE_CONTACT_BLOCKING = CONN + '.Interface.ContactBlocking1' +CONN_IFACE_ADDRESSING = CONN + '.Interface.Addressing1' +ATTR_CONTACT_ID = CONN + '/contact-id' ATTR_CONTACT_CAPABILITIES = CONN_IFACE_CONTACT_CAPS + '/capabilities' +ATTR_PRESENCE = CONN_IFACE_PRESENCE + '/presence' +ATTR_SUBSCRIBE = CONN_IFACE_CONTACT_LIST + '/subscribe' +ATTR_PUBLISH = CONN_IFACE_CONTACT_LIST + '/publish' +ATTR_GROUPS = CONN_IFACE_CONTACT_GROUPS + '/groups' STREAM_HANDLER = PREFIX + '.Media.StreamHandler' @@ -154,6 +209,9 @@ NOT_YET = ERROR + '.NotYet' INVALID_HANDLE = ERROR + '.InvalidHandle' CERT_UNTRUSTED = ERROR + '.Cert.Untrusted' SERVICE_BUSY = ERROR + '.ServiceBusy' +SERVICE_CONFUSED = ERROR + '.ServiceConfused' + +BANNED = ERROR + '.Channel.Banned' UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' @@ -187,6 +245,13 @@ TUBE_CHANNEL_STATE_NOT_OFFERED = 3 MEDIA_STREAM_TYPE_AUDIO = 0 MEDIA_STREAM_TYPE_VIDEO = 1 +MEDIA_STREAM_BASE_PROTO_UDP = 0 +MEDIA_STREAM_BASE_PROTO_TCP = 1 + +MEDIA_STREAM_TRANSPORT_TYPE_LOCAL = 0 +MEDIA_STREAM_TRANSPORT_TYPE_DERIVED = 1 +MEDIA_STREAM_TRANSPORT_TYPE_RELAY = 2 + SOCKET_ADDRESS_TYPE_UNIX = 0 SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX = 1 SOCKET_ADDRESS_TYPE_IPV4 = 2 @@ -256,6 +321,9 @@ 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 @@ -293,11 +361,6 @@ 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 @@ -347,11 +410,8 @@ PRESENCE_ERROR = 8 CONTACT_INFO_FLAG_CAN_SET = 1 CONTACT_INFO_FLAG_PUSH = 2 -CONTACT_INFO_FIELD_FLAG_PARAMETERS_MANDATORY = 1 - -# Channel_Type_ServerAuthentication -AUTH_TYPE_SASL = 0 -AUTH_TYPE_CAPTCHA = 1 +CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT = 1 +CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME = 2 # Channel_Interface_SaslAuthentication SASL_STATUS_NOT_STARTED = 0 @@ -366,12 +426,20 @@ SASL_ABORT_REASON_INVALID_CHALLENGE = 0 SASL_ABORT_REASON_USER_ABORT = 1 AUTH_METHOD = CHANNEL_TYPE_SERVER_AUTHENTICATION + ".AuthenticationMethod" -AUTH_INFO = CHANNEL_TYPE_SERVER_AUTHENTICATION + ".AuthenticationInformation" 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" +TLS_REFERENCE_IDENTITIES = \ + CHANNEL_TYPE_SERVER_TLS_CONNECTION + ".ReferenceIdentities" # Connection.Interface.Location @@ -385,7 +453,25 @@ MT_NOTICE = 2 MT_AUTO_REPLY = 3 MT_DELIVERY_REPORT = 4 +class MessageFlag(object): + TRUNCATED = 1 + NON_TEXT_CONTENT = 2 + SCROLLBACK = 4 + RESCUED = 8 + +class SendError(object): + UNKNOWN = 0 + OFFLINE = 1 + INVALID_CONTACT = 2 + PERMISSION_DENIED = 3 + TOO_LONG = 4 + NOT_IMPLEMENTED = 5 + PROTOCOL = PREFIX + '.Protocol' +PROTOCOL_IFACE_PRESENCES = PROTOCOL + '.Interface.Presence1' +PROTOCOL_IFACE_ADDRESSING = PROTOCOL + '.Interface.Addressing1' +PROTOCOL_IFACE_AVATARS = PROTOCOL + '.Interface.Avatars1' + PARAM_REQUIRED = 1 PARAM_REGISTER = 2 PARAM_HAS_DEFAULT = 4 @@ -416,3 +502,34 @@ 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 + +DELIVERY_STATUS_UNKNOWN = 0 +DELIVERY_STATUS_DELIVERED = 1 +DELIVERY_STATUS_TEMPORARILY_FAILED = 2 +DELIVERY_STATUS_PERMANENTLY_FAILED = 3 +DELIVERY_STATUS_ACCEPTED = 4 +DELIVERY_STATUS_READ = 5 +DELIVERY_STATUS_DELETED = 6 + +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 + +# Channel.Interface.Room +ROOM_NAME = CHANNEL_IFACE_ROOM + '.RoomName' +ROOM_SERVER = CHANNEL_IFACE_ROOM + '.Server' + +# Channel.Interface.Subject +SUBJECT = CHANNEL_IFACE_ROOM + '.Subject' +SUBJECT_PRESENT = 1 +SUBJECT_CAN_SET = 2 + +DEBUG_IFACE = PREFIX + '.Debug' +DEBUG_PATH = '/' + PREFIX.replace('.', '/') + '/debug' diff --git a/tests/twisted/gabbletest.py b/tests/twisted/gabbletest.py new file mode 100644 index 0000000..11cf3d2 --- /dev/null +++ b/tests/twisted/gabbletest.py @@ -0,0 +1,856 @@ + +""" +Infrastructure code for testing Gabble by pretending to be a Jabber server. +""" + +import base64 +import os +import hashlib +import sys +import random +import re +import traceback + +import ns +import constants as cs +import servicetest +from servicetest import ( + assertEquals, assertLength, assertContains, wrap_channel, + EventPattern, call_async, unwrap, Event) +import twisted +from twisted.words.xish import domish, xpath +from twisted.words.protocols.jabber.client import IQ +from twisted.words.protocols.jabber import xmlstream +from twisted.internet import reactor, ssl + +import dbus + +def make_result_iq(stream, iq, add_query_node=True): + result = IQ(stream, "result") + result["id"] = iq["id"] + to = iq.getAttribute('to') + if to is not None: + result["from"] = to + query = iq.firstChildElement() + + if query and add_query_node: + result.addElement((query.uri, query.name)) + + return result + +def acknowledge_iq(stream, iq): + stream.send(make_result_iq(stream, iq)) + +def send_error_reply(stream, iq, error_stanza=None): + result = IQ(stream, "error") + result["id"] = iq["id"] + query = iq.firstChildElement() + to = iq.getAttribute('to') + if to is not None: + result["from"] = to + + if query: + result.addElement((query.uri, query.name)) + + if error_stanza: + result.addChild(error_stanza) + + stream.send(result) + +def request_muc_handle(q, conn, stream, muc_jid): + servicetest.call_async(q, conn, 'RequestHandles', 2, [muc_jid]) + event = q.expect('dbus-return', method='RequestHandles') + return event.value[0][0] + +def make_muc_presence(affiliation, role, muc_jid, alias, jid=None, photo=None): + presence = domish.Element((None, 'presence')) + presence['from'] = '%s/%s' % (muc_jid, alias) + x = presence.addElement((ns.MUC_USER, 'x')) + item = x.addElement('item') + item['affiliation'] = affiliation + item['role'] = role + if jid is not None: + item['jid'] = jid + + if photo is not None: + presence.addChild( + elem(ns.VCARD_TEMP_UPDATE, 'x')( + elem('photo')(unicode(photo)) + )) + + return presence + +def sync_stream(q, stream): + """Used to ensure that Gabble has processed all stanzas sent to it.""" + + iq = IQ(stream, "get") + id = iq['id'] + iq.addElement(('http://jabber.org/protocol/disco#info', 'query')) + stream.send(iq) + q.expect('stream-iq', query_ns='http://jabber.org/protocol/disco#info', + predicate=(lambda event: + event.stanza['id'] == id and event.iq_type == 'result')) + +class GabbleAuthenticator(xmlstream.Authenticator): + def __init__(self, username, password, resource=None): + self.username = username + self.password = password + self.resource = resource + self.bare_jid = None + self.full_jid = None + self._event_func = lambda e: None + xmlstream.Authenticator.__init__(self) + + def set_event_func(self, event_func): + self._event_func = event_func + +class JabberAuthenticator(GabbleAuthenticator): + "Trivial XML stream authenticator that accepts one username/digest pair." + + # Patch in fix from http://twistedmatrix.com/trac/changeset/23418. + # This monkeypatch taken from Gadget source code + from twisted.words.xish.utility import EventDispatcher + + def _addObserver(self, onetime, event, observerfn, priority, *args, + **kwargs): + if self._dispatchDepth > 0: + self._updateQueue.append(lambda: self._addObserver(onetime, event, + observerfn, priority, *args, **kwargs)) + + return self._oldAddObserver(onetime, event, observerfn, priority, + *args, **kwargs) + + EventDispatcher._oldAddObserver = EventDispatcher._addObserver + EventDispatcher._addObserver = _addObserver + + def __init__(self, username, password, resource=None, emit_events=False): + GabbleAuthenticator.__init__(self, username, password, resource) + self.emit_events = emit_events + + def streamStarted(self, root=None): + if root: + self.xmlstream.sid = '%x' % random.randint(1, sys.maxint) + self.xmlstream.domain = root.getAttribute('to') + + self.xmlstream.sendHeader() + self.xmlstream.addOnetimeObserver( + "/iq/query[@xmlns='jabber:iq:auth']", self.initialIq) + + def initialIq(self, iq): + if self.emit_events: + self._event_func(Event('auth-initial-iq', authenticator=self, + iq=iq, id=iq["id"])) + else: + self.respondToInitialIq(iq) + + self.xmlstream.addOnetimeObserver('/iq/query/username', self.secondIq) + + def respondToInitialIq(self, iq): + result = IQ(self.xmlstream, "result") + result["id"] = iq["id"] + query = result.addElement('query') + query["xmlns"] = "jabber:iq:auth" + query.addElement('username', content='test') + query.addElement('password') + query.addElement('digest') + query.addElement('resource') + self.xmlstream.send(result) + + def secondIq(self, iq): + if self.emit_events: + self._event_func(Event('auth-second-iq', authenticator=self, + iq=iq, id=iq["id"])) + else: + self.respondToSecondIq(iq) + + def respondToSecondIq(self, iq): + username = xpath.queryForNodes('/iq/query/username', iq) + assert map(str, username) == [self.username] + + digest = xpath.queryForNodes('/iq/query/digest', iq) + expect = hashlib.sha1(self.xmlstream.sid + self.password).hexdigest() + assert map(str, digest) == [expect] + + resource = xpath.queryForNodes('/iq/query/resource', iq) + assertLength(1, resource) + if self.resource is not None: + assertEquals(self.resource, str(resource[0])) + + self.bare_jid = '%s@%s' % (self.username, self.xmlstream.domain) + self.full_jid = '%s/%s' % (self.bare_jid, resource) + + result = IQ(self.xmlstream, "result") + result["id"] = iq["id"] + self.xmlstream.send(result) + self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT) + +class XmppAuthenticator(GabbleAuthenticator): + def __init__(self, username, password, resource=None): + GabbleAuthenticator.__init__(self, username, password, resource) + self.authenticated = False + + self._mechanisms = ['PLAIN'] + + def streamInitialize(self, root): + if root: + self.xmlstream.sid = root.getAttribute('id') + self.xmlstream.domain = root.getAttribute('to') + + if self.xmlstream.sid is None: + self.xmlstream.sid = '%x' % random.randint(1, sys.maxint) + + self.xmlstream.sendHeader() + + def streamIQ(self): + features = elem(xmlstream.NS_STREAMS, 'features')( + elem(ns.NS_XMPP_BIND, 'bind'), + elem(ns.NS_XMPP_SESSION, 'session'), + ) + self.xmlstream.send(features) + + self.xmlstream.addOnetimeObserver( + "/iq/bind[@xmlns='%s']" % ns.NS_XMPP_BIND, self.bindIq) + self.xmlstream.addOnetimeObserver( + "/iq/session[@xmlns='%s']" % ns.NS_XMPP_SESSION, self.sessionIq) + + def streamSASL(self): + features = domish.Element((xmlstream.NS_STREAMS, 'features')) + mechanisms = features.addElement((ns.NS_XMPP_SASL, 'mechanisms')) + for mechanism in self._mechanisms: + mechanisms.addElement('mechanism', content=mechanism) + self.xmlstream.send(features) + + self.xmlstream.addOnetimeObserver("/auth", self.auth) + + def streamStarted(self, root=None): + self.streamInitialize(root) + + if self.authenticated: + # Initiator authenticated itself, and has started a new stream. + self.streamIQ() + else: + self.streamSASL() + + def auth(self, auth): + assert (base64.b64decode(str(auth)) == + '\x00%s\x00%s' % (self.username, self.password)) + + success = domish.Element((ns.NS_XMPP_SASL, 'success')) + self.xmlstream.send(success) + self.xmlstream.reset() + self.authenticated = True + + def bindIq(self, iq): + resource = xpath.queryForString('/iq/bind/resource', iq) + if self.resource is not None: + assertEquals(self.resource, resource) + else: + assert resource is not None + + result = IQ(self.xmlstream, "result") + result["id"] = iq["id"] + bind = result.addElement((ns.NS_XMPP_BIND, 'bind')) + self.bare_jid = '%s@%s' % (self.username, self.xmlstream.domain) + self.full_jid = '%s/%s' % (self.bare_jid, resource) + jid = bind.addElement('jid', content=self.full_jid) + self.xmlstream.send(result) + + self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT) + + def sessionIq(self, iq): + self.xmlstream.send(make_result_iq(self.xmlstream, iq)) + +class StreamEvent(servicetest.Event): + def __init__(self, type_, stanza, stream): + servicetest.Event.__init__(self, type_, stanza=stanza) + self.stream = stream + self.to = stanza.getAttribute("to") + +class IQEvent(StreamEvent): + def __init__(self, stream, iq): + StreamEvent.__init__(self, 'stream-iq', iq, stream) + self.iq_type = iq.getAttribute("type") + self.iq_id = iq.getAttribute("id") + + query = iq.firstChildElement() + + if query: + self.query = query + self.query_ns = query.uri + self.query_name = query.name + + if query.getAttribute("node"): + self.query_node = query.getAttribute("node") + else: + self.query = None + +class PresenceEvent(StreamEvent): + def __init__(self, stream, stanza): + StreamEvent.__init__(self, 'stream-presence', stanza, stream) + self.presence_type = stanza.getAttribute('type') + + statuses = xpath.queryForNodes('/presence/status', stanza) + + if statuses: + self.presence_status = str(statuses[0]) + +class MessageEvent(StreamEvent): + def __init__(self, stream, stanza): + StreamEvent.__init__(self, 'stream-message', stanza, stream) + self.message_type = stanza.getAttribute('type') + +class StreamFactory(twisted.internet.protocol.Factory): + def __init__(self, streams, jids): + self.streams = streams + self.jids = jids + self.presences = {} + self.mappings = dict(map (lambda jid, stream: (jid, stream), + jids, streams)) + + # Make a copy of the streams + self.factory_streams = list(streams) + self.factory_streams.reverse() + + # Do not add observers for single instances because it's unnecessary and + # some unit tests need to respond to the roster request, and we shouldn't + # answer it for them otherwise we break compatibility + if len(streams) > 1: + # We need to have a function here because lambda keeps a reference on + # the stream and jid and in the for loop, there is no context + def addObservers(stream, jid): + stream.addObserver('/iq', lambda x: \ + self.forward_iq(stream, jid, x)) + stream.addObserver('/presence', lambda x: \ + self.got_presence(stream, jid, x)) + + for (jid, stream) in self.mappings.items(): + addObservers(stream, jid) + + def protocol(self, *args): + return self.factory_streams.pop() + + + def got_presence (self, stream, jid, stanza): + stanza.attributes['from'] = jid + self.presences[jid] = stanza + + for dest_jid in self.presences.keys(): + # Dispatch the new presence to other clients + stanza.attributes['to'] = dest_jid + self.mappings[dest_jid].send(stanza) + + # Don't echo the presence twice + if dest_jid != jid: + # Dispatch other client's presence to this stream + presence = self.presences[dest_jid] + presence.attributes['to'] = jid + stream.send(presence) + + def lost_presence(self, stream, jid): + if self.presences.has_key(jid): + del self.presences[jid] + for dest_jid in self.presences.keys(): + presence = domish.Element(('jabber:client', 'presence')) + presence['from'] = jid + presence['to'] = dest_jid + presence['type'] = 'unavailable' + self.mappings[dest_jid].send(presence) + + def forward_iq(self, stream, jid, stanza): + stanza.attributes['from'] = jid + + query = stanza.firstChildElement() + + # Fake other accounts as being part of our roster + if query and query.uri == ns.ROSTER: + roster = make_result_iq(stream, stanza) + query = roster.firstChildElement() + for roster_jid in self.mappings.keys(): + if jid != roster_jid: + item = query.addElement('item') + item['jid'] = roster_jid + item['subscription'] = 'both' + stream.send(roster) + return + + to = stanza.getAttribute('to') + dest = None + if to is not None: + dest = self.mappings.get(to) + + if dest is not None: + dest.send(stanza) + +class BaseXmlStream(xmlstream.XmlStream): + initiating = False + namespace = 'jabber:client' + pep_support = True + disco_features = [] + handle_privacy_lists = True + + def __init__(self, event_func, authenticator): + xmlstream.XmlStream.__init__(self, authenticator) + self.event_func = event_func + self.addObserver('//iq', lambda x: event_func( + IQEvent(self, x))) + self.addObserver('//message', lambda x: event_func( + MessageEvent(self, x))) + self.addObserver('//presence', lambda x: event_func( + PresenceEvent(self, x))) + self.addObserver('//event/stream/authd', self._cb_authd) + if self.handle_privacy_lists: + self.addObserver("/iq/query[@xmlns='%s']" % ns.PRIVACY, + self._cb_priv_list) + + def connectionMade(self): + xmlstream.XmlStream.connectionMade(self) + + if 'GABBLE_NODELAY' in os.environ: + self.transport.setTcpNoDelay(True) + + def _cb_priv_list(self, iq): + send_error_reply(self, iq) + + def _cb_authd(self, _): + # called when stream is authenticated + assert self.authenticator.full_jid is not None + assert self.authenticator.bare_jid is not None + + self.addObserver( + "/iq[@to='%s']/query[@xmlns='http://jabber.org/protocol/disco#info']" % self.domain, + self._cb_disco_iq) + self.addObserver( + "/iq[@to='%s']/query[@xmlns='http://jabber.org/protocol/disco#info']" + % self.authenticator.bare_jid, + self._cb_bare_jid_disco_iq) + self.event_func(servicetest.Event('stream-authenticated')) + + def _cb_disco_iq(self, iq): + nodes = xpath.queryForNodes( + "/iq/query[@xmlns='http://jabber.org/protocol/disco#info']", iq) + query = nodes[0] + + for feature in self.disco_features: + query.addChild(elem('feature', var=feature)) + + iq['type'] = 'result' + iq['from'] = iq['to'] + self.send(iq) + + def _cb_bare_jid_disco_iq(self, iq): + # advertise PEP support + nodes = xpath.queryForNodes( + "/iq/query[@xmlns='http://jabber.org/protocol/disco#info']", + iq) + query = nodes[0] + identity = query.addElement('identity') + identity['category'] = 'pubsub' + identity['type'] = 'pep' + + iq['type'] = 'result' + iq['from'] = iq['to'] + self.send(iq) + + def onDocumentEnd(self): + self.event_func(servicetest.Event('stream-closed')) + # We don't chain up XmlStream.onDocumentEnd() because it will + # disconnect the TCP connection making tests as + # connect/disconnect-timeout.py not working + + def connectionLost(self, reason): + self.event_func(servicetest.Event('stream-connection-lost')) + xmlstream.XmlStream.connectionLost(self, reason) + + def send_stream_error(self, error='system-shutdown'): + # Yes, there are meant to be two different STREAMS namespaces. + go_away = \ + elem(xmlstream.NS_STREAMS, 'error')( + elem(ns.STREAMS, error) + ) + + self.send(go_away) + +class JabberXmlStream(BaseXmlStream): + version = (0, 9) + +class XmppXmlStream(BaseXmlStream): + version = (1, 0) + +class GoogleXmlStream(BaseXmlStream): + version = (1, 0) + + pep_support = False + disco_features = [ns.GOOGLE_ROSTER, + ns.GOOGLE_JINGLE_INFO, + ns.GOOGLE_MAIL_NOTIFY, + ns.GOOGLE_QUEUE, + ] + + def _cb_bare_jid_disco_iq(self, iq): + # Google talk doesn't support PEP :( + iq['type'] = 'result' + iq['from'] = iq['to'] + self.send(iq) + + +def make_connection(bus, event_func, params=None, suffix=''): + # Gabble accepts a resource in 'account', but the value of 'resource' + # overrides it if there is one. + test_name = re.sub('(.*tests/twisted/|\./)', '', sys.argv[0]) + account = 'test%s@localhost/%s' % (suffix, test_name) + + default_params = { + 'account': account, + 'password': 'pass', + 'resource': 'Resource', + 'server': 'localhost', + 'port': dbus.UInt32(4242), + 'fallback-socks5-proxies': dbus.Array([], signature='s'), + 'require-encryption': False, + } + + if params: + default_params.update(params) + + # Allow omitting the 'password' param + if default_params['password'] is None: + del default_params['password'] + + # Allow omitting the 'account' param + if default_params['account'] is None: + del default_params['account'] + + jid = default_params.get('account', None) + conn = servicetest.make_connection(bus, event_func, 'gabble', 'jabber', + default_params) + return (conn, jid) + +def make_stream(event_func, authenticator=None, protocol=None, + resource=None, suffix=''): + # set up Jabber server + if authenticator is None: + authenticator = XmppAuthenticator('test%s' % suffix, 'pass', resource=resource) + + authenticator.set_event_func(event_func) + + if protocol is None: + protocol = XmppXmlStream + + stream = protocol(event_func, authenticator) + return stream + +def disconnect_conn(q, conn, stream, expected_before=[], expected_after=[]): + call_async(q, conn, 'Disconnect') + + tmp = expected_before + [ + EventPattern('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_DISCONNECTED, cs.CSR_REQUESTED]), + EventPattern('stream-closed')] + + before_events = q.expect_many(*tmp) + + stream.sendFooter() + + tmp = expected_after + [EventPattern('dbus-return', method='Disconnect')] + after_events = q.expect_many(*tmp) + + return before_events[:-2], after_events[:-1] + +def element_repr(element): + """__repr__ cannot safely return non-ASCII: see + <http://bugs.python.org/issue5876>. So we print non-ASCII characters as + \uXXXX escapes in debug output + + """ + return element.toXml().encode('unicode-escape') + +def expect_connected(queue): + queue.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED]) + queue.expect('stream-authenticated') + queue.expect('dbus-signal', signal='PresencesChanged', + args=[{1L: (cs.PRESENCE_AVAILABLE, u'available', '')}]) + queue.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) + +def exec_test_deferred(fun, params, protocol=None, timeout=None, + authenticator=None, num_instances=1, + do_connect=True, + make_connection_func=make_connection, + expect_connected_func=expect_connected): + # hack to ease debugging + domish.Element.__repr__ = element_repr + colourer = None + + if sys.stdout.isatty() or 'CHECK_FORCE_COLOR' in os.environ: + colourer = servicetest.install_colourer() + + try: + bus = dbus.SessionBus() + except dbus.exceptions.DBusException as e: + print e + os._exit(1) + + queue = servicetest.IteratingEventQueue(timeout) + queue.verbose = ( + os.environ.get('CHECK_TWISTED_VERBOSE', '') != '' + or '-v' in sys.argv) + + conns = [] + jids = [] + streams = [] + resource = params.get('resource') if params is not None else None + for i in range(0, num_instances): + if i == 0: + suffix = '' + else: + suffix = str(i) + + try: + (conn, jid) = make_connection_func(bus, queue.append, params, suffix) + except Exception, e: + # Crap. This is normally because the connection's still kicking + # around on the bus. Let's bin any connections we *did* manage to + # get going and then bail out unceremoniously. + print e + + for conn in conns: + conn.Disconnect() + + os._exit(1) + + conns.append(conn) + jids.append(jid) + streams.append(make_stream(queue.append, protocol=protocol, + authenticator=authenticator, + resource=resource, suffix=suffix)) + + factory = StreamFactory(streams, jids) + port = reactor.listenTCP(4242, factory, interface='localhost') + + def signal_receiver(*args, **kw): + if kw['path'] == '/org/freedesktop/DBus' and \ + kw['member'] == 'NameOwnerChanged': + bus_name, old_name, new_name = args + if new_name == '': + for i, conn in enumerate(conns): + stream = streams[i] + jid = jids[i] + if conn._requested_bus_name == bus_name: + factory.lost_presence(stream, jid) + break + queue.append(Event('dbus-signal', + path=unwrap(kw['path']), + signal=kw['member'], args=map(unwrap, args), + interface=kw['interface'])) + + match_all_signals = 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: + if do_connect: + for conn in conns: + conn.Connect() + expect_connected_func(queue) + + if len(conns) == 1: + fun(queue, bus, conns[0], streams[0]) + else: + fun(queue, bus, conns, streams) + except Exception, e: + traceback.print_exc() + error = e + queue.verbose = False + + if colourer: + sys.stdout = colourer.fh + + d = port.stopListening() + + # Does the Connection object still exist? + for i, conn in enumerate(conns): + if not bus.name_has_owner(conn.object.bus_name): + # Connection has already been disconnected and destroyed + continue + try: + if conn.Properties.Get(cs.CONN, 'Status') == cs.CONN_STATUS_CONNECTED: + # Connection is connected, properly disconnect it + disconnect_conn(queue, conn, streams[i]) + else: + # Connection is not connected, call Disconnect() to destroy it + conn.Disconnect() + except dbus.DBusException, e: + pass + except Exception, e: + traceback.print_exc() + error = e + + 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 + + match_all_signals.remove() + + if error is None: + d.addBoth((lambda *args: reactor.crash())) + else: + # please ignore the POSIX behind the curtain + d.addBoth((lambda *args: os._exit(1))) + + +def exec_test(fun, params=None, protocol=None, timeout=None, + authenticator=None, num_instances=1, do_connect=True): + reactor.callWhenRunning( + exec_test_deferred, fun, params, protocol, timeout, authenticator, num_instances, + do_connect) + reactor.run() + +# Useful routines for server-side vCard handling +current_vcard = domish.Element(('vcard-temp', 'vCard')) + +def expect_and_handle_get_vcard(q, stream): + get_vcard_event = q.expect('stream-iq', query_ns=ns.VCARD_TEMP, + query_name='vCard', iq_type='get') + + iq = get_vcard_event.stanza + vcard = iq.firstChildElement() + assert vcard.name == 'vCard', vcard.toXml() + + # Send back current vCard + result = make_result_iq(stream, iq, add_query_node=False) + result.addChild(current_vcard) + stream.send(result) + +def expect_and_handle_set_vcard(q, stream, check=None): + global current_vcard + set_vcard_event = q.expect('stream-iq', query_ns=ns.VCARD_TEMP, + query_name='vCard', iq_type='set') + iq = set_vcard_event.stanza + vcard = iq.firstChildElement() + assert vcard.name == 'vCard', vcard.toXml() + + if check is not None: + check(vcard) + + # Update current vCard + current_vcard = vcard + + stream.send(make_result_iq(stream, iq)) + +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='test@localhost', 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/tests/twisted/hazetest.py b/tests/twisted/hazetest.py index ea2d68f..e03c58c 100644 --- a/tests/twisted/hazetest.py +++ b/tests/twisted/hazetest.py @@ -1,439 +1,19 @@ - """ Infrastructure code for testing Haze by pretending to be a Jabber server. - -This is based on gabbletest.py in telepathy-gabble. Haze-specific hacks should -be marked with an 'XXX Haze' comment. This offends me too, but I don't have -time to do anything better. """ -import base64 -import os -import hashlib import sys -import random -import re -import traceback - -import ns -import constants as cs -import servicetest -from servicetest import ( - assertEquals, assertLength, assertContains, wrap_channel, - EventPattern, call_async, unwrap, Event) -import twisted from twisted.words.xish import domish, xpath -from twisted.words.protocols.jabber.client import IQ -from twisted.words.protocols.jabber import xmlstream -from twisted.internet import reactor, ssl - import dbus -def make_result_iq(stream, iq, add_query_node=True): - result = IQ(stream, "result") - result["id"] = iq["id"] - to = iq.getAttribute('to') - if to is not None: - result["from"] = to - query = iq.firstChildElement() - - if query and add_query_node: - result.addElement((query.uri, query.name)) - - return result - -def acknowledge_iq(stream, iq): - stream.send(make_result_iq(stream, iq)) - -def send_error_reply(stream, iq, error_stanza=None): - result = IQ(stream, "error") - result["id"] = iq["id"] - query = iq.firstChildElement() - to = iq.getAttribute('to') - if to is not None: - result["from"] = to - - if query: - result.addElement((query.uri, query.name)) - - if error_stanza: - result.addChild(error_stanza) - - stream.send(result) - -def request_muc_handle(q, conn, stream, muc_jid): - servicetest.call_async(q, conn, 'RequestHandles', 2, [muc_jid]) - event = q.expect('dbus-return', method='RequestHandles') - return event.value[0][0] - -def make_muc_presence(affiliation, role, muc_jid, alias, jid=None): - presence = domish.Element((None, 'presence')) - presence['from'] = '%s/%s' % (muc_jid, alias) - x = presence.addElement((ns.MUC_USER, 'x')) - item = x.addElement('item') - item['affiliation'] = affiliation - item['role'] = role - if jid is not None: - item['jid'] = jid - return presence - -def sync_stream(q, stream): - """Used to ensure that Gabble has processed all stanzas sent to it.""" - - iq = IQ(stream, "get") - id = iq['id'] - iq.addElement(('http://jabber.org/protocol/disco#info', 'query')) - stream.send(iq) - q.expect('stream-iq', query_ns='http://jabber.org/protocol/disco#info', - predicate=(lambda event: - event.stanza['id'] == id and event.iq_type == 'result')) - -class GabbleAuthenticator(xmlstream.Authenticator): - def __init__(self, username, password, resource=None): - self.username = username - self.password = password - self.resource = resource - self.bare_jid = None - self.full_jid = None - xmlstream.Authenticator.__init__(self) - -class JabberAuthenticator(GabbleAuthenticator): - "Trivial XML stream authenticator that accepts one username/digest pair." - - # Patch in fix from http://twistedmatrix.com/trac/changeset/23418. - # This monkeypatch taken from Gadget source code - from twisted.words.xish.utility import EventDispatcher - - def _addObserver(self, onetime, event, observerfn, priority, *args, - **kwargs): - if self._dispatchDepth > 0: - self._updateQueue.append(lambda: self._addObserver(onetime, event, - observerfn, priority, *args, **kwargs)) - - return self._oldAddObserver(onetime, event, observerfn, priority, - *args, **kwargs) - - EventDispatcher._oldAddObserver = EventDispatcher._addObserver - EventDispatcher._addObserver = _addObserver - - def streamStarted(self, root=None): - if root: - self.xmlstream.sid = '%x' % random.randint(1, sys.maxint) - - self.xmlstream.sendHeader() - self.xmlstream.addOnetimeObserver( - "/iq/query[@xmlns='jabber:iq:auth']", self.initialIq) - - def initialIq(self, iq): - result = IQ(self.xmlstream, "result") - result["id"] = iq["id"] - query = result.addElement('query') - query["xmlns"] = "jabber:iq:auth" - query.addElement('username', content='test') - query.addElement('password') - query.addElement('digest') - query.addElement('resource') - self.xmlstream.addOnetimeObserver('/iq/query/username', self.secondIq) - self.xmlstream.send(result) - - def secondIq(self, iq): - username = xpath.queryForNodes('/iq/query/username', iq) - assert map(str, username) == [self.username] - - digest = xpath.queryForNodes('/iq/query/digest', iq) - expect = hashlib.sha1(self.xmlstream.sid + self.password).hexdigest() - assert map(str, digest) == [expect] - - resource = xpath.queryForNodes('/iq/query/resource', iq) - assertLength(1, resource) - if self.resource is not None: - assertEquals(self.resource, str(resource[0])) - - self.bare_jid = '%s@localhost' % self.username - self.full_jid = '%s/%s' % (self.bare_jid, resource) - - result = IQ(self.xmlstream, "result") - result["id"] = iq["id"] - self.xmlstream.send(result) - self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT) - -class XmppAuthenticator(GabbleAuthenticator): - def __init__(self, username, password, resource=None): - GabbleAuthenticator.__init__(self, username, password, resource) - self.authenticated = False - - def streamInitialize(self, root): - if root: - self.xmlstream.sid = root.getAttribute('id') - - if self.xmlstream.sid is None: - self.xmlstream.sid = '%x' % random.randint(1, sys.maxint) - - self.xmlstream.sendHeader() - - def streamIQ(self): - features = elem(xmlstream.NS_STREAMS, 'features')( - elem(ns.NS_XMPP_BIND, 'bind'), - elem(ns.NS_XMPP_SESSION, 'session'), - ) - self.xmlstream.send(features) - - self.xmlstream.addOnetimeObserver( - "/iq/bind[@xmlns='%s']" % ns.NS_XMPP_BIND, self.bindIq) - self.xmlstream.addOnetimeObserver( - "/iq/session[@xmlns='%s']" % ns.NS_XMPP_SESSION, self.sessionIq) - - def streamSASL(self): - features = domish.Element((xmlstream.NS_STREAMS, 'features')) - mechanisms = features.addElement((ns.NS_XMPP_SASL, 'mechanisms')) - mechanism = mechanisms.addElement('mechanism', content='PLAIN') - self.xmlstream.send(features) - - self.xmlstream.addOnetimeObserver("/auth", self.auth) - - def streamStarted(self, root=None): - self.streamInitialize(root) - - if self.authenticated: - # Initiator authenticated itself, and has started a new stream. - self.streamIQ() - else: - self.streamSASL() - - def auth(self, auth): - assert (base64.b64decode(str(auth)) == - '\x00%s\x00%s' % (self.username, self.password)) - - success = domish.Element((ns.NS_XMPP_SASL, 'success')) - self.xmlstream.send(success) - self.xmlstream.reset() - self.authenticated = True - - def bindIq(self, iq): - resource = xpath.queryForString('/iq/bind/resource', iq) - if self.resource is not None: - assertEquals(self.resource, resource) - else: - assert resource is not None - - result = IQ(self.xmlstream, "result") - result["id"] = iq["id"] - bind = result.addElement((ns.NS_XMPP_BIND, 'bind')) - self.bare_jid = '%s@localhost' % self.username - self.full_jid = '%s/%s' % (self.bare_jid, resource) - jid = bind.addElement('jid', content=self.full_jid) - self.xmlstream.send(result) - - self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT) - - def sessionIq(self, iq): - self.xmlstream.send(make_result_iq(self.xmlstream, iq)) - -def make_stream_event(type, stanza, stream): - event = servicetest.Event(type, stanza=stanza) - event.stream = stream - event.to = stanza.getAttribute("to") - return event - -def make_iq_event(stream, iq): - event = make_stream_event('stream-iq', iq, stream) - 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(stream, stanza): - event = make_stream_event('stream-presence', stanza, stream) - 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(stream, stanza): - event = make_stream_event('stream-message', stanza, stream) - event.message_type = stanza.getAttribute('type') - return event - -class StreamFactory(twisted.internet.protocol.Factory): - def __init__(self, streams, jids): - self.streams = streams - self.jids = jids - self.presences = {} - self.mappings = dict(map (lambda jid, stream: (jid, stream), - jids, streams)) - - # Make a copy of the streams - self.factory_streams = list(streams) - self.factory_streams.reverse() - - # Do not add observers for single instances because it's unnecessary and - # some unit tests need to respond to the roster request, and we shouldn't - # answer it for them otherwise we break compatibility - if len(streams) > 1: - # We need to have a function here because lambda keeps a reference on - # the stream and jid and in the for loop, there is no context - def addObservers(stream, jid): - stream.addObserver('/iq', lambda x: \ - self.forward_iq(stream, jid, x)) - stream.addObserver('/presence', lambda x: \ - self.got_presence(stream, jid, x)) - - for (jid, stream) in self.mappings.items(): - addObservers(stream, jid) - - def protocol(self, *args): - return self.factory_streams.pop() - - - def got_presence (self, stream, jid, stanza): - stanza.attributes['from'] = jid - self.presences[jid] = stanza - - for dest_jid in self.presences.keys(): - # Dispatch the new presence to other clients - stanza.attributes['to'] = dest_jid - self.mappings[dest_jid].send(stanza) - - # Don't echo the presence twice - if dest_jid != jid: - # Dispatch other client's presence to this stream - presence = self.presences[dest_jid] - presence.attributes['to'] = jid - stream.send(presence) - - def lost_presence(self, stream, jid): - if self.presences.has_key(jid): - del self.presences[jid] - for dest_jid in self.presences.keys(): - presence = domish.Element(('jabber:client', 'presence')) - presence['from'] = jid - presence['to'] = dest_jid - presence['type'] = 'unavailable' - self.mappings[dest_jid].send(presence) - - def forward_iq(self, stream, jid, stanza): - stanza.attributes['from'] = jid - - query = stanza.firstChildElement() - - # Fake other accounts as being part of our roster - if query and query.uri == ns.ROSTER: - roster = make_result_iq(stream, stanza) - query = roster.firstChildElement() - for roster_jid in self.mappings.keys(): - if jid != roster_jid: - item = query.addElement('item') - item['jid'] = roster_jid - item['subscription'] = 'both' - stream.send(roster) - return - - to = stanza.getAttribute('to') - dest = None - if to is not None: - dest = self.mappings.get(to) - - if dest is not None: - dest.send(stanza) - -class BaseXmlStream(xmlstream.XmlStream): - initiating = False - namespace = 'jabber:client' - pep_support = True - disco_features = [] - handle_privacy_lists = True - - def __init__(self, event_func, authenticator): - xmlstream.XmlStream.__init__(self, authenticator) - self.event_func = event_func - self.addObserver('//iq', lambda x: event_func( - make_iq_event(self, x))) - self.addObserver('//message', lambda x: event_func( - make_message_event(self, x))) - self.addObserver('//presence', lambda x: event_func( - make_presence_event(self, x))) - self.addObserver('//event/stream/authd', self._cb_authd) - if self.handle_privacy_lists: - self.addObserver("/iq/query[@xmlns='%s']" % ns.PRIVACY, - self._cb_priv_list) - - def _cb_priv_list(self, iq): - send_error_reply(self, iq) - - def _cb_authd(self, _): - # called when stream is authenticated - assert self.authenticator.full_jid is not None - assert self.authenticator.bare_jid is not None - - self.addObserver( - "/iq[@to='localhost']/query[@xmlns='http://jabber.org/protocol/disco#info']", - self._cb_disco_iq) - self.addObserver( - "/iq[@to='%s']/query[@xmlns='http://jabber.org/protocol/disco#info']" - % self.authenticator.bare_jid, - self._cb_bare_jid_disco_iq) - # XXX Haze - self.add_roster_observer() - self.event_func(servicetest.Event('stream-authenticated')) - - def _cb_disco_iq(self, iq): - nodes = xpath.queryForNodes( - "/iq/query[@xmlns='http://jabber.org/protocol/disco#info']", iq) - query = nodes[0] - - for feature in self.disco_features: - query.addChild(elem('feature', var=feature)) - - iq['type'] = 'result' - iq['from'] = iq['to'] - self.send(iq) - - def _cb_bare_jid_disco_iq(self, iq): - # advertise PEP support - nodes = xpath.queryForNodes( - "/iq/query[@xmlns='http://jabber.org/protocol/disco#info']", - iq) - query = nodes[0] - identity = query.addElement('identity') - identity['category'] = 'pubsub' - identity['type'] = 'pep' - - iq['type'] = 'result' - iq['from'] = iq['to'] - self.send(iq) - - def onDocumentEnd(self): - self.event_func(servicetest.Event('stream-closed')) - # We don't chain up XmlStream.onDocumentEnd() because it will - # disconnect the TCP connection making tests as - # connect/disconnect-timeout.py not working - - def send_stream_error(self, error='system-shutdown'): - # Yes, there are meant to be two different STREAMS namespaces. - go_away = \ - elem(xmlstream.NS_STREAMS, 'error')( - elem(ns.STREAMS, error) - ) +import servicetest +# reexport everything, but override a thing +from gabbletest import * - self.send(go_away) +class EmptyRosterXmppXmlStream(XmppXmlStream): + def _cb_authd(self, x): + XmppXmlStream._cb_authd(self, x) - # XXX Haze: the next two methods are Haze-specific. - def add_roster_observer(self): self.addObserver( "/iq/query[@xmlns='jabber:iq:roster']", self._cb_roster_get) @@ -444,44 +24,18 @@ class BaseXmlStream(xmlstream.XmlStream): if iq.getAttribute('type') == 'get': self.send(make_result_iq(self, iq)) -class JabberXmlStream(BaseXmlStream): - version = (0, 9) - -class XmppXmlStream(BaseXmlStream): - version = (1, 0) - -class GoogleXmlStream(BaseXmlStream): - version = (1, 0) - - pep_support = False - disco_features = [ns.GOOGLE_ROSTER, - ns.GOOGLE_JINGLE_INFO, - ns.GOOGLE_MAIL_NOTIFY, - ] - - def _cb_bare_jid_disco_iq(self, iq): - # Google talk doesn't support PEP :( - iq['type'] = 'result' - iq['from'] = iq['to'] - self.send(iq) - - -def make_connection(bus, event_func, params=None, suffix=''): +def make_haze_connection(bus, event_func, params=None, suffix=''): # Gabble accepts a resource in 'account', but the value of 'resource' - # overrides it if there is one. - # XXX Haze doesn't. + # overrides it if there is one. Haze doesn't. # account = 'test%s@localhost/%s' % (suffix, re.sub(r'.*/', '', sys.argv[0])) account = 'test%s@localhost/Resource' % (suffix, ) default_params = { 'account': account, 'password': 'pass', - # XXX Haze: fd.o#14212. - #'resource': 'Resource', 'ft-proxies': sys.argv[0], 'server': 'localhost', 'port': dbus.UInt32(4242), - # XXX Haze 'require-encryption': False, 'auth-plain-in-clear': True, } @@ -498,308 +52,25 @@ def make_connection(bus, event_func, params=None, suffix=''): del default_params['account'] jid = default_params.get('account', None) - # XXX Haze conn = servicetest.make_connection(bus, event_func, 'haze', 'jabber', default_params) return (conn, jid) -def make_stream(event_func, authenticator=None, protocol=None, - resource=None, suffix=''): - # set up Jabber server - if authenticator is None: - authenticator = XmppAuthenticator('test%s' % suffix, 'pass', resource=resource) - - if protocol is None: - protocol = XmppXmlStream - - stream = protocol(event_func, authenticator) - return stream - -def disconnect_conn(q, conn, stream, expected_before=[], expected_after=[]): - call_async(q, conn, 'Disconnect') - - tmp = expected_before + [ - EventPattern('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_DISCONNECTED, cs.CSR_REQUESTED]), - EventPattern('stream-closed')] - - before_events = q.expect_many(*tmp) - - stream.sendFooter() - - tmp = expected_after + [EventPattern('dbus-return', method='Disconnect')] - after_events = q.expect_many(*tmp) - - return before_events[:-2], after_events[:-1] - -def exec_test_deferred(fun, params, protocol=None, timeout=None, - authenticator=None, num_instances=1): - # hack to ease debugging - domish.Element.__repr__ = domish.Element.toXml - colourer = None - - 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) - - conns = [] - jids = [] - streams = [] - resource = params.get('resource') if params is not None else None - for i in range(0, num_instances): - if i == 0: - suffix = '' - else: - suffix = str(i) - - try: - (conn, jid) = make_connection(bus, queue.append, params, suffix) - except Exception, e: - # Crap. This is normally because the connection's still kicking - # around on the bus. Let's bin any connections we *did* manage to - # get going and then bail out unceremoniously. - print e - - for conn in conns: - conn.Disconnect() - - os._exit(1) - - conns.append(conn) - jids.append(jid) - streams.append(make_stream(queue.append, protocol=protocol, - authenticator=authenticator, - resource=resource, suffix=suffix)) - - factory = StreamFactory(streams, jids) - port = reactor.listenTCP(4242, factory) - - def signal_receiver(*args, **kw): - if kw['path'] == '/org/freedesktop/DBus' and \ - kw['member'] == 'NameOwnerChanged': - bus_name, old_name, new_name = args - if new_name == '': - for i, conn in enumerate(conns): - stream = streams[i] - jid = jids[i] - if conn._requested_bus_name == bus_name: - factory.lost_presence(stream, jid) - break - 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: - if len(conns) == 1: - fun(queue, bus, conns[0], streams[0]) - else: - fun(queue, bus, conns, streams) - except Exception, e: - traceback.print_exc() - error = e - - if colourer: - sys.stdout = colourer.fh - - d = port.stopListening() - - # Does the Connection object still exist? - for i, conn in enumerate(conns): - if not bus.name_has_owner(conn.object.bus_name): - # Connection has already been disconnected and destroyed - continue - try: - if conn.Properties.Get(cs.CONN, 'Status') == cs.CONN_STATUS_CONNECTED: - # Connection is connected, properly disconnect it - disconnect_conn(queue, conn, streams[i]) - 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: - d.addBoth((lambda *args: reactor.crash())) - else: - # please ignore the POSIX behind the curtain - d.addBoth((lambda *args: os._exit(1))) - - -def exec_test(fun, params=None, protocol=None, timeout=None, - authenticator=None, num_instances=1): +def expect_kinda_connected(queue): + queue.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED]) + queue.expect('stream-authenticated') + # FIXME: unlike Gabble, Haze does not signal a presence update to available + # during connect + # queue.expect('dbus-signal', signal='PresencesChanged', + # args=[{1L: (cs.PRESENCE_AVAILABLE, u'available', '')}]) + queue.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) + +# Copy pasta because we need to replace make_connection +def exec_test(fun, params=None, protocol=EmptyRosterXmppXmlStream, timeout=None, + authenticator=None, num_instances=1, do_connect=True): reactor.callWhenRunning( - exec_test_deferred, fun, params, protocol, timeout, authenticator, num_instances) + exec_test_deferred, fun, params, protocol, timeout, authenticator, num_instances, + do_connect, make_haze_connection, expect_kinda_connected) reactor.run() - -# Useful routines for server-side vCard handling -current_vcard = domish.Element(('vcard-temp', 'vCard')) - -def expect_and_handle_get_vcard(q, stream): - get_vcard_event = q.expect('stream-iq', query_ns=ns.VCARD_TEMP, - query_name='vCard', iq_type='get') - - iq = get_vcard_event.stanza - vcard = iq.firstChildElement() - assert vcard.name == 'vCard', vcard.toXml() - - # Send back current vCard - result = make_result_iq(stream, iq) - result.addChild(current_vcard) - stream.send(result) - -def expect_and_handle_set_vcard(q, stream, check=None): - set_vcard_event = q.expect('stream-iq', query_ns=ns.VCARD_TEMP, - query_name='vCard', iq_type='set') - iq = set_vcard_event.stanza - vcard = iq.firstChildElement() - assert vcard.name == 'vCard', vcard.toXml() - - if check is not None: - check(vcard) - - # Update current vCard - current_vcard = vcard - - stream.send(make_result_iq(stream, iq)) - -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='test@localhost', 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 - -def close_all_groups(q, bus, conn, stream): - channels = conn.Properties.Get(cs.CONN_IFACE_REQUESTS, 'Channels') - for path, props in channels: - if props.get(cs.CHANNEL_TYPE) != cs.CHANNEL_TYPE_CONTACT_LIST: - continue - if props.get(cs.TARGET_HANDLE_TYPE) != cs.HT_GROUP: - continue - wrap_channel(bus.get_object(conn.bus_name, path), - cs.CHANNEL_TYPE_CONTACT_LIST).Close() diff --git a/tests/twisted/presence/presence.py b/tests/twisted/presence/presence.py index dea36a3..d722890 100644 --- a/tests/twisted/presence/presence.py +++ b/tests/twisted/presence/presence.py @@ -1,7 +1,5 @@ """ A simple smoke-test for C.I.SimplePresence - -FIXME: test C.I.Presence too """ import dbus @@ -9,13 +7,12 @@ import dbus from twisted.words.xish import domish, xpath from twisted.words.protocols.jabber.client import IQ +from servicetest import assertEquals from hazetest import exec_test +import constants as cs def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - - amy_handle = conn.RequestHandles(1, ['amy@foo.com'])[0] + amy_handle = conn.get_contact_handle_sync('amy@foo.com') # Divergence from Gabble: hazetest responds to all roster gets with an # empty roster, so we need to push the roster. @@ -51,6 +48,15 @@ def test(q, bus, conn, stream): # produces. assert event.args[0] == { amy_handle: (2, 'available', 'I may have been drinking') } + amy_handle, asv = conn.Contacts.GetContactByID('amy@foo.com', + [cs.CONN_IFACE_PRESENCE]) + assertEquals(event.args[0][amy_handle], asv.get(cs.ATTR_PRESENCE)) + + bob_handle, asv = conn.Contacts.GetContactByID('bob@foo.com', + [cs.CONN_IFACE_PRESENCE]) + assertEquals((cs.PRESENCE_UNKNOWN, 'unknown', ''), + asv.get(cs.ATTR_PRESENCE)) + conn.Disconnect() q.expect('dbus-signal', signal='StatusChanged', args=[2, 1]) diff --git a/tests/twisted/roster/groups.py b/tests/twisted/roster/groups.py index 1e38630..ab74c2a 100644 --- a/tests/twisted/roster/groups.py +++ b/tests/twisted/roster/groups.py @@ -8,8 +8,8 @@ from twisted.words.protocols.jabber.client import IQ from twisted.words.xish import domish, xpath from servicetest import (EventPattern, wrap_channel, assertLength, - assertEquals, call_async, sync_dbus, assertContains) -from hazetest import acknowledge_iq, exec_test, sync_stream, close_all_groups + assertEquals, call_async, sync_dbus, assertContains, assertSameSets) +from hazetest import acknowledge_iq, exec_test, sync_stream import constants as cs import ns @@ -17,25 +17,9 @@ import ns raise SystemExit(77) def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', - args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) - self_handle = conn.GetSelfHandle() - - # Close all Group channels to get a clean slate, so we can rely on - # the NewChannels signal for the default group later - close_all_groups(q, bus, conn, stream) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'subscribe', - }) - e = q.expect('dbus-return', method='EnsureChannel') - subscribe = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - - romeo, juliet, duncan = conn.RequestHandles(cs.HT_CONTACT, + self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") + + romeo, juliet, duncan = conn.get_contact_handles_sync( ['romeo@montague.lit', 'juliet@capulet.lit', 'duncan@scotland.lit']) @@ -70,94 +54,74 @@ def test(q, bus, conn, stream): sync_dbus(bus, q, conn) sync_stream(q, stream) - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_GROUP, - cs.TARGET_ID: 'Still alive', - }) - e = q.expect('dbus-return', method='EnsureChannel') - still_alive = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_GROUP, - cs.TARGET_ID: 'Capulets', - }) - e = q.expect('dbus-return', method='EnsureChannel') - capulets = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - # the XMPP prpl puts people into some sort of group, probably called # Buddies - channels = conn.Properties.Get(cs.CONN_IFACE_REQUESTS, 'Channels') + groups = conn.Properties.Get(cs.CONN_IFACE_CONTACT_GROUPS, 'Groups') default_group = None - default_props = None - - for path, props in channels: - if props.get(cs.CHANNEL_TYPE) != cs.CHANNEL_TYPE_CONTACT_LIST: - continue - if props.get(cs.TARGET_HANDLE_TYPE) != cs.HT_GROUP: - continue - - if props.get(cs.TARGET_ID) in ('Capulets', 'Still alive'): + for group in groups: + if group in ('Capulets', 'Still alive'): continue if default_group is not None: raise AssertionError('Two unexplained groups: %s, %s' % - (path, default_group.object_path)) + (group, default_group)) + + default_group = group - default_group = wrap_channel(bus.get_object(conn.bus_name, path), - cs.CHANNEL_TYPE_CONTACT_LIST) - default_group_name = props.get(cs.TARGET_ID) + call_async(q, conn.ContactList, 'GetContactListAttributes', + [cs.CONN_IFACE_CONTACT_GROUPS], False) + r = q.expect('dbus-return', method='GetContactListAttributes') - assertEquals(set([romeo, juliet]), set(still_alive.Group.GetMembers())) - assertEquals(set([juliet]), set(capulets.Group.GetMembers())) - assertEquals(set([duncan]), set(default_group.Group.GetMembers())) + assertSameSets(['Still alive'], r.value[0][romeo][cs.ATTR_GROUPS]) + assertSameSets(['Still alive', 'Capulets'], + r.value[0][juliet][cs.ATTR_GROUPS]) + assertSameSets([default_group], r.value[0][duncan][cs.ATTR_GROUPS]) # We can't remove Duncan from the default group, because it's his only # group - call_async(q, default_group.Group, 'RemoveMembers', [duncan], '') - q.expect('dbus-error', method='RemoveMembers', + call_async(q, conn.ContactGroups, 'RemoveFromGroup', default_group, + [duncan]) + q.expect('dbus-error', method='RemoveFromGroup', name=cs.NOT_AVAILABLE) + call_async(q, conn.ContactGroups, 'SetGroupMembers', default_group, + []) + q.expect('dbus-error', method='SetGroupMembers', + name=cs.NOT_AVAILABLE) + # SetContactGroups just doesn't do anything in this situation + call_async(q, conn.ContactGroups, 'SetContactGroups', duncan, []) + q.expect('dbus-return', method='SetContactGroups') + + call_async(q, conn.ContactList, 'GetContactListAttributes', + [cs.CONN_IFACE_CONTACT_GROUPS], False) + r = q.expect('dbus-return', method='GetContactListAttributes') + assertSameSets([default_group], r.value[0][duncan][cs.ATTR_GROUPS]) # Make a new group and add Duncan to it - call_async(q, conn.Requests, 'CreateChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_GROUP, - cs.TARGET_ID: 'Scots', - }) - e = q.expect('dbus-return', method='CreateChannel') - scots = wrap_channel(bus.get_object(conn.bus_name, e.value[0]), - cs.CHANNEL_TYPE_CONTACT_LIST) - assertEquals(set(), set(scots.Group.GetMembers())) - - call_async(q, scots.Group, 'AddMembers', [duncan], '') + call_async(q, conn.ContactGroups, 'AddToGroup', 'Scots', [duncan]) iq, _, _ = q.expect_many( EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), - EventPattern('dbus-signal', signal='MembersChanged', - path=scots.object_path, - args=['', [duncan], [], [], [], self_handle, cs.GC_REASON_NONE]), - EventPattern('dbus-return', method='AddMembers'), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[duncan], ['Scots'], []]), + EventPattern('dbus-return', method='AddToGroup'), ) assertEquals('duncan@scotland.lit', iq.stanza.query.item['jid']) groups = set([str(x) for x in xpath.queryForNodes('/iq/query/item/group', iq.stanza)]) assertLength(2, groups) - assertContains(default_group_name, groups) + assertContains(default_group, groups) assertContains('Scots', groups) # Now we can remove him from the default group. Much rejoicing. - call_async(q, default_group.Group, 'RemoveMembers', [duncan], '') + call_async(q, conn.ContactGroups, 'RemoveFromGroup', default_group, + [duncan]) iq, _, _ = q.expect_many( EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), - EventPattern('dbus-signal', signal='MembersChanged', - path=default_group.object_path, - args=['', [], [duncan], [], [], self_handle, cs.GC_REASON_NONE]), - EventPattern('dbus-return', method='RemoveMembers'), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[duncan], [], [default_group]]), + EventPattern('dbus-return', method='RemoveFromGroup'), ) assertEquals('duncan@scotland.lit', iq.stanza.query.item['jid']) groups = set([str(x) for x in xpath.queryForNodes('/iq/query/item/group', @@ -165,22 +129,49 @@ def test(q, bus, conn, stream): assertLength(1, groups) assertContains('Scots', groups) + # Test SetContactGroups, which didn't previously have proper coverage + call_async(q, conn.ContactGroups, 'SetContactGroups', duncan, + ['Scottish former kings']) + iq, _, _, _ = q.expect_many( + EventPattern('stream-iq', iq_type='set', query_name='query', + query_ns=ns.ROSTER), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[duncan], ['Scottish former kings'], []]), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[duncan], [], ['Scots']]), + EventPattern('dbus-return', method='SetContactGroups'), + ) + assertEquals('duncan@scotland.lit', iq.stanza.query.item['jid']) + groups = set([str(x) for x in xpath.queryForNodes('/iq/query/item/group', + iq.stanza)]) + assertLength(2, groups) + assertContains('Scots', groups) + assertContains('Scottish former kings', groups) + iq, = q.expect_many( + EventPattern('stream-iq', iq_type='set', query_name='query', + query_ns=ns.ROSTER), + ) + assertEquals('duncan@scotland.lit', iq.stanza.query.item['jid']) + groups = set([str(x) for x in xpath.queryForNodes('/iq/query/item/group', + iq.stanza)]) + assertLength(1, groups) + assertContains('Scottish former kings', groups) + # Romeo dies. If he drops off the roster as a result, that would be # fd.o #21294. However, to fix that bug, Haze now puts him in the # default group. - call_async(q, still_alive.Group, 'RemoveMembers', [romeo], '') + call_async(q, conn.ContactGroups, 'RemoveFromGroup', 'Still alive', + [romeo]) iq1, iq2, _, _, _ = q.expect_many( EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), - EventPattern('dbus-signal', signal='MembersChanged', - path=still_alive.object_path, - args=['', [], [romeo], [], [], self_handle, cs.GC_REASON_NONE]), - EventPattern('dbus-signal', signal='MembersChanged', - path=default_group.object_path, - args=['', [romeo], [], [], [], self_handle, cs.GC_REASON_NONE]), - EventPattern('dbus-return', method='RemoveMembers'), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[romeo], [default_group], []]), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[romeo], [], ['Still alive']]), + EventPattern('dbus-return', method='RemoveFromGroup'), ) assertEquals('romeo@montague.lit', iq1.stanza.query.item['jid']) @@ -188,24 +179,23 @@ def test(q, bus, conn, stream): iq1.stanza)]) assertLength(2, groups) assertContains('Still alive', groups) - assertContains(default_group_name, groups) + assertContains(default_group, groups) assertEquals('romeo@montague.lit', iq2.stanza.query.item['jid']) groups = set([str(x) for x in xpath.queryForNodes('/iq/query/item/group', iq2.stanza)]) assertLength(1, groups) - assertContains(default_group_name, groups) + assertContains(default_group, groups) # Juliet dies. She's in another group already, so the workaround for # fd.o #21294 is not active. - call_async(q, still_alive.Group, 'RemoveMembers', [juliet], '') + call_async(q, conn.ContactGroups, 'SetGroupMembers', 'Still alive', []) iq, _, _ = q.expect_many( EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), - EventPattern('dbus-signal', signal='MembersChanged', - path=still_alive.object_path, - args=['', [], [juliet], [], [], self_handle, cs.GC_REASON_NONE]), - EventPattern('dbus-return', method='RemoveMembers'), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[juliet], [], ['Still alive']]), + EventPattern('dbus-return', method='SetGroupMembers'), ) assertEquals('juliet@capulet.lit', iq.stanza.query.item['jid']) groups = set([str(x) for x in xpath.queryForNodes('/iq/query/item/group', @@ -215,17 +205,19 @@ def test(q, bus, conn, stream): # At the end of a tragedy, everyone dies, so there's no need for this # group. - call_async(q, still_alive, 'Close') - q.expect('dbus-signal', signal='Closed', path=still_alive.object_path) - q.expect('dbus-return', method='Close') - - # Deleting a non-empty group is not allowed. - call_async(q, capulets, 'Close') - q.expect('dbus-error', method='Close', name=cs.NOT_AVAILABLE) - - # Neither is deleting a List channel. - call_async(q, subscribe, 'Close') - q.expect('dbus-error', method='Close', name=cs.NOT_IMPLEMENTED) + call_async(q, conn.ContactGroups, 'RemoveGroup', 'Still alive') + q.expect('dbus-signal', signal='GroupsRemoved', args=[['Still alive']]) + + # Deleting a non-empty group is allowed. (It removes everyone.) + call_async(q, conn.ContactGroups, 'RemoveGroup', 'Capulets') + q.expect_many( + EventPattern('dbus-signal', signal='GroupsRemoved', + args=[['Capulets']]), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[juliet], [default_group], []]), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[juliet], [], ['Capulets']]), + ) if __name__ == '__main__': exec_test(test) diff --git a/tests/twisted/roster/initial-roster.py b/tests/twisted/roster/initial-roster.py index 0fedf63..7fa9c96 100644 --- a/tests/twisted/roster/initial-roster.py +++ b/tests/twisted/roster/initial-roster.py @@ -6,18 +6,10 @@ import dbus from hazetest import exec_test, JabberXmlStream from servicetest import (assertLength, EventPattern, wrap_channel, - assertEquals, call_async) + assertEquals, call_async, assertSameSets) import constants as cs import ns -# TODO: this needs to be ported to Connection.ContactList -raise SystemExit(77) - -class RosterXmlStream(JabberXmlStream): - def add_roster_observer(self): - # don't wait for the roster IQ before continuing into the test - pass - def test(q, bus, conn, stream): conn.Connect() @@ -50,97 +42,69 @@ def test(q, bus, conn, stream): stream.send(event.stanza) - q.expect('dbus-signal', signal='StatusChanged', - args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) - - # Amy, Bob and Chris are all stored on our server-side roster - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'stored', - }) - e = q.expect('dbus-return', method='EnsureChannel') - stored = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - jids = set(conn.InspectHandles(cs.HT_CONTACT, stored.Group.GetMembers())) - assertEquals(set(['amy@foo.com', 'bob@foo.com', 'chris@foo.com']), jids) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'subscribe', - }) - e = q.expect('dbus-return', method='EnsureChannel') - subscribe = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - jids = set(conn.InspectHandles(cs.HT_CONTACT, subscribe.Group.GetMembers())) - # everyone on our roster is (falsely!) alleged to be on 'subscribe' - # (in fact this ought to be just Amy and Chris, but libpurple apparently - # can't represent this) - assertEquals(set(['amy@foo.com', 'bob@foo.com', 'chris@foo.com']), jids) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'publish', - }) - e = q.expect('dbus-return', method='EnsureChannel') - publish = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - jids = set(conn.InspectHandles(cs.HT_CONTACT, publish.Group.GetMembers())) - # the publish list is somewhat imaginary because libpurple doesn't have - # state-recovery - assertEquals(set(), jids) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_GROUP, - cs.TARGET_ID: '3 letter names', - }) - e = q.expect('dbus-return', method='EnsureChannel') - group_chan = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - jids = set(conn.InspectHandles(cs.HT_CONTACT, - group_chan.Group.GetMembers())) - assertEquals(set(['amy@foo.com', 'bob@foo.com']), jids) + _, s, _ = q.expect_many( + EventPattern('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]), + EventPattern('dbus-signal', signal='ContactsChanged', + interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path), + EventPattern('dbus-signal', signal='ContactListStateChanged', + args=[cs.CONTACT_LIST_STATE_SUCCESS]), + ) + + amy, bob, chris = conn.get_contact_handles_sync( + ['amy@foo.com', 'bob@foo.com', 'chris@foo.com']) + + # Amy, Bob and Chris are all stored on our server-side roster. + # + # Everyone on our roster is (falsely!) alleged to have subscribe=YES + # (in fact this ought to be just Amy and Chris, because we're publishing + # presence to Bob without being subscribed to his presence, but libpurple + # apparently can't represent this). + # + # The publish value is unknown, because libpurple doesn't have + # state-recovery. + assertEquals([{ + amy: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_UNKNOWN, ''), + bob: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_UNKNOWN, ''), + chris: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_UNKNOWN, ''), + }, + { + amy: 'amy@foo.com', + bob: 'bob@foo.com', + chris: 'chris@foo.com', + }, + {}], s.args) # the XMPP prpl puts people into some sort of group, probably called # Buddies - channels = conn.Properties.Get(cs.CONN_IFACE_REQUESTS, 'Channels') + groups = conn.Properties.Get(cs.CONN_IFACE_CONTACT_GROUPS, 'Groups') default_group = None - default_props = None - - for path, props in channels: - if props.get(cs.CHANNEL_TYPE) != cs.CHANNEL_TYPE_CONTACT_LIST: - continue - if props.get(cs.TARGET_HANDLE_TYPE) != cs.HT_GROUP: - continue - - if path == group_chan.object_path: + for group in groups: + if group == '3 letter names': continue if default_group is not None: raise AssertionError('Two unexplained groups: %s, %s' % - (path, default_group.object_path)) - - default_group = wrap_channel(bus.get_object(conn.bus_name, path), - cs.CHANNEL_TYPE_CONTACT_LIST) - default_props = props - - jids = set(conn.InspectHandles(cs.HT_CONTACT, - default_group.Group.GetMembers())) - assertEquals(set(['chris@foo.com']), jids) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_GROUP, - cs.TARGET_ID: default_props[cs.TARGET_ID], - }) - e = q.expect('dbus-return', method='EnsureChannel') - assertEquals(False, e.value[0]) - assertEquals(default_group.object_path, e.value[1]) - assertEquals(default_props, e.value[2]) + (group, default_group)) + + default_group = group + + call_async(q, conn.ContactList, 'GetContactListAttributes', + [cs.CONN_IFACE_CONTACT_GROUPS]) + r = q.expect('dbus-return', method='GetContactListAttributes') + + assertEquals(cs.SUBSCRIPTION_STATE_YES, r.value[0][amy][cs.ATTR_SUBSCRIBE]) + assertEquals(cs.SUBSCRIPTION_STATE_YES, r.value[0][bob][cs.ATTR_SUBSCRIBE]) + assertEquals(cs.SUBSCRIPTION_STATE_YES, r.value[0][chris][cs.ATTR_SUBSCRIBE]) + + assertEquals(cs.SUBSCRIPTION_STATE_UNKNOWN, r.value[0][amy][cs.ATTR_PUBLISH]) + assertEquals(cs.SUBSCRIPTION_STATE_UNKNOWN, r.value[0][bob][cs.ATTR_PUBLISH]) + assertEquals(cs.SUBSCRIPTION_STATE_UNKNOWN, r.value[0][chris][cs.ATTR_PUBLISH]) + + assertSameSets(['3 letter names'], r.value[0][amy][cs.ATTR_GROUPS]) + assertSameSets(['3 letter names'], r.value[0][bob][cs.ATTR_GROUPS]) + assertSameSets([default_group], r.value[0][chris][cs.ATTR_GROUPS]) if __name__ == '__main__': - exec_test(test, protocol=RosterXmlStream) + exec_test(test, protocol=JabberXmlStream, do_connect=False) diff --git a/tests/twisted/roster/publish.py b/tests/twisted/roster/publish.py index 69e400f..3b83e18 100644 --- a/tests/twisted/roster/publish.py +++ b/tests/twisted/roster/publish.py @@ -8,8 +8,8 @@ from twisted.words.protocols.jabber.client import IQ from twisted.words.xish import domish from servicetest import (EventPattern, wrap_channel, assertLength, - assertEquals, call_async, sync_dbus) -from hazetest import acknowledge_iq, exec_test, sync_stream, close_all_groups + assertEquals, call_async, sync_dbus, assertSameSets) +from hazetest import acknowledge_iq, exec_test, sync_stream import constants as cs import ns @@ -17,49 +17,13 @@ import ns raise SystemExit(77) def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', - args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) - - # Close all Group channels to get a clean slate, so we can rely on - # the NewChannels signal for the default group later - close_all_groups(q, bus, conn, stream) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'publish', - }) - e = q.expect('dbus-return', method='EnsureChannel') - publish = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - jids = set(conn.InspectHandles(cs.HT_CONTACT, publish.Group.GetMembers())) - assertEquals(set(), jids) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'stored', - }) - e = q.expect('dbus-return', method='EnsureChannel') - stored = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - jids = set(conn.InspectHandles(cs.HT_CONTACT, stored.Group.GetMembers())) - assertEquals(set(), jids) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'subscribe', - }) - e = q.expect('dbus-return', method='EnsureChannel') - subscribe = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - jids = set(conn.InspectHandles(cs.HT_CONTACT, subscribe.Group.GetMembers())) - assertEquals(set(), jids) + call_async(q, conn.ContactList, 'GetContactListAttributes', + [cs.CONN_IFACE_CONTACT_GROUPS], False) + r = q.expect('dbus-return', method='GetContactListAttributes') + assertLength(0, r.value[0].keys()) # receive a subscription request - alice = conn.RequestHandles(cs.HT_CONTACT, ['alice@wonderland.lit'])[0] + alice = conn.get_contact_handle_sync('alice@wonderland.lit') presence = domish.Element(('jabber:client', 'presence')) presence['from'] = 'alice@wonderland.lit' @@ -69,31 +33,31 @@ def test(q, bus, conn, stream): # it seems either libpurple or haze doesn't pass the message through q.expect_many( - EventPattern('dbus-signal', path=publish.object_path, - args=['', [], [], [alice], [], alice, - cs.GC_REASON_NONE]), - # In the Conn.I.ContactList world, 'stored' has been - # re-purposed to mean "we have some reason to care", so she - # appears here even though she's not on the server-side roster - # just yet - EventPattern('dbus-signal', signal='MembersChanged', - path=stored.object_path, - args=['', [alice], [], [], [], 0, cs.GC_REASON_NONE]), + EventPattern('dbus-signal', signal='ContactsChanged', + args=[{ + alice: + (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, + ''), + }, + {alice: 'alice@wonderland.lit'}, {}]), ) - self_handle = conn.GetSelfHandle() + self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") # accept - call_async(q, publish.Group, 'AddMembers', [alice], '') + call_async(q, conn.ContactList, 'AuthorizePublication', [alice]) q.expect_many( EventPattern('stream-presence', presence_type='subscribed', to='alice@wonderland.lit'), - EventPattern('dbus-signal', signal='MembersChanged', - path=publish.object_path, - args=['', [alice], [], [], [], self_handle, - cs.GC_REASON_NONE]), - EventPattern('dbus-return', method='AddMembers'), + EventPattern('dbus-signal', signal='ContactsChanged', + args=[{ + alice: + (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_YES, + ''), + }, + {alice: 'alice@wonderland.lit'}, {}]), + EventPattern('dbus-return', method='AuthorizePublication'), ) # the server sends us a roster push @@ -106,33 +70,30 @@ def test(q, bus, conn, stream): stream.send(iq) - _, _, new_group = q.expect_many( + q.expect_many( EventPattern('stream-iq', iq_type='result', predicate=lambda e: e.stanza['id'] == 'roster-push'), # She's not really on our subscribe list, but this is the closest # we can guess from libpurple - # FIXME: TpBaseContactList assumes she's the actor - she must have - # accepted our request, right? Not actually true in libpurple. - EventPattern('dbus-signal', signal='MembersChanged', - path=subscribe.object_path, - args=['', [alice], [], [], [], alice, cs.GC_REASON_NONE]), + EventPattern('dbus-signal', signal='ContactsChanged', + args=[{ + alice: + (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_YES, + ''), + }, + {alice: 'alice@wonderland.lit'}, {}]), # the buddy needs a group, because libpurple - EventPattern('dbus-signal', signal='NewChannels', - predicate=lambda e: - e.args[0][0][1].get(cs.CHANNEL_TYPE) == - cs.CHANNEL_TYPE_CONTACT_LIST and - e.args[0][0][1].get(cs.TARGET_HANDLE_TYPE) == - cs.HT_GROUP), + EventPattern('dbus-signal', signal='GroupsChanged', + predicate=lambda e: e.args[0] == [alice]), ) - def_group = wrap_channel(bus.get_object(conn.bus_name, - new_group.args[0][0][0]), cs.CHANNEL_TYPE_CONTACT_LIST) - - assertEquals(set([alice]), set(def_group.Group.GetMembers())) + call_async(q, conn.ContactList, 'GetContactListAttributes', + [cs.CONN_IFACE_CONTACT_GROUPS], False) + r = q.expect('dbus-return', method='GetContactListAttributes') + assertSameSets([alice], r.value[0].keys()) # receive another subscription request - queen = conn.RequestHandles(cs.HT_CONTACT, - ['queen.of.hearts@wonderland.lit'])[0] + queen = conn.get_contact_handle_sync('queen.of.hearts@wonderland.lit') presence = domish.Element(('jabber:client', 'presence')) presence['from'] = 'queen.of.hearts@wonderland.lit' @@ -141,27 +102,45 @@ def test(q, bus, conn, stream): stream.send(presence) # it seems either libpurple or haze doesn't pass the message through - q.expect('dbus-signal', path=publish.object_path, - args=['', [], [], [queen], [], queen, - cs.GC_REASON_NONE]) + q.expect_many( + EventPattern('dbus-signal', signal='ContactsChanged', + args=[{ + queen: + (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, + ''), + }, + {queen: 'queen.of.hearts@wonderland.lit'}, {}]), + ) + + # the contact is temporarily on our roster + call_async(q, conn.ContactList, 'GetContactListAttributes', + [cs.CONN_IFACE_CONTACT_GROUPS], False) + r = q.expect('dbus-return', method='GetContactListAttributes') + assertSameSets([alice, queen], r.value[0].keys()) # decline - call_async(q, publish.Group, 'RemoveMembers', [queen], '') + call_async(q, conn.ContactList, 'RemoveContacts', [queen]) q.expect_many( EventPattern('stream-presence', presence_type='unsubscribed', to='queen.of.hearts@wonderland.lit'), - EventPattern('dbus-signal', signal='MembersChanged', - path=publish.object_path, - args=['', [], [queen], [], [], 0, cs.GC_REASON_NONE]), - EventPattern('dbus-return', method='RemoveMembers'), + EventPattern('dbus-signal', signal='ContactsChanged', + args=[{ + queen: + (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, + ''), + }, {queen: 'queen.of.hearts@wonderland.lit'}, {}]), + EventPattern('dbus-return', method='RemoveContacts'), ) sync_dbus(bus, q, conn) sync_stream(q, stream) - # the declined contact isn't on our roster - assertEquals(set([alice]), set(def_group.Group.GetMembers())) + # the declined contact isn't on our roster any more + call_async(q, conn.ContactList, 'GetContactListAttributes', + [cs.CONN_IFACE_CONTACT_GROUPS], False) + r = q.expect('dbus-return', method='GetContactListAttributes') + assertSameSets([alice], r.value[0].keys()) # she's persistent presence = domish.Element(('jabber:client', 'presence')) @@ -169,9 +148,17 @@ def test(q, bus, conn, stream): presence['type'] = 'subscribe' presence.addElement('status', content='How dare you?') stream.send(presence) - q.expect('dbus-signal', path=publish.object_path, - args=['', [], [], [queen], [], queen, - cs.GC_REASON_NONE]) + + q.expect_many( + EventPattern('dbus-signal', signal='ContactsChanged', + args=[{ + queen: + (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, + ''), + }, + {queen: 'queen.of.hearts@wonderland.lit'}, {}]), + ) + # disconnect with the request outstanding, to make sure we don't crash conn.Disconnect() q.expect('dbus-signal', signal='StatusChanged', diff --git a/tests/twisted/roster/removed-from-rp-subscribe.py b/tests/twisted/roster/removed-from-rp-subscribe.py index 339db05..e48ae41 100644 --- a/tests/twisted/roster/removed-from-rp-subscribe.py +++ b/tests/twisted/roster/removed-from-rp-subscribe.py @@ -16,38 +16,7 @@ raise SystemExit(77) jid = 'marco@barisione.lit' def test(q, bus, conn, stream, remove, local): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', - args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'subscribe', - }) - e = q.expect('dbus-return', method='EnsureChannel') - subscribe = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'stored', - }) - e = q.expect('dbus-return', method='EnsureChannel') - stored = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'publish', - }) - e = q.expect('dbus-return', method='EnsureChannel') - publish = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - - h = conn.RequestHandles(cs.HT_CONTACT, [jid])[0] + h = conn.get_contact_handle_sync(jid) # Another client logged into our account (Gajim, say) wants to subscribe to # Marco's presence. First, per RFC 3921 it 'SHOULD perform a "roster set" @@ -71,12 +40,13 @@ def test(q, bus, conn, stream, remove, local): # In response, Haze adds Marco to the roster, which we guess (wrongly, # in this case) also means subscribe - q.expect_many( - EventPattern('dbus-signal', signal='MembersChanged', - args=['', [h], [], [], [], h, 0], path=subscribe.object_path), - EventPattern('dbus-signal', signal='MembersChanged', - args=['', [h], [], [], [], 0, 0], path=stored.object_path), - ) + q.expect('dbus-signal', signal='ContactsChanged', + args=[{ + h: + (cs.SUBSCRIPTION_STATE_YES, + cs.SUBSCRIPTION_STATE_UNKNOWN, ''), + }, + {h: jid}, {}]) # Gajim sends a <presence type='subscribe'/> to Marco. 'As a result, the # user's server MUST initiate a second roster push to all of the user's @@ -97,8 +67,8 @@ def test(q, bus, conn, stream, remove, local): if remove: # ...removes him from the roster... if local: - # ...by telling Haze to remove him from stored - stored.Group.RemoveMembers([h], '') + # ...by telling Haze to remove him from the roster + conn.ContactList.RemoveContacts([h]) event = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER) item = event.query.firstChildElement() @@ -122,15 +92,9 @@ def test(q, bus, conn, stream, remove, local): stream.send(iq) # In response, Haze should announce that Marco has been removed from - # subscribe:remote-pending and stored:members - q.expect_many( - EventPattern('dbus-signal', signal='MembersChanged', - args=['', [], [h], [], [], 0, 0], - path=subscribe.object_path), - EventPattern('dbus-signal', signal='MembersChanged', - args=['', [], [h], [], [], 0, 0], - path=stored.object_path), - ) + # the roster + q.expect('dbus-signal', signal='ContactsChanged', + args=[{}, {}, {h: jid}]) else: # ...rescinds the subscription request... if local: diff --git a/tests/twisted/roster/subscribe.py b/tests/twisted/roster/subscribe.py index 539feef..ebd325b 100644 --- a/tests/twisted/roster/subscribe.py +++ b/tests/twisted/roster/subscribe.py @@ -8,7 +8,7 @@ from twisted.words.xish import domish from servicetest import (EventPattern, wrap_channel, assertLength, assertEquals, call_async, sync_dbus) -from hazetest import acknowledge_iq, exec_test, sync_stream, close_all_groups +from hazetest import acknowledge_iq, exec_test, sync_stream import constants as cs import ns @@ -16,55 +16,46 @@ import ns raise SystemExit(77) def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', - args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) - self_handle = conn.GetSelfHandle() - - # Close all Group channels to get a clean slate, so we can rely on - # the NewChannels signal for the default group later - close_all_groups(q, bus, conn, stream) - - call_async(q, conn.Requests, 'EnsureChannel',{ - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, - cs.TARGET_HANDLE_TYPE: cs.HT_LIST, - cs.TARGET_ID: 'subscribe', - }) - e = q.expect('dbus-return', method='EnsureChannel') - subscribe = wrap_channel(bus.get_object(conn.bus_name, e.value[1]), - cs.CHANNEL_TYPE_CONTACT_LIST) - jids = set(conn.InspectHandles(cs.HT_CONTACT, subscribe.Group.GetMembers())) - assertEquals(set(), jids) - - assertLength(0, subscribe.Group.GetMembers()) + self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") + + call_async(q, conn.ContactList, 'GetContactListAttributes', + [cs.CONN_IFACE_CONTACT_GROUPS], False) + r = q.expect('dbus-return', method='GetContactListAttributes') + assertLength(0, r.value[0].keys()) # request subscription - handle = conn.RequestHandles(cs.HT_CONTACT, ['suggs@night.boat.cairo'])[0] - call_async(q, subscribe.Group, 'AddMembers', [handle], '') + handle = conn.get_contact_handle_sync('suggs@night.boat.cairo') + call_async(q, conn.ContactList, 'RequestSubscription', [handle], + 'half past monsoon') # libpurple puts him on our blist as soon as we've asked; there doesn't # seem to be any concept of remote-pending state. # # It also puts him in the default group, probably "Buddies". - set_iq, _, _, _, new_channels = q.expect_many( + set_iq, _, _, _, groups_changed = q.expect_many( EventPattern('stream-iq', iq_type='set', query_ns=ns.ROSTER, query_name='query'), EventPattern('stream-presence', presence_type='subscribe', to='suggs@night.boat.cairo'), - EventPattern('dbus-return', method='AddMembers', value=()), - # FIXME: TpBaseContactList wrongly assumes that he's the actor, - # because he must have accepted our request... right? Wrong. - EventPattern('dbus-signal', signal='MembersChanged', - path=subscribe.object_path, - args=['', [handle], [], [], [], handle, 0]), - EventPattern('dbus-signal', signal='NewChannels', - predicate=lambda e: - e.args[0][0][1].get(cs.TARGET_HANDLE_TYPE) == cs.HT_GROUP), + EventPattern('dbus-return', method='RequestSubscription', value=()), + EventPattern('dbus-signal', signal='ContactsChanged', + args=[{ + handle: + (cs.SUBSCRIPTION_STATE_YES, + cs.SUBSCRIPTION_STATE_UNKNOWN, ''), + }, + {handle: 'suggs@night.boat.cairo'}, {}]), + EventPattern('dbus-signal', signal='GroupsChanged', + predicate=lambda e: e.args[0] == [handle]), ) assertEquals('suggs@night.boat.cairo', set_iq.query.item['jid']) acknowledge_iq(stream, set_iq.stanza) + assertLength(1, groups_changed.args[1]) + assertLength(0, groups_changed.args[2]) + def_group = groups_changed.args[1][0] + # Suggs accepts our subscription request presence = domish.Element(('jabber:client', 'presence')) presence['from'] = 'suggs@night.boat.cairo' @@ -74,15 +65,10 @@ def test(q, bus, conn, stream): # ... but nothing much happens, because there's no concept of pending # state in libpurple - def_group = wrap_channel(bus.get_object(conn.bus_name, - new_channels.args[0][0][0]), cs.CHANNEL_TYPE_CONTACT_LIST) - handles = set(subscribe.Group.GetMembers()) - assertEquals(set([handle]), handles) - # put a contact into the *group* explicitly: this shouldn't ask for - # subscription, but it does - handle = conn.RequestHandles(cs.HT_CONTACT, ['ayria@revenge.world'])[0] - call_async(q, def_group.Group, 'AddMembers', [handle], '') + # subscription, but it does, because libpurple + handle = conn.get_contact_handle_sync('ayria@revenge.world') + call_async(q, conn.ContactGroups, 'AddToGroup', def_group, [handle]) # libpurple puts her on our blist as soon as we've asked; there doesn't # seem to be any concept of remote-pending state. It also puts her in the @@ -92,13 +78,16 @@ def test(q, bus, conn, stream): query_ns=ns.ROSTER, query_name='query'), EventPattern('stream-presence', presence_type='subscribe', to='ayria@revenge.world'), - EventPattern('dbus-return', method='AddMembers', value=()), - EventPattern('dbus-signal', signal='MembersChanged', - path=subscribe.object_path, - args=['', [handle], [], [], [], handle, 0]), - EventPattern('dbus-signal', signal='MembersChanged', - path=def_group.object_path, - args=['', [handle], [], [], [], self_handle, 0]), + EventPattern('dbus-return', method='AddToGroup', value=()), + EventPattern('dbus-signal', signal='ContactsChanged', + args=[{ + handle: + (cs.SUBSCRIPTION_STATE_YES, + cs.SUBSCRIPTION_STATE_UNKNOWN, ''), + }, + {handle: 'ayria@revenge.world'}, {}]), + EventPattern('dbus-signal', signal='GroupsChanged', + args=[[handle], [def_group], []]), ) acknowledge_iq(stream, set_iq.stanza) diff --git a/tests/twisted/run-test.sh.in b/tests/twisted/run-test.sh.in new file mode 100644 index 0000000..11bbaf9 --- /dev/null +++ b/tests/twisted/run-test.sh.in @@ -0,0 +1,69 @@ +#!/bin/sh + +if test "x$HAZE_TEST_UNINSTALLED" = x; then + script_fullname=`readlink -e "@hazetestsdir@/twisted/run-test.sh"` + if [ `readlink -e "$0"` != "$script_fullname" ] ; then + echo "This script is meant to be installed at $script_fullname" >&2 + exit 1 + fi + + test_src="@hazetestsdir@" + test_build="@hazetestsdir@" + config_file="@hazetestsdir@/twisted/tools/servicedir/tmp-session-bus.conf" + + PYTHONPATH="@hazetestsdir@/twisted" + export PYTHONPATH + + HAZE_TWISTED_PATH="@hazetestsdir@/twisted" + export HAZE_TWISTED_PATH +else + if test -z "$HAZE_ABS_TOP_SRCDIR"; then + echo "HAZE_ABS_TOP_SRCDIR must be set" >&2 + exit 1 + fi + if test -z "$HAZE_ABS_TOP_BUILDDIR"; then + echo "HAZE_ABS_TOP_BUILDDIR must be set" >&2 + exit 1 + fi + + test_src="${HAZE_ABS_TOP_SRCDIR}/tests" + test_build="${HAZE_ABS_TOP_BUILDDIR}/tests" + config_file="${test_build}/twisted/tools/tmp-session-bus.conf" + + PYTHONPATH="${test_src}/twisted:${test_build}/twisted" + export PYTHONPATH + + HAZE_TWISTED_PATH="${test_src}/twisted" + export HAZE_TWISTED_PATH +fi + +if [ -n "$1" ] ; then + list="$1" +else + list=$(cat "${test_build}"/twisted/haze-twisted-tests.list) +fi + +any_failed=0 +for i in $list ; do + echo "Testing $i ..." + sh "${test_src}/twisted/tools/with-session-bus.sh" \ + ${HAZE_TEST_SLEEP} \ + --config-file="${config_file}" \ + -- \ + @TEST_PYTHON@ -u "${test_src}/twisted/$i" + e=$? + case "$e" in + (0) + echo "PASS: $i" + ;; + (77) + echo "SKIP: $i" + ;; + (*) + any_failed=1 + echo "FAIL: $i ($e)" + ;; + esac +done + +exit $any_failed diff --git a/tests/twisted/sasl/close.py b/tests/twisted/sasl/close.py new file mode 100644 index 0000000..a7d26f8 --- /dev/null +++ b/tests/twisted/sasl/close.py @@ -0,0 +1,43 @@ +"""Test the SASL channel being undispatchable.""" + +import dbus + +from servicetest import EventPattern +from hazetest import exec_test, assertEquals +import constants as cs +from saslutil import connect_and_get_sasl_channel + +JID = 'weaver@crobuzon.fic' + +def test_no_password(q, bus, conn, stream): + chan, props = connect_and_get_sasl_channel(q, bus, conn) + + chan.Close() + + _, _, status_changed = q.expect_many( + EventPattern('dbus-signal', path=chan.object_path, + signal='Closed'), + EventPattern('dbus-signal', path=conn.object_path, + signal='ChannelClosed', args=[chan.object_path]), + # Unhelpfully prpl-jabber just sets the account to disabled so we + # don't get an error. + # EventPattern('dbus-signal', path=conn.object_path, + # signal='ConnectionError', + # predicate=lambda e: e.args[0] == cs.AUTHENTICATION_FAILED), + EventPattern('dbus-signal', path=conn.object_path, + signal='StatusChanged'), + ) + + status, reason = status_changed.args + assertEquals(cs.CONN_STATUS_DISCONNECTED, status) + # We would like to have + # assertEquals(cs.CSR_AUTHENTICATION_FAILED, reason) + + # prpl-sipe does actually report a connection error rather + # than just disabling the account, so yay. prpl-silc sets + # PURPLE_CONNECTION_ERROR_OTHER_ERROR, which also comes out as + # AUTHENTICATION_FAILED. No other prpls use + # purple_account_request_password(). + +if __name__ == '__main__': + exec_test(test_no_password, {'password': None,'account' : JID}, do_connect=False) diff --git a/tests/twisted/sasl/saslutil.py b/tests/twisted/sasl/saslutil.py new file mode 100644 index 0000000..83afe2b --- /dev/null +++ b/tests/twisted/sasl/saslutil.py @@ -0,0 +1,130 @@ +# hey, Python: encoding: utf-8 +from hazetest import XmppAuthenticator +from base64 import b64decode, b64encode +from twisted.words.xish import domish +import constants as cs +import ns +from servicetest import (ProxyWrapper, EventPattern, assertEquals, + assertLength, Event) + +class SaslChannelWrapper(ProxyWrapper): + def __init__(self, object, default=cs.CHANNEL, interfaces={ + "ServerAuthentication" : cs.CHANNEL_TYPE_SERVER_AUTHENTICATION, + "SASLAuthentication" : cs.CHANNEL_IFACE_SASL_AUTH}): + ProxyWrapper.__init__(self, object, default, interfaces) + +class SaslEventAuthenticator(XmppAuthenticator): + def __init__(self, jid, mechanisms): + XmppAuthenticator.__init__(self, jid, '') + self._mechanisms = mechanisms + + def streamSASL(self): + XmppAuthenticator.streamSASL(self) + + self.xmlstream.addObserver("/response", self._response) + self.xmlstream.addObserver("/abort", self._abort) + + def failure(self, fail_str): + reply = domish.Element((ns.NS_XMPP_SASL, 'failure')) + reply.addElement(fail_str) + self.xmlstream.send(reply) + self.xmlstream.reset() + + def abort(self): + self.failure('abort') + + def not_authorized(self): + self.failure('not-authorized') + + def success(self, data=None): + reply = domish.Element((ns.NS_XMPP_SASL, 'success')) + + if data is not None: + reply.addContent(b64encode(data)) + + self.xmlstream.send(reply) + self.authenticated=True + self.xmlstream.reset() + + def challenge(self, data): + reply = domish.Element((ns.NS_XMPP_SASL, 'challenge')) + reply.addContent(b64encode(data)) + self.xmlstream.send(reply) + + def auth(self, auth): + # Special case in XMPP: '=' means a zero-byte blob, whereas an empty + # or self-terminating XML element means no initial response. + # (RFC 3920 §6.2 (3)) + if str(auth) == '': + self._event_func(Event('sasl-auth', authenticator=self, + has_initial_response=False, + initial_response=None, + xml=auth)) + elif str(auth) == '=': + self._event_func(Event('sasl-auth', authenticator=self, + has_initial_response=False, + initial_response=None, + xml=auth)) + else: + self._event_func(Event('sasl-auth', authenticator=self, + has_initial_response=True, + initial_response=b64decode(str(auth)), + xml=auth)) + + def _response(self, response): + self._event_func(Event('sasl-response', authenticator=self, + response=b64decode(str(response)), + xml=response)) + + def _abort(self, abort): + self._event_func(Event('sasl-abort', authenticator=self, + xml=abort)) + +def connect_and_get_sasl_channel(q, bus, conn): + conn.Connect() + + q.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED]) + + return expect_sasl_channel(q, bus, conn) + +def expect_sasl_channel(q, bus, conn): + signal, = q.expect_many( + EventPattern('dbus-signal', signal='NewChannels', + predicate=lambda e: + e.args[0][0][1].get(cs.CHANNEL_TYPE) == + cs.CHANNEL_TYPE_SERVER_AUTHENTICATION), + ) + path = signal.args[0][0][0] + + chan = SaslChannelWrapper(bus.get_object(conn.bus_name, path)) + assertLength(1, signal.args[0]) + props = signal.args[0][0][1] + + assertEquals(cs.CHANNEL_IFACE_SASL_AUTH, props.get(cs.AUTH_METHOD)) + return chan, props + +def abort_auth(q, chan, reason, message): + reason_err_map = { + cs.SASL_ABORT_REASON_USER_ABORT : cs.CANCELLED, + cs.SASL_ABORT_REASON_INVALID_CHALLENGE : cs.SERVICE_CONFUSED } + + mapped_error = reason_err_map.get(reason, cs.CANCELLED) + + chan.SASLAuthentication.AbortSASL(reason, message) + + ssc, ce, _ = q.expect_many( + EventPattern( + 'dbus-signal', signal='SASLStatusChanged', + interface=cs.CHANNEL_IFACE_SASL_AUTH, + predicate=lambda e: e.args[0] == cs.SASL_STATUS_CLIENT_FAILED), + EventPattern('dbus-signal', signal='ConnectionError'), + EventPattern( + 'dbus-signal', signal="StatusChanged", + args=[cs.CONN_STATUS_DISCONNECTED, + cs.CSR_AUTHENTICATION_FAILED])) + + assertEquals(cs.SASL_STATUS_CLIENT_FAILED, ssc.args[0]) + assertEquals(mapped_error, ssc.args[1]) + assertEquals(message, ssc.args[2].get('debug-message')), + assertEquals(mapped_error, ce.args[0]) diff --git a/tests/twisted/sasl/telepathy-password.py b/tests/twisted/sasl/telepathy-password.py new file mode 100644 index 0000000..f95a3d0 --- /dev/null +++ b/tests/twisted/sasl/telepathy-password.py @@ -0,0 +1,53 @@ +""" +Test the server sasl channel with the X-TELEPATHY-PASSWORD mechanism. +""" + +from servicetest import call_async, EventPattern +from hazetest import exec_test +import constants as cs +from saslutil import connect_and_get_sasl_channel + +PASSWORD = "pass" + +def test_close_straight_after_accept(q, bus, conn, stream): + chan, props = connect_and_get_sasl_channel(q, bus, conn) + + call_async(q, chan.SASLAuthentication, 'StartMechanismWithData', + 'X-TELEPATHY-PASSWORD', PASSWORD) + + # In_Progress appears before StartMechanismWithData returns + q.expect('dbus-signal', signal='SASLStatusChanged', + interface=cs.CHANNEL_IFACE_SASL_AUTH, + args=[cs.SASL_STATUS_IN_PROGRESS, '', {}]) + + # Different order to Gabble + q.expect_many( + EventPattern('dbus-return', method='StartMechanismWithData'), + EventPattern('dbus-signal', signal='SASLStatusChanged', + interface=cs.CHANNEL_IFACE_SASL_AUTH, + args=[cs.SASL_STATUS_SERVER_SUCCEEDED, '', {}]), + ) + + # fd.o#32278: + # When this was breaking, gabble received AcceptSASL and told the + # success_async GAsyncResult to complete in an idle. But, before + # the result got its callback called, Close was also received and + # the auth manager cleared its channel. When the idle function was + # finally reached it saw it no longer had a channel (it had been + # cleared in the closed callback) and thought it should be + # chaining up to the wocky auth registry but of course it should + # be calling the channel finish function. + call_async(q, chan.SASLAuthentication, 'AcceptSASL') + call_async(q, chan, 'Close') + + q.expect('dbus-signal', signal='SASLStatusChanged', + interface=cs.CHANNEL_IFACE_SASL_AUTH, + args=[cs.SASL_STATUS_SUCCEEDED, '', {}]) + + e = q.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) + +if __name__ == '__main__': + exec_test(test_close_straight_after_accept, + {'password': None, 'account' : "test@example.org/Resource"}, + do_connect=False) diff --git a/tests/twisted/servicetest.py b/tests/twisted/servicetest.py index 1b8e073..fcba708 100644 --- a/tests/twisted/servicetest.py +++ b/tests/twisted/servicetest.py @@ -8,29 +8,54 @@ from twisted.internet.protocol import Protocol, Factory, ClientFactory glib2reactor.install() import sys import time +import os import pprint import unittest -import dbus.glib +import dbus +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) from twisted.internet import reactor import constants as cs -tp_name_prefix = 'im.telepathy1' -tp_path_prefix = '/im/telepathy1' +tp_name_prefix = cs.PREFIX +tp_path_prefix = '/' + cs.PREFIX.replace('.', '/') -class Event: +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(object): def __init__(self, type, **kw): self.__dict__.update(kw) self.type = type (self.subqueue, self.subtype) = type.split ("-", 1) + def __str__(self): + return '\n'.join([ str(type(self)) ] + format_event(self)) + def format_event(event): ret = ['- type %s' % event.type] - for key in dir(event): + for key in sorted(dir(event)): if key != 'type' and not key.startswith('_'): ret.append('- %s: %s' % ( key, pprint.pformat(getattr(event, key)))) @@ -79,6 +104,14 @@ class EventPattern: 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. @@ -124,13 +157,16 @@ class BaseEventQueue: """ self.forbidden_events.difference_update(set(patterns)) + def unforbid_all(self): + """ + Remove all patterns from the set of forbidden events. + """ + self.forbidden_events.clear() + def _check_forbidden(self, event): for e in self.forbidden_events: if e.match(event): - print "forbidden event occurred:" - for x in format_event(event): - print x - assert False + raise ForbiddenEventOccurred(event) def expect(self, type, **kw): """ @@ -390,16 +426,21 @@ def call_async(test, proxy, method, *args, **kw): method_proxy(*args, **kw) def sync_dbus(bus, q, conn): - # Dummy D-Bus method call + # Dummy D-Bus method call. We can't use DBus.Peer.Ping() because libdbus + # replies to that message immediately, rather than handing it up to + # dbus-glib and thence Gabble, which means that Ping()ing Gabble doesn't + # ensure that it's processed all D-Bus messages prior to our ping. + # # 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.DBus.Peer'), 'Ping') - q.expect('dbus-return', method='Ping') + root_object = bus.get_object(conn.object.bus_name, '/', introspect=False) + call_async(q, + dbus.Interface(root_object, cs.PREFIX + '.Tests'), + 'DummySyncDBus') + q.expect('dbus-error', method='DummySyncDBus') class ProxyWrapper: - def __init__(self, object, default, others): + def __init__(self, object, default, others={}): self.object = object self.default_interface = dbus.Interface(object, default) self.Properties = dbus.Interface(object, dbus.PROPERTIES_IFACE) @@ -418,12 +459,29 @@ class ProxyWrapper: return getattr(self.default_interface, name) +class ConnWrapper(ProxyWrapper): + def inspect_contact_sync(self, handle): + return self.inspect_contacts_sync([handle])[0] + + def inspect_contacts_sync(self, handles): + h2asv = self.Contacts.GetContactAttributes(handles, [], True) + ret = [] + for h in handles: + ret.append(h2asv[h][cs.ATTR_CONTACT_ID]) + return ret + + def get_contact_handle_sync(self, identifier): + return self.Contacts.GetContactByID(identifier, [])[0] + + def get_contact_handles_sync(self, ids): + return [self.get_contact_handle_sync(i) for i in ids] + def wrap_connection(conn): - return ProxyWrapper(conn, tp_name_prefix + '.Connection', + return ConnWrapper(conn, tp_name_prefix + '.Connection', dict([ (name, tp_name_prefix + '.Connection.Interface.' + name) for name in ['Aliasing', 'Avatars', 'Capabilities', 'Contacts', - 'Presence', 'SimplePresence', 'Requests']] + + 'SimplePresence', 'Requests']] + [('Peer', 'org.freedesktop.DBus.Peer'), ('ContactCapabilities', cs.CONN_IFACE_CONTACT_CAPS), ('ContactInfo', cs.CONN_IFACE_CONTACT_INFO), @@ -433,6 +491,7 @@ def wrap_connection(conn): ('ContactList', cs.CONN_IFACE_CONTACT_LIST), ('ContactGroups', cs.CONN_IFACE_CONTACT_GROUPS), ('PowerSaving', cs.CONN_IFACE_POWER_SAVING), + ('Addressing', cs.CONN_IFACE_ADDRESSING), ])) def wrap_channel(chan, type_, extra=None): @@ -448,14 +507,26 @@ def wrap_channel(chan, type_, extra=None): return ProxyWrapper(chan, tp_name_prefix + '.Channel', interfaces) + +def wrap_content(chan, extra=None): + interfaces = { } + + if extra: + interfaces.update(dict([ + (name, tp_name_prefix + '.Call1.Content.Interface.' + name) + for name in extra])) + + return ProxyWrapper(chan, tp_name_prefix + '.Call1.Content', 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) + tp_path_prefix + '/ConnectionManager/%s' % name, + introspect=False) cm_iface = dbus.Interface(cm, tp_name_prefix + '.ConnectionManager') connection_name, connection_path = cm_iface.RequestConnection( - proto, params) + proto, dbus.Dictionary(params, signature='sv')) conn = wrap_connection(bus.get_object(connection_name, connection_path)) return conn @@ -568,6 +639,12 @@ def assertFlagsUnset(flags, value): "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 @@ -596,6 +673,16 @@ def install_colourer(): sys.stdout = Colourer(sys.stdout, patterns) return sys.stdout -if __name__ == '__main__': - unittest.main() +# this is just to shut up unittest. +class DummyStream(object): + def write(self, s): + if 'CHECK_TWISTED_VERBOSE' in os.environ: + print s, + + def flush(self): + pass +if __name__ == '__main__': + stream = DummyStream() + runner = unittest.TextTestRunner(stream=stream) + unittest.main(testRunner=runner) diff --git a/tests/twisted/simple-caps.py b/tests/twisted/simple-caps.py index e563003..d1657b9 100644 --- a/tests/twisted/simple-caps.py +++ b/tests/twisted/simple-caps.py @@ -6,11 +6,9 @@ Make sure ContactCaps works well enough. from twisted.words.xish import domish from servicetest import assertEquals, assertContains, EventPattern -from hazetest import exec_test, sync_stream +from hazetest import exec_test, sync_stream, JabberXmlStream import constants as cs -import config - import ns # assert this list of RCCs is only text @@ -20,12 +18,8 @@ def check_text_only(rccs): cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT }, [cs.TARGET_HANDLE])], rccs) -# assert GetContactCaps and GetContactAttributes returns just text caps +# assert just text caps def check_rccs(conn, handle): - rccs = conn.ContactCapabilities.GetContactCapabilities([handle]) - assertEquals(1, len(rccs)) - check_text_only(rccs[handle]) - attrs = conn.Contacts.GetContactAttributes([handle], [cs.CONN_IFACE_CONTACT_CAPS]) rccs = attrs[handle][cs.CONN_IFACE_CONTACT_CAPS + '/capabilities'] @@ -33,10 +27,6 @@ def check_rccs(conn, handle): # do the self handle which will just be text def test_self_handle(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', - args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) - self_handle = conn.Properties.Get(cs.CONN, 'SelfHandle') check_rccs(conn, self_handle) @@ -62,43 +52,13 @@ def test_someone_else(q, bus, conn, stream): q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) - bob_handle = conn.RequestHandles(cs.HT_CONTACT, ['bob@foo.com'])[0] + bob_handle = conn.get_contact_handle_sync('bob@foo.com') check_rccs(conn, bob_handle) # now a randomer who isn't even in our contact list - amy_handle = conn.RequestHandles(cs.HT_CONTACT, ['amy@foo.com'])[0] + amy_handle = conn.get_contact_handle_sync('amy@foo.com') check_rccs(conn, amy_handle) -def test_media(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', - args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) - - sync_stream(q, stream) - - conn.ContactCapabilities.UpdateCapabilities([( - 'im.telepathy1.Client.Foobar', - [{ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA, - cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, - cs.INITIAL_AUDIO: True }, - { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA, - cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, - cs.INITIAL_VIDEO: True }], - [], - )]) - - q.expect('stream-presence') # can't be bothered checking this - - conn.ContactCapabilities.UpdateCapabilities([( - 'im.telepathy1.Client.Foobar', - [], [])]) - - q.expect('stream-presence') # can't be bothered checking this - if __name__ == '__main__': exec_test(test_self_handle) - exec_test(test_someone_else) - - if config.MEDIA_ENABLED: - exec_test(test_media) - + exec_test(test_someone_else, do_connect=False, protocol=JabberXmlStream) diff --git a/tests/twisted/text/destroy.py b/tests/twisted/text/destroy.py index 022f136..c6edcff 100644 --- a/tests/twisted/text/destroy.py +++ b/tests/twisted/text/destroy.py @@ -8,62 +8,45 @@ import dbus from twisted.words.xish import domish from hazetest import exec_test -from servicetest import call_async, EventPattern, assertEquals +from servicetest import call_async, EventPattern, assertEquals, assertLength import constants as cs def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - - self_handle = conn.Properties.Get(cs.CONN, 'SelfHandle') + self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") jid = 'foo@bar.com' - call_async(q, conn, 'RequestHandles', 1, [jid]) - - event = q.expect('dbus-return', method='RequestHandles') - foo_handle = event.value[0][0] + foo_handle = conn.get_contact_handle_sync(jid) call_async(q, conn.Requests, 'CreateChannel', - { - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, - cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, - cs.TARGET_HANDLE: foo_handle + { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_HANDLE: foo_handle, }) - ret, new_sig = q.expect_many( + ret, sig = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('dbus-signal', signal='NewChannels'), ) text_chan = bus.get_object(conn.bus_name, ret.value[0]) - chan_iface = dbus.Interface(text_chan, - 'im.telepathy1.Channel') - text_iface = dbus.Interface(text_chan, - 'im.telepathy1.Channel.Type.Text') - destroyable_iface = dbus.Interface(text_chan, - 'im.telepathy1.Channel.Interface.Destroyable1') - - assert len(new_sig.args) == 1 - assert len(new_sig.args[0]) == 1 # one channel - assert len(new_sig.args[0][0]) == 2 # two struct members - assert new_sig.args[0][0][0] == ret.value[0] - emitted_props = new_sig.args[0][0][1] - assert emitted_props['im.telepathy1.Channel.ChannelType'] ==\ - 'im.telepathy1.Channel.Type.Text' - assert emitted_props['im.telepathy1.Channel.' - 'TargetHandleType'] == 1 - assert emitted_props['im.telepathy1.Channel.TargetHandle'] ==\ - foo_handle - assert emitted_props['im.telepathy1.Channel.TargetID'] == jid - assert emitted_props['im.telepathy1.Channel.' - 'Requested'] == True - assert emitted_props['im.telepathy1.Channel.' - 'InitiatorHandle'] == self_handle - assert emitted_props['im.telepathy1.Channel.' - 'InitiatorID'] == 'test@localhost' - - channel_props = text_chan.GetAll( - 'im.telepathy1.Channel', + chan_iface = dbus.Interface(text_chan, cs.CHANNEL) + text_iface = dbus.Interface(text_chan, cs.CHANNEL_TYPE_TEXT) + destroyable_iface = dbus.Interface(text_chan, cs.CHANNEL_IFACE_DESTROYABLE) + + assertLength(1, sig.args) + assertLength(1, sig.args[0]) # one channel + assertLength(2, sig.args[0][0]) # two struct members + assertEquals(ret.value, sig.args[0][0]) + emitted_props = sig.args[0][0][1] + assertEquals(cs.CHANNEL_TYPE_TEXT, emitted_props[cs.CHANNEL_TYPE]) + assertEquals(cs.HT_CONTACT, emitted_props[cs.TARGET_HANDLE_TYPE]) + assertEquals(foo_handle, emitted_props[cs.TARGET_HANDLE]) + assertEquals(jid, emitted_props[cs.TARGET_ID]) + assertEquals(True, emitted_props[cs.REQUESTED]) + assertEquals(self_handle, emitted_props[cs.INITIATOR_HANDLE]) + assertEquals('test@localhost', emitted_props[cs.INITIATOR_ID]) + + channel_props = text_chan.GetAll(cs.CHANNEL, dbus_interface=dbus.PROPERTIES_IFACE) assert channel_props['TargetID'] == jid,\ (channel_props['TargetID'], jid) @@ -73,14 +56,10 @@ def test(q, bus, conn, stream): assert channel_props['InitiatorID'] == 'test@localhost',\ channel_props['InitiatorID'] - hey = [ - dbus.Dictionary({ 'message-type': cs.MT_NORMAL, - }, signature='sv'), - { 'content-type': 'text/plain', - 'content': u"hey", - } - ] - text_iface.SendMessage(hey, 0) + text_iface.SendMessage([{}, { + 'content-type': 'text/plain', + 'content': 'hey', + }], 0) event = q.expect('stream-message') @@ -105,19 +84,19 @@ def test(q, bus, conn, stream): event = q.expect('dbus-signal', signal='MessageReceived') - assertEquals(2, len(event.args[0])) - header, body = event.args[0] - hello_message_id = header['pending-message-id'] - hello_message_time = header['message-received'], - assert header['message-sender'] == foo_handle - # message type: normal - assert header['message-type'] == cs.MT_NORMAL - # body - assert body['content'] == 'hello' + message = event.args[0] + assertLength(2, message) + hello_message_id = message[0]['pending-message-id'] + assertEquals(foo_handle, message[0]['message-sender']) + assertEquals('foo@bar.com', message[0]['message-sender-id']) + assertEquals(cs.MT_NORMAL, + message[0].get('message-type', cs.MT_NORMAL)) + assertEquals('text/plain', message[1]['content-type']) + assertEquals('hello', message[1]['content']) messages = text_chan.Get(cs.CHANNEL_TYPE_TEXT, 'PendingMessages', - dbus_interface=cs.PROPERTIES_IFACE) - assert messages == [[header, body]], messages + dbus_interface=cs.PROPERTIES_IFACE) + assertEquals([message], messages) # destroy the channel without acking the message; it does not come back diff --git a/tests/twisted/text/ensure.py b/tests/twisted/text/ensure.py index dcf413e..6896408 100644 --- a/tests/twisted/text/ensure.py +++ b/tests/twisted/text/ensure.py @@ -7,26 +7,20 @@ import dbus from twisted.words.xish import domish from hazetest import exec_test -from servicetest import call_async, EventPattern, unwrap +from servicetest import (call_async, EventPattern, unwrap, assertEquals, + assertLength, assertContains) import pprint import constants as cs def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - - self_handle = conn.Properties.Get(cs.CONN, 'SelfHandle') + self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") jids = ['foo@bar.com', 'truc@cafe.fr'] - call_async(q, conn, 'RequestHandles', 1, jids) - - event = q.expect('dbus-return', method='RequestHandles') - handles = event.value[0] + handles = conn.get_contact_handles_sync(jids) - properties = conn.GetAll( - 'im.telepathy1.Connection.Interface.Requests', + properties = conn.GetAll(cs.CONN_IFACE_REQUESTS, dbus_interface=dbus.PROPERTIES_IFACE) # Difference from Gabble: Haze's roster channels spring to life even if you # haven't received the XMPP roster. @@ -34,13 +28,10 @@ def test(q, bus, conn, stream): if c[1][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TEXT ] assert text_channels == [], text_channels - assert ({'im.telepathy1.Channel.ChannelType': - 'im.telepathy1.Channel.Type.Text', - 'im.telepathy1.Channel.TargetHandleType': 1, + assert ({cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, }, - ['im.telepathy1.Channel.TargetHandle', - 'im.telepathy1.Channel.TargetID' - ], + [cs.TARGET_HANDLE, cs.TARGET_ID], ) in properties.get('RequestableChannelClasses'),\ properties['RequestableChannelClasses'] @@ -60,7 +51,7 @@ def test_ensure_ensure(q, conn, self_handle, jid, handle): # Check that Ensuring a channel that doesn't exist succeeds call_async(q, conn.Requests, 'EnsureChannel', request_props (handle)) - ret, new_sig = q.expect_many( + ret, sig = q.expect_many( EventPattern('dbus-return', method='EnsureChannel'), EventPattern('dbus-signal', signal='NewChannels'), ) @@ -74,19 +65,16 @@ def test_ensure_ensure(q, conn, self_handle, jid, handle): check_props(emitted_props, self_handle, handle, jid) - assert len(new_sig.args) == 1 - assert len(new_sig.args[0]) == 1 # one channel - assert len(new_sig.args[0][0]) == 2 # two struct members - assert new_sig.args[0][0][0] == path - assert new_sig.args[0][0][1] == emitted_props + assertLength(1, sig.args) + assertLength(1, sig.args[0]) # one channel + assertLength(2, sig.args[0][0]) # two struct members + assertEquals(path, sig.args[0][0][0]) + assertEquals(emitted_props, sig.args[0][0][1]) - properties = conn.GetAll( - 'im.telepathy1.Connection.Interface.Requests', + properties = conn.GetAll(cs.CONN_IFACE_REQUESTS, dbus_interface=dbus.PROPERTIES_IFACE) - assert new_sig.args[0][0] in properties['Channels'], \ - (new_sig.args[0][0], properties['Channels']) - + assertContains(sig.args[0][0], properties['Channels']) # Now try Ensuring a channel which already exists call_async(q, conn.Requests, 'EnsureChannel', request_props (handle)) @@ -110,7 +98,7 @@ def test_request_ensure(q, conn, self_handle, jid, handle): call_async(q, conn.Requests, 'CreateChannel', request_props (handle)) - ret, new_sig = q.expect_many( + ret, sig = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('dbus-signal', signal='NewChannels'), ) @@ -120,19 +108,16 @@ def test_request_ensure(q, conn, self_handle, jid, handle): check_props(emitted_props, self_handle, handle, jid) - assert len(new_sig.args) == 1 - assert len(new_sig.args[0]) == 1 # one channel - assert len(new_sig.args[0][0]) == 2 # two struct members - assert new_sig.args[0][0][0] == path - assert new_sig.args[0][0][1] == emitted_props + assertLength(1, sig.args) + assertLength(1, sig.args[0]) # one channel + assertLength(2, sig.args[0][0]) # two struct members + assertEquals(path, sig.args[0][0][0]) + assertEquals(emitted_props, sig.args[0][0][1]) - properties = conn.GetAll( - 'im.telepathy1.Connection.Interface.Requests', + properties = conn.GetAll(cs.CONN_IFACE_REQUESTS, dbus_interface=dbus.PROPERTIES_IFACE) - assert new_sig.args[0][0] in properties['Channels'], \ - (new_sig.args[0][0], properties['Channels']) - + assertContains(sig.args[0][0], properties['Channels']) # Now try Ensuring that same channel. call_async(q, conn.Requests, 'EnsureChannel', request_props (handle)) @@ -149,26 +134,19 @@ def test_request_ensure(q, conn, self_handle, jid, handle): def check_props(props, self_handle, handle, jid): - assert props['im.telepathy1.Channel.ChannelType'] ==\ - 'im.telepathy1.Channel.Type.Text' - assert props['im.telepathy1.Channel.' - 'TargetHandleType'] == 1 - assert props['im.telepathy1.Channel.TargetHandle'] ==\ - handle - assert props['im.telepathy1.Channel.TargetID'] == jid - assert props['im.telepathy1.Channel.' - 'Requested'] == True - assert props['im.telepathy1.Channel.' - 'InitiatorHandle'] == self_handle - assert props['im.telepathy1.Channel.' - 'InitiatorID'] == 'test@localhost' + assertEquals(cs.CHANNEL_TYPE_TEXT, props[cs.CHANNEL_TYPE]) + assertEquals(cs.HT_CONTACT, props[cs.TARGET_HANDLE_TYPE]) + assertEquals(handle, props[cs.TARGET_HANDLE]) + assertEquals(jid, props[cs.TARGET_ID]) + assertEquals(True, props[cs.REQUESTED]) + assertEquals(self_handle, props[cs.INITIATOR_HANDLE]) + assertEquals('test@localhost', props[cs.INITIATOR_ID]) def request_props(handle): - return { 'im.telepathy1.Channel.ChannelType': - 'im.telepathy1.Channel.Type.Text', - 'im.telepathy1.Channel.TargetHandleType': 1, - 'im.telepathy1.Channel.TargetHandle': handle, + return { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_HANDLE: handle, } diff --git a/tests/twisted/text/initiate-requestotron.py b/tests/twisted/text/initiate-requestotron.py index 3679df0..c76dd36 100644 --- a/tests/twisted/text/initiate-requestotron.py +++ b/tests/twisted/text/initiate-requestotron.py @@ -7,24 +7,20 @@ import dbus from twisted.words.xish import domish from hazetest import exec_test -from servicetest import call_async, EventPattern +from servicetest import (call_async, EventPattern, unwrap, assertEquals, + assertLength, assertContains) + +import pprint import constants as cs def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - - self_handle = conn.Properties.Get(cs.CONN, 'SelfHandle') + self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") jid = 'foo@bar.com' - call_async(q, conn, 'RequestHandles', 1, [jid]) - - event = q.expect('dbus-return', method='RequestHandles') - foo_handle = event.value[0][0] + foo_handle = conn.get_contact_handle_sync(jid) - properties = conn.GetAll( - 'im.telepathy1.Connection.Interface.Requests', + properties = conn.GetAll(cs.CONN_IFACE_REQUESTS, dbus_interface=dbus.PROPERTIES_IFACE) # Difference from Gabble: Haze's roster channels spring to life even if you # haven't received the XMPP roster. @@ -32,56 +28,44 @@ def test(q, bus, conn, stream): if c[1][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TEXT ] assert text_channels == [], text_channels - assert ({'im.telepathy1.Channel.ChannelType': - 'im.telepathy1.Channel.Type.Text', - 'im.telepathy1.Channel.TargetHandleType': 1, + assert ({cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, }, - ['im.telepathy1.Channel.TargetHandle', - 'im.telepathy1.Channel.TargetID' - ], + [cs.TARGET_HANDLE, cs.TARGET_ID], ) in properties.get('RequestableChannelClasses'),\ properties['RequestableChannelClasses'] call_async(q, conn.Requests, 'CreateChannel', - { 'im.telepathy1.Channel.ChannelType': - 'im.telepathy1.Channel.Type.Text', - 'im.telepathy1.Channel.TargetHandleType': 1, - 'im.telepathy1.Channel.TargetHandle': foo_handle, - }) + { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_HANDLE: foo_handle, + }) - ret, new_sig = q.expect_many( + ret, sig = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('dbus-signal', signal='NewChannels'), ) assert len(ret.value) == 2 emitted_props = ret.value[1] - assert emitted_props['im.telepathy1.Channel.ChannelType'] ==\ - 'im.telepathy1.Channel.Type.Text' - assert emitted_props['im.telepathy1.Channel.' - 'TargetHandleType'] == 1 - assert emitted_props['im.telepathy1.Channel.TargetHandle'] ==\ - foo_handle - assert emitted_props['im.telepathy1.Channel.TargetID'] == jid - assert emitted_props['im.telepathy1.Channel.' - 'Requested'] == True - assert emitted_props['im.telepathy1.Channel.' - 'InitiatorHandle'] == self_handle - assert emitted_props['im.telepathy1.Channel.' - 'InitiatorID'] == 'test@localhost' - - assert len(new_sig.args) == 1 - assert len(new_sig.args[0]) == 1 # one channel - assert len(new_sig.args[0][0]) == 2 # two struct members - assert new_sig.args[0][0][0] == ret.value[0] - assert new_sig.args[0][0][1] == ret.value[1] - - properties = conn.GetAll( - 'im.telepathy1.Connection.Interface.Requests', + assertEquals(cs.CHANNEL_TYPE_TEXT, emitted_props[cs.CHANNEL_TYPE]) + assertEquals(cs.HT_CONTACT, emitted_props[cs.TARGET_HANDLE_TYPE]) + assertEquals(foo_handle, emitted_props[cs.TARGET_HANDLE]) + assertEquals(jid, emitted_props[cs.TARGET_ID]) + assertEquals(True, emitted_props[cs.REQUESTED]) + assertEquals(self_handle, emitted_props[cs.INITIATOR_HANDLE]) + assertEquals('test@localhost', emitted_props[cs.INITIATOR_ID]) + + assertLength(1, sig.args) + assertLength(1, sig.args[0]) # one channel + assertLength(2, sig.args[0][0]) # two struct members + assertEquals(ret.value[0], sig.args[0][0][0]) + assertEquals(ret.value[1], sig.args[0][0][1]) + + properties = conn.GetAll(cs.CONN_IFACE_REQUESTS, dbus_interface=dbus.PROPERTIES_IFACE) - assert new_sig.args[0][0] in properties['Channels'], \ - (new_sig.args[0][0], properties['Channels']) + assertContains(sig.args[0][0], properties['Channels']) conn.Disconnect() q.expect('dbus-signal', signal='StatusChanged', args=[2, 1]) diff --git a/tests/twisted/text/respawn.py b/tests/twisted/text/respawn.py index 2afebe2..cdc9105 100644 --- a/tests/twisted/text/respawn.py +++ b/tests/twisted/text/respawn.py @@ -7,59 +7,44 @@ import dbus from twisted.words.xish import domish from hazetest import exec_test -from servicetest import call_async, EventPattern, assertEquals +from servicetest import call_async, EventPattern, assertEquals, assertLength import constants as cs def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - - self_handle = conn.Properties.Get(cs.CONN, 'SelfHandle') + self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") jid = 'foo@bar.com' - call_async(q, conn, 'RequestHandles', 1, [jid]) - - event = q.expect('dbus-return', method='RequestHandles') - foo_handle = event.value[0][0] + foo_handle = conn.get_contact_handle_sync(jid) - call_async(q, conn.Requests, 'CreateChannel', { - cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, - cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, - cs.TARGET_HANDLE: foo_handle + call_async(q, conn.Requests, 'CreateChannel', + { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_HANDLE: foo_handle, }) - ret, new_sig = q.expect_many( + ret, sig = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('dbus-signal', signal='NewChannels'), ) text_chan = bus.get_object(conn.bus_name, ret.value[0]) - chan_iface = dbus.Interface(text_chan, - 'im.telepathy1.Channel') - text_iface = dbus.Interface(text_chan, - 'im.telepathy1.Channel.Type.Text') - - assert len(new_sig.args) == 1 - assert len(new_sig.args[0]) == 1 # one channel - assert len(new_sig.args[0][0]) == 2 # two struct members - assert new_sig.args[0][0][0] == ret.value[0] - emitted_props = new_sig.args[0][0][1] - assert emitted_props['im.telepathy1.Channel.ChannelType'] ==\ - 'im.telepathy1.Channel.Type.Text' - assert emitted_props['im.telepathy1.Channel.' - 'TargetHandleType'] == 1 - assert emitted_props['im.telepathy1.Channel.TargetHandle'] ==\ - foo_handle - assert emitted_props['im.telepathy1.Channel.TargetID'] == jid - assert emitted_props['im.telepathy1.Channel.' - 'Requested'] == True - assert emitted_props['im.telepathy1.Channel.' - 'InitiatorHandle'] == self_handle - assert emitted_props['im.telepathy1.Channel.' - 'InitiatorID'] == 'test@localhost' - - channel_props = text_chan.GetAll( - 'im.telepathy1.Channel', + chan_iface = dbus.Interface(text_chan, cs.CHANNEL) + text_iface = dbus.Interface(text_chan, cs.CHANNEL_TYPE_TEXT) + + assertLength(1, sig.args) + assertLength(1, sig.args[0]) # one channel + assertLength(2, sig.args[0][0]) # two struct members + assertEquals(ret.value, sig.args[0][0]) + emitted_props = sig.args[0][0][1] + assertEquals(cs.CHANNEL_TYPE_TEXT, emitted_props[cs.CHANNEL_TYPE]) + assertEquals(cs.HT_CONTACT, emitted_props[cs.TARGET_HANDLE_TYPE]) + assertEquals(foo_handle, emitted_props[cs.TARGET_HANDLE]) + assertEquals(jid, emitted_props[cs.TARGET_ID]) + assertEquals(True, emitted_props[cs.REQUESTED]) + assertEquals(self_handle, emitted_props[cs.INITIATOR_HANDLE]) + assertEquals('test@localhost', emitted_props[cs.INITIATOR_ID]) + + channel_props = text_chan.GetAll(cs.CHANNEL, dbus_interface=dbus.PROPERTIES_IFACE) assert channel_props['TargetID'] == jid,\ (channel_props['TargetID'], jid) @@ -69,14 +54,10 @@ def test(q, bus, conn, stream): assert channel_props['InitiatorID'] == 'test@localhost',\ channel_props['InitiatorID'] - hey = [ - dbus.Dictionary({ 'message-type': cs.MT_NORMAL, - }, signature='sv'), - { 'content-type': 'text/plain', - 'content': u"hey", - } - ] - text_iface.SendMessage(hey, 0) + text_iface.SendMessage([{}, { + 'content-type': 'text/plain', + 'content': 'hey', + }], 0) event = q.expect('stream-message') @@ -101,19 +82,19 @@ def test(q, bus, conn, stream): event = q.expect('dbus-signal', signal='MessageReceived') - assertEquals(2, len(event.args[0])) - header, body = event.args[0] - hello_message_id = header['pending-message-id'] - hello_message_time = header['message-received'], - assert header['message-sender'] == foo_handle - # message type: normal - assert header['message-type'] == cs.MT_NORMAL - # body - assert body['content'] == 'hello' + message = event.args[0] + assertLength(2, message) + hello_message_id = message[0]['pending-message-id'] + assertEquals(foo_handle, message[0]['message-sender']) + assertEquals('foo@bar.com', message[0]['message-sender-id']) + assertEquals(cs.MT_NORMAL, + message[0].get('message-type', cs.MT_NORMAL)) + assertEquals('text/plain', message[1]['content-type']) + assertEquals('hello', message[1]['content']) messages = text_chan.Get(cs.CHANNEL_TYPE_TEXT, 'PendingMessages', - dbus_interface=cs.PROPERTIES_IFACE) - assert messages == [[header, body]], messages + dbus_interface=cs.PROPERTIES_IFACE) + assertEquals([message], messages) # close the channel without acking the message; it comes back @@ -126,20 +107,21 @@ def test(q, bus, conn, stream): assertEquals(text_chan.object_path, old.path) assertEquals(text_chan.object_path, new.args[0]) + # it now behaves as if the message had initiated it + new_props = {} + for k in emitted_props: + new_props[k] = emitted_props[k] + new_props[cs.INITIATOR_HANDLE] = foo_handle + new_props[cs.INITIATOR_ID] = 'foo@bar.com' + new_props[cs.REQUESTED] = False + event = q.expect('dbus-signal', signal='NewChannels') - assertEquals(1, len(event.args[0])) - path, props = event.args[0][0] - assert path == text_chan.object_path - assert props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TEXT - assert props[cs.TARGET_HANDLE_TYPE] == cs.HT_CONTACT - assert props[cs.TARGET_HANDLE] == foo_handle + assertEquals(text_chan.object_path, event.args[0][0][0]) + assertEquals(new_props, event.args[0][0][1]) event = q.expect('dbus-return', method='Close') - # it now behaves as if the message had initiated it - - channel_props = text_chan.GetAll( - 'im.telepathy1.Channel', + channel_props = text_chan.GetAll(cs.CHANNEL, dbus_interface=dbus.PROPERTIES_IFACE) assert channel_props['TargetID'] == jid,\ (channel_props['TargetID'], jid) @@ -149,21 +131,21 @@ def test(q, bus, conn, stream): assert channel_props['InitiatorID'] == 'foo@bar.com',\ channel_props['InitiatorID'] - # the message is still there + # the message is still there, but is marked as rescued now + message[0]['rescued'] = True - header['rescued'] = True messages = text_chan.Get(cs.CHANNEL_TYPE_TEXT, 'PendingMessages', - dbus_interface=cs.PROPERTIES_IFACE) - assert messages == [[header, body]], messages + dbus_interface=cs.PROPERTIES_IFACE) + assertEquals([message], messages) # acknowledge it text_chan.AcknowledgePendingMessages([hello_message_id], - dbus_interface='im.telepathy1.Channel.Type.Text') + dbus_interface=cs.CHANNEL_TYPE_TEXT) messages = text_chan.Get(cs.CHANNEL_TYPE_TEXT, 'PendingMessages', - dbus_interface=cs.PROPERTIES_IFACE) - assert messages == [] + dbus_interface=cs.PROPERTIES_IFACE) + assertEquals([], messages) # close the channel again diff --git a/tests/twisted/text/test-text-delayed.py b/tests/twisted/text/test-text-delayed.py index 295e01c..59e73d0 100644 --- a/tests/twisted/text/test-text-delayed.py +++ b/tests/twisted/text/test-text-delayed.py @@ -12,9 +12,6 @@ from servicetest import EventPattern, assertEquals import constants as cs def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - m = domish.Element((None, 'message')) m['from'] = 'foo@bar.com' m['type'] = 'chat' @@ -27,12 +24,9 @@ def test(q, bus, conn, stream): stream.send(m) event = q.expect('dbus-signal', signal='NewChannels') - assertEquals(1, len(event.args[0])) - path, props = event.args[0][0] - assert props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TEXT - # check that handle type == contact handle - assert props[cs.TARGET_HANDLE_TYPE] == cs.HT_CONTACT - assert props[cs.TARGET_ID] == 'foo@bar.com' + assertEquals(cs.CHANNEL_TYPE_TEXT, event.args[0][0][1][cs.CHANNEL_TYPE]) + assertEquals(cs.HT_CONTACT, event.args[0][0][1][cs.TARGET_HANDLE_TYPE]) + assertEquals('foo@bar.com', event.args[0][0][1][cs.TARGET_ID]) message_received = q.expect('dbus-signal', signal='MessageReceived') diff --git a/tests/twisted/text/test-text-no-body.py b/tests/twisted/text/test-text-no-body.py index 0ce9c33..c8aacd7 100644 --- a/tests/twisted/text/test-text-no-body.py +++ b/tests/twisted/text/test-text-no-body.py @@ -6,16 +6,13 @@ new text channel. from twisted.words.xish import domish -from servicetest import assertEquals from hazetest import exec_test +from servicetest import assertEquals import constants as cs import ns def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) - # message without body m = domish.Element((None, 'message')) m['from'] = 'alice@foo.com' @@ -33,10 +30,10 @@ def test(q, bus, conn, stream): # first message should be from Bob, not Alice event = q.expect('dbus-signal', signal='NewChannels') - assertEquals(1, len(event.args[0])) - path, props = event.args[0][0] - assertEquals(cs.CHANNEL_TYPE_TEXT, props[cs.CHANNEL_TYPE]) - assertEquals('bob@foo.com', props[cs.TARGET_ID]) + assertEquals(cs.CHANNEL_TYPE_TEXT, event.args[0][0][1][cs.CHANNEL_TYPE]) + assertEquals(cs.HT_CONTACT, event.args[0][0][1][cs.TARGET_HANDLE_TYPE]) + assertEquals('bob@foo.com', event.args[0][0][1][cs.TARGET_ID]) + conn.Disconnect() q.expect('dbus-signal', signal='StatusChanged', args=[2, 1]) diff --git a/tests/twisted/text/test-text.py b/tests/twisted/text/test-text.py index e33d276..0a33755 100644 --- a/tests/twisted/text/test-text.py +++ b/tests/twisted/text/test-text.py @@ -8,12 +8,11 @@ import dbus from twisted.words.xish import domish from hazetest import exec_test -from servicetest import EventPattern, assertEquals +from servicetest import EventPattern, assertEquals, assertContains import constants as cs def test(q, bus, conn, stream): - conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0, 1]) + jid = 'foo@bar.com' # <message type="chat"><body>hello</body</message> m = domish.Element((None, 'message')) @@ -23,41 +22,31 @@ def test(q, bus, conn, stream): stream.send(m) event = q.expect('dbus-signal', signal='NewChannels') - assertEquals(1, len(event.args[0])) - path, props = event.args[0][0] - text_chan = bus.get_object(conn.bus_name, path) - assertEquals(cs.CHANNEL_TYPE_TEXT, props[cs.CHANNEL_TYPE]) - # check that handle type == contact handle - assertEquals(cs.HT_CONTACT, props[cs.TARGET_HANDLE_TYPE]) - foo_at_bar_dot_com_handle = props[cs.TARGET_HANDLE] - jid = conn.InspectHandles(1, [foo_at_bar_dot_com_handle])[0] - assertEquals('foo@bar.com', jid) - assertEquals(jid, props[cs.TARGET_ID]) + assertEquals(cs.CHANNEL_TYPE_TEXT, event.args[0][0][1][cs.CHANNEL_TYPE]) + assertEquals(cs.HT_CONTACT, event.args[0][0][1][cs.TARGET_HANDLE_TYPE]) + assertEquals(jid, event.args[0][0][1][cs.TARGET_ID]) + foo_at_bar_dot_com_handle = event.args[0][0][1][cs.TARGET_HANDLE] + + text_chan = bus.get_object(conn.bus_name, event.args[0][0][0]) # Exercise basic Channel Properties from spec 0.17.7 - channel_props = text_chan.GetAll( - 'im.telepathy1.Channel', + channel_props = text_chan.GetAll(cs.CHANNEL, dbus_interface=dbus.PROPERTIES_IFACE) - assert channel_props.get('TargetHandle') == props[cs.TARGET_HANDLE],\ - (channel_props.get('TargetHandle'), props[cs.TARGET_HANDLE]) - assert channel_props.get('TargetHandleType') == cs.HT_CONTACT,\ + assertEquals(foo_at_bar_dot_com_handle, channel_props.get('TargetHandle')) + assert channel_props.get('TargetHandleType') == 1,\ channel_props.get('TargetHandleType') - assert channel_props.get('ChannelType') == cs.CHANNEL_TYPE_TEXT,\ - channel_props.get('ChannelType') - assert cs.CHANNEL_IFACE_CHAT_STATE in \ - channel_props.get('Interfaces', ()), \ - channel_props.get('Interfaces') + assertEquals(cs.CHANNEL_TYPE_TEXT, channel_props.get('ChannelType')) + assertContains(cs.CHANNEL_IFACE_CHAT_STATE, + channel_props.get('Interfaces', ())) assert channel_props['TargetID'] == jid,\ (channel_props['TargetID'], jid) assert channel_props['Requested'] == False - assert channel_props['InitiatorHandle'] == props[cs.TARGET_HANDLE],\ - (channel_props['InitiatorHandle'], props[cs.TARGET_HANDLE]) + assertEquals(foo_at_bar_dot_com_handle, channel_props['InitiatorHandle']) assert channel_props['InitiatorID'] == jid,\ (channel_props['InitiatorID'], jid) message_received = q.expect('dbus-signal', signal='MessageReceived') - # Check that MessageReceived looks right. message = message_received.args[0] # message should have two parts: the header and one content part @@ -76,8 +65,7 @@ def test(q, bus, conn, stream): # PendingMessagesRemoved fires. message_id = header['pending-message-id'] - dbus.Interface(text_chan, - u'im.telepathy1.Channel.Type.Text' + dbus.Interface(text_chan, cs.CHANNEL_TYPE_TEXT ).AcknowledgePendingMessages([message_id]) removed = q.expect('dbus-signal', signal='PendingMessagesRemoved') @@ -89,7 +77,7 @@ def test(q, bus, conn, stream): # Send an action using the Messages API # In Gabble, this is a Notice, but we don't support those. greeting = [ - dbus.Dictionary({ 'message-type': 1, # Action + dbus.Dictionary({ 'message-type': cs.MT_ACTION, }, signature='sv'), { 'content-type': 'text/plain', 'content': u"waves", @@ -97,7 +85,7 @@ def test(q, bus, conn, stream): ] dbus.Interface(text_chan, cs.CHANNEL_TYPE_TEXT - ).SendMessage(greeting, dbus.UInt32(0)) + ).SendMessage(greeting, dbus.UInt32(0)) stream_message, message_sent = q.expect_many( EventPattern('stream-message'), @@ -124,6 +112,40 @@ def test(q, bus, conn, stream): assert body['content-type'] == 'text/plain', body assert body['content'] == u'waves', body + # Send a message using Channel.Type.Text API + dbus.Interface(text_chan, cs.CHANNEL_TYPE_TEXT + ).SendMessage([{}, { + 'content-type': 'text/plain', + 'content': 'goodbye', + }], 0) + + stream_message, message_sent = q.expect_many( + EventPattern('stream-message'), + EventPattern('dbus-signal', signal='MessageSent'), + ) + + elem = stream_message.stanza + assert elem.name == 'message' + assert elem['type'] == 'chat' + + found = False + for e in elem.elements(): + if e.name == 'body': + found = True + e.children[0] == u'goodbye' + break + assert found, elem.toXml() + + sent_message = message_sent.args[0] + assert len(sent_message) == 2, sent_message + header = sent_message[0] + # the spec says that message-type "MAY be omitted for normal chat + # messages." + assert 'message-type' not in header or header['message-type'] == 0, header + body = sent_message[1] + assert body['content-type'] == 'text/plain', body + assert body['content'] == u'goodbye', body + conn.Disconnect() q.expect('dbus-signal', signal='StatusChanged', args=[2, 1]) diff --git a/tests/twisted/tools/Makefile.am b/tests/twisted/tools/Makefile.am new file mode 100644 index 0000000..c64e7c1 --- /dev/null +++ b/tests/twisted/tools/Makefile.am @@ -0,0 +1,31 @@ +%.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) +im.telepathy1.ConnectionManager.%.service: %.service.in + $(AM_V_GEN)sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" \ + -e "s|[@]abs_top_srcdir[@]|@abs_top_srcdir@|g" $< > $@ + +# D-Bus service file for testing +service_in_files = haze.service.in +service_files = im.telepathy1.ConnectionManager.haze.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) + +EXTRA_DIST = \ + $(service_in_files) \ + $(conf_in_files) \ + exec-with-log.sh \ + with-session-bus.sh \ + $(NULL) + +CLEANFILES = \ + $(BUILT_SOURCES) \ + haze-testing.log \ + $(NULL) diff --git a/tests/exec-with-log.sh b/tests/twisted/tools/exec-with-log.sh index fe25b3c..e6e9379 100755 --- a/tests/exec-with-log.sh +++ b/tests/twisted/tools/exec-with-log.sh @@ -9,9 +9,20 @@ cd "${abs_top_builddir}/tests" export LC_ALL=C export HAZE_DEBUG=all +G_MESSAGES_DEBUG=all +export G_MESSAGES_DEBUG ulimit -c unlimited exec >> haze-testing.log 2>&1 +# Avoid using a non-trivial GSettings backend +GSETTINGS_BACKEND=memory +export GSETTINGS_BACKEND +# Avoid libpurple doing "clever" things +unset KDE_FULL_SESSION +unset KDEDIR +unset KDEDIRS +unset GNOME_DESKTOP_SESSION_ID + if test -n "$HAZE_TEST_VALGRIND"; then export G_DEBUG=${G_DEBUG:+"${G_DEBUG},"}gc-friendly export G_SLICE=always-malloc @@ -27,6 +38,8 @@ elif test -n "$HAZE_TEST_REFDBG"; then if test -z "$HAZE_WRAPPER" ; then HAZE_WRAPPER="refdbg" fi +elif test -n "$HAZE_TEST_BACKTRACE"; then + HAZE_WRAPPER="gdb -x ${abs_top_srcdir}/tools/run_and_bt.gdb" fi # not suitable for haze: diff --git a/tests/twisted/tools/haze.service.in b/tests/twisted/tools/haze.service.in new file mode 100644 index 0000000..8c3cea7 --- /dev/null +++ b/tests/twisted/tools/haze.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=im.telepathy1.ConnectionManager.haze +Exec=@abs_top_srcdir@/tests/twisted/tools/exec-with-log.sh @abs_top_srcdir@ @abs_top_builddir@ diff --git a/tests/tmp-session-bus.conf.in b/tests/twisted/tools/tmp-session-bus.conf.in index c73caac..84d8d65 100644 --- a/tests/tmp-session-bus.conf.in +++ b/tests/twisted/tools/tmp-session-bus.conf.in @@ -10,7 +10,7 @@ <listen>unix:tmpdir=/tmp</listen> - <servicedir>@abs_top_builddir@/tests</servicedir> + <servicedir>@abs_top_builddir@/tests/twisted/tools</servicedir> <policy context="default"> <!-- Allow everything to be sent --> diff --git a/tools/with-session-bus.sh b/tests/twisted/tools/with-session-bus.sh index 063bd7e..0afa593 100644 --- a/tools/with-session-bus.sh +++ b/tests/twisted/tools/with-session-bus.sh @@ -59,7 +59,9 @@ cleanup () { pid=`head -n1 $me-$$.pid` if test -n "$pid" ; then - echo "Killing temporary bus daemon: $pid" >&2 + if [ -n "$VERBOSE_TESTS" ]; then + echo "Killing temporary bus daemon: $pid" >&2 + fi kill -INT "$pid" fi rm -f $me-$$.address @@ -69,12 +71,22 @@ cleanup () trap cleanup INT HUP TERM dbus-daemon $dbus_daemon_args -{ echo -n "Temporary bus daemon is "; cat $me-$$.address; } >&2 -{ echo -n "Temporary bus daemon PID is "; head -n1 $me-$$.pid; } >&2 +if [ -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 + +# These might be non-null when run from e.g. gnome-terminal 3.8, which uses +# an activatable service for its windows; we don't want to inherit them either +unset DBUS_STARTER_ADDRESS +unset DBUS_STARTER_BUS_TYPE + DBUS_SESSION_BUS_ADDRESS="`cat $me-$$.address`" export DBUS_SESSION_BUS_ADDRESS +DBUS_SESSION_BUS_PID="`cat $me-$$.pid`" +export DBUS_SESSION_BUS_PID if [ -n "$WITH_SESSION_BUS_FORK_DBUS_MONITOR" ] ; then echo -n "Forking dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT" >&2 diff --git a/tools/Makefile.am b/tools/Makefile.am index ac54560..715d792 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,5 +1,4 @@ EXTRA_DIST = \ - with-session-bus.sh \ c-constants-gen.py \ doc-generator.xsl \ glib-ginterface-gen.py \ diff --git a/tools/glib-ginterface-gen.py b/tools/glib-ginterface-gen.py index 13f7f69..6543146 100644 --- a/tools/glib-ginterface-gen.py +++ b/tools/glib-ginterface-gen.py @@ -701,7 +701,7 @@ class Generator(object): self.h('#include <dbus/dbus-glib.h>') if self.have_properties(nodes): - self.h('#include <telepathy-glib/dbus-properties-mixin.h>') + self.h('#include <telepathy-glib/telepathy-glib.h>') self.h('') self.h('G_BEGIN_DECLS') diff --git a/tools/run_and_bt.gdb b/tools/run_and_bt.gdb new file mode 100644 index 0000000..201353f --- /dev/null +++ b/tools/run_and_bt.gdb @@ -0,0 +1,7 @@ +run +echo ---- [bt full] -------------------------------------------\n +bt full +echo -----[thread apply all bt full] --------------------------\n +thread apply all bt full +echo ----------------------------------------------------------\n +quit diff --git a/tools/telepathy.am b/tools/telepathy.am index 9715026..2b6c430 100644 --- a/tools/telepathy.am +++ b/tools/telepathy.am @@ -3,7 +3,7 @@ dist-hook: chmod u+w ${distdir}/ChangeLog if test -d ${top_srcdir}/.git; then \ - git log --date=iso $(CHANGELOG_RANGE) > ${distdir}/ChangeLog; \ + ( cd ${top_srcdir} && git log --date=iso $(CHANGELOG_RANGE) ) > ${distdir}/ChangeLog; \ fi distcheck-hook: @@ -26,11 +26,13 @@ _is-release-check: exit 2; \ ;; \ esac - @if ! git diff --no-ext-diff --quiet --exit-code; then \ + @cd ${top_srcdir} && \ + 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 \ + @cd ${top_srcdir} && \ + 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 @@ -38,9 +40,16 @@ _is-release-check: %.tar.gz.asc: %.tar.gz $(AM_V_GEN)gpg --detach-sign --armor $@ -@PACKAGE@-@VERSION@.tar.gz: _is-release-check check distcheck +@PACKAGE@-@VERSION@.tar.gz: + $(MAKE) _is-release-check + $(MAKE) check + $(MAKE) distcheck -maintainer-prepare-release: _is-release-check check distcheck release-mail +maintainer-prepare-release: + $(MAKE) _is-release-check + $(MAKE) all + $(MAKE) distcheck + $(MAKE) release-mail git tag -s @PACKAGE@-@VERSION@ -m @PACKAGE@' '@VERSION@ gpg --detach-sign --armor @PACKAGE@-@VERSION@.tar.gz @@ -60,7 +69,9 @@ _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 +maintainer-make-release: + $(MAKE) maintainer-prepare-release + $(MAKE) maintainer-upload-release @echo "Now:" @echo " • bump the nano-version;" @echo " • push the branch and tags upstream; and" |