/* * jingle-share.c - Source for GabbleJingleShare * * Copyright (C) 2010 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Share content type deals with file sharing content, ie. file transfers. It * Google's jingle variants (libjingle 0.3/0.4). */ #include "config.h" #include "jingle-share.h" #include #include #include #include #define DEBUG_FLAG GABBLE_DEBUG_SHARE #include "connection.h" #include "debug.h" #include "namespaces.h" #include "util.h" /****************************************************************** * Example description XML: * * * * * foo.txt * * * foo.jpg * * * * stuff * * * * * /temporary/23A53F01/ * /temporary/90266EA1/ * * * * *******************************************************************/ G_DEFINE_TYPE (GabbleJingleShare, gabble_jingle_share, WOCKY_TYPE_JINGLE_CONTENT); /* properties */ enum { PROP_MEDIA_TYPE = 1, PROP_FILENAME, PROP_FILESIZE, LAST_PROPERTY }; struct _GabbleJingleSharePrivate { gboolean dispose_has_run; GabbleJingleShareManifest *manifest; gchar *filename; guint64 filesize; }; static gchar * generate_temp_url (void) { gchar *uuid = gabble_generate_id (); gchar *url = NULL; url = g_strdup_printf ("/temporary/%s/", uuid); g_free (uuid); return url; } static void free_manifest (GabbleJingleShare *self) { GList * i; if (self->priv->manifest) { for (i = self->priv->manifest->entries; i; i = i->next) { GabbleJingleShareManifestEntry *item = i->data; g_free (item->name); g_slice_free (GabbleJingleShareManifestEntry, item); } g_list_free (self->priv->manifest->entries); g_free (self->priv->manifest->source_url); g_free (self->priv->manifest->preview_url); g_slice_free (GabbleJingleShareManifest, self->priv->manifest); self->priv->manifest = NULL; } } static void ensure_manifest (GabbleJingleShare *self) { if (self->priv->manifest == NULL) { GabbleJingleShareManifestEntry *m = NULL; self->priv->manifest = g_slice_new0 (GabbleJingleShareManifest); self->priv->manifest->source_url = generate_temp_url (); self->priv->manifest->preview_url = generate_temp_url (); if (self->priv->filename != NULL) { m = g_slice_new0 (GabbleJingleShareManifestEntry); m->name = g_strdup (self->priv->filename); m->size = self->priv->filesize; self->priv->manifest->entries = g_list_prepend (NULL, m); } } } static void gabble_jingle_share_init (GabbleJingleShare *obj) { GabbleJingleSharePrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj, GABBLE_TYPE_JINGLE_SHARE, GabbleJingleSharePrivate); DEBUG ("jingle share init called"); obj->priv = priv; priv->dispose_has_run = FALSE; } static void gabble_jingle_share_dispose (GObject *object) { GabbleJingleShare *self = GABBLE_JINGLE_SHARE (object); GabbleJingleSharePrivate *priv = self->priv; if (priv->dispose_has_run) return; DEBUG ("dispose called"); priv->dispose_has_run = TRUE; g_free (priv->filename); priv->filename = NULL; free_manifest (self); if (G_OBJECT_CLASS (gabble_jingle_share_parent_class)->dispose) G_OBJECT_CLASS (gabble_jingle_share_parent_class)->dispose (object); } static void parse_description (WockyJingleContent *content, WockyNode *desc_node, GError **error); static void produce_description (WockyJingleContent *obj, WockyNode *content_node); static void gabble_jingle_share_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GabbleJingleShare *self = GABBLE_JINGLE_SHARE (object); GabbleJingleSharePrivate *priv = self->priv; switch (property_id) { case PROP_MEDIA_TYPE: g_value_set_uint (value, WOCKY_JINGLE_MEDIA_TYPE_NONE); break; case PROP_FILENAME: g_value_set_string (value, priv->filename); break; case PROP_FILESIZE: g_value_set_uint64 (value, priv->filesize); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_jingle_share_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GabbleJingleShare *self = GABBLE_JINGLE_SHARE (object); GabbleJingleSharePrivate *priv = self->priv; switch (property_id) { case PROP_MEDIA_TYPE: break; case PROP_FILENAME: g_free (priv->filename); priv->filename = g_value_dup_string (value); free_manifest (self); /* simulate a media_ready when we know our own filename */ _wocky_jingle_content_set_media_ready (WOCKY_JINGLE_CONTENT (self)); break; case PROP_FILESIZE: priv->filesize = g_value_get_uint64 (value); free_manifest (self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static WockyJingleContentSenders get_default_senders (WockyJingleContent *c) { return WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR; } static void gabble_jingle_share_class_init (GabbleJingleShareClass *cls) { GObjectClass *object_class = G_OBJECT_CLASS (cls); WockyJingleContentClass *content_class = WOCKY_JINGLE_CONTENT_CLASS (cls); g_type_class_add_private (cls, sizeof (GabbleJingleSharePrivate)); object_class->get_property = gabble_jingle_share_get_property; object_class->set_property = gabble_jingle_share_set_property; object_class->dispose = gabble_jingle_share_dispose; content_class->parse_description = parse_description; content_class->produce_description = produce_description; content_class->get_default_senders = get_default_senders; /* This property is here only because jingle-session sets the media-type when constructing the object.. */ g_object_class_install_property (object_class, PROP_MEDIA_TYPE, g_param_spec_uint ("media-type", "media type", "irrelevant media type. Will always be NONE.", WOCKY_JINGLE_MEDIA_TYPE_NONE, WOCKY_JINGLE_MEDIA_TYPE_NONE, WOCKY_JINGLE_MEDIA_TYPE_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FILENAME, g_param_spec_string ("filename", "file name", "The name of the file", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FILESIZE, g_param_spec_uint64 ("filesize", "file size", "The size of the file", 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void parse_description (WockyJingleContent *content, WockyNode *desc_node, GError **error) { GabbleJingleShare *self = GABBLE_JINGLE_SHARE (content); GabbleJingleSharePrivate *priv = self->priv; WockyNodeIter i; WockyNode *manifest_node = NULL; WockyNode *protocol_node = NULL; WockyNode *http_node = NULL; WockyNode *node; DEBUG ("parse description called"); if (priv->manifest != NULL) { DEBUG ("Not parsing description, we already have a manifest"); return; } manifest_node = wocky_node_get_child (desc_node, "manifest"); if (manifest_node == NULL) { g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST, "description missing node"); return; } protocol_node = wocky_node_get_child (desc_node, "protocol"); if (protocol_node != NULL) http_node = wocky_node_get_child (protocol_node, "http"); free_manifest (self); priv->manifest = g_slice_new0 (GabbleJingleShareManifest); /* Build the manifest */ wocky_node_iter_init (&i, manifest_node, NULL, NULL); while (wocky_node_iter_next (&i, &node)) { WockyNode *name = NULL; WockyNode *image = NULL; gboolean folder; const gchar *size; GabbleJingleShareManifestEntry *m = NULL; if (!wocky_strdiff (node->name, "folder")) folder = TRUE; else if (!wocky_strdiff (node->name, "file")) folder = FALSE; else continue; name = wocky_node_get_child (node, "name"); if (name == NULL) continue; m = g_slice_new0 (GabbleJingleShareManifestEntry); m->folder = folder; m->name = g_strdup (name->content); size = wocky_node_get_attribute (node, "size"); if (size) m->size = g_ascii_strtoull (size, NULL, 10); image = wocky_node_get_child (node, "image"); if (image) { const gchar *width; const gchar *height; m->image = TRUE; width = wocky_node_get_attribute (image, "width"); if (width) m->image_width = g_ascii_strtoull (width, NULL, 10); height =wocky_node_get_attribute (image, "height"); if (height) m->image_height = g_ascii_strtoull (height, NULL, 10); } priv->manifest->entries = g_list_prepend (priv->manifest->entries, m); } /* Get the source and preview url paths from the protocol/http node */ if (http_node != NULL) { /* clear the previously set values */ wocky_node_iter_init (&i, http_node, "url", NULL); while (wocky_node_iter_next (&i, &node)) { const gchar *name = wocky_node_get_attribute (node, "name"); if (name == NULL) continue; if (!wocky_strdiff (name, "source-path")) { const gchar *url = node->content; priv->manifest->source_url = g_strdup (url); } if (!wocky_strdiff (name, "preview-path")) { const gchar *url = node->content; priv->manifest->preview_url = g_strdup (url); } } } /* Build the filename/filesize property values based on the new manifest */ g_free (priv->filename); priv->filename = NULL; priv->filesize = 0; if (g_list_length (priv->manifest->entries) > 0) { if (g_list_length (priv->manifest->entries) == 1) { GabbleJingleShareManifestEntry *m = priv->manifest->entries->data; if (m->folder) priv->filename = g_strdup_printf ("%s.tar", m->name); else priv->filename = g_strdup (m->name); priv->filesize = m->size; } else { GList *li; gchar *temp; priv->filename = g_strdup (""); for (li = priv->manifest->entries; li; li = li->next) { GabbleJingleShareManifestEntry *m = li->data; temp = priv->filename; priv->filename = g_strdup_printf ("%s%s%s%s", temp, m->name, m->folder? ".tar":"", li->next == NULL? "": "-"); g_free (temp); priv->filesize += m->size; } temp = priv->filename; priv->filename = g_strdup_printf ("%s.tar", temp); g_free (temp); } } _wocky_jingle_content_set_media_ready (content); } static void produce_description (WockyJingleContent *content, WockyNode *content_node) { GabbleJingleShare *self = GABBLE_JINGLE_SHARE (content); GabbleJingleSharePrivate *priv = self->priv; GList *i; WockyNode *desc_node; WockyNode *manifest_node; WockyNode *protocol_node; WockyNode *http_node; WockyNode *url_node; DEBUG ("produce description called"); ensure_manifest (self); desc_node = wocky_node_add_child_ns (content_node, "description", NS_GOOGLE_SESSION_SHARE); manifest_node = wocky_node_add_child (desc_node, "manifest"); for (i = priv->manifest->entries; i; i = i->next) { GabbleJingleShareManifestEntry *m = i->data; WockyNode *file_node; WockyNode *image_node; gchar *size_str, *width_str, *height_str; if (m->folder) file_node = wocky_node_add_child (manifest_node, "folder"); else file_node = wocky_node_add_child (manifest_node, "file"); if (m->size > 0) { size_str = g_strdup_printf ("%" G_GUINT64_FORMAT, m->size); wocky_node_set_attribute (file_node, "size", size_str); g_free (size_str); } wocky_node_add_child_with_content (file_node, "name", m->name); if (m->image && (m->image_width > 0 || m->image_height > 0)) { image_node = wocky_node_add_child (file_node, "image"); if (m->image_width > 0) { width_str = g_strdup_printf ("%d", m->image_width); wocky_node_set_attribute (image_node, "width", width_str); g_free (width_str); } if (m->image_height > 0) { height_str = g_strdup_printf ("%d", m->image_height); wocky_node_set_attribute (image_node, "height", height_str); g_free (height_str); } } } protocol_node = wocky_node_add_child (desc_node, "protocol"); http_node = wocky_node_add_child (protocol_node, "http"); url_node = wocky_node_add_child_with_content (http_node, "url", priv->manifest->source_url); wocky_node_set_attribute (url_node, "name", "source-path"); url_node = wocky_node_add_child_with_content (http_node, "url", priv->manifest->preview_url); wocky_node_set_attribute (url_node, "name", "preview-path"); } GabbleJingleShareManifest * gabble_jingle_share_get_manifest (GabbleJingleShare *self) { ensure_manifest (self); return self->priv->manifest; } void jingle_share_register (WockyJingleFactory *factory) { /* GTalk video call namespace */ wocky_jingle_factory_register_content_type (factory, NS_GOOGLE_SESSION_SHARE, GABBLE_TYPE_JINGLE_SHARE); }