/* * ft-manager.c - Source for GabbleFtManager * Copyright (C) 2009-2010 Collabora Ltd. * @author: Guillaume Desmottes * * 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" #define _BSD_SOURCE #define _XOPEN_SOURCE /* glibc2 needs this */ #include #include #include #include #include #include #include #ifdef ENABLE_JINGLE_FILE_TRANSFER #include "jingle-share.h" #endif #include "gabble/caps-channel-manager.h" #include "connection.h" #include "ft-manager.h" #include "ft-channel.h" #include "gabble-signals-marshal.h" #include "namespaces.h" #include "presence-cache.h" #include "util.h" #include #include #include #define DEBUG_FLAG GABBLE_DEBUG_FT #include "debug.h" static void channel_manager_iface_init (gpointer, gpointer); static void gabble_ft_manager_channel_created (GabbleFtManager *mgr, GabbleFileTransferChannel *chan, gpointer request_token); static void caps_channel_manager_iface_init (gpointer g_iface, gpointer iface_data); G_DEFINE_TYPE_WITH_CODE (GabbleFtManager, gabble_ft_manager, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, channel_manager_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_CAPS_CHANNEL_MANAGER, caps_channel_manager_iface_init)); /* properties */ enum { PROP_CONNECTION = 1, LAST_PROPERTY }; /* private structure */ struct _GabbleFtManagerPrivate { gboolean dispose_has_run; GabbleConnection *connection; GList *channels; /* path of the temporary directory used to store UNIX sockets */ gchar *tmp_dir; gulong status_changed_id; }; static void gabble_ft_manager_init (GabbleFtManager *obj) { obj->priv = G_TYPE_INSTANCE_GET_PRIVATE (obj, GABBLE_TYPE_FT_MANAGER, GabbleFtManagerPrivate); obj->priv->connection = NULL; /* allocate any data required by the object here */ obj->priv->channels = NULL; } static void gabble_ft_manager_constructed (GObject *object); static void gabble_ft_manager_dispose (GObject *object); static void gabble_ft_manager_finalize (GObject *object); static void connection_status_changed_cb (GabbleConnection *conn, guint status, guint reason, GabbleFtManager *self); static void gabble_ft_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GabbleFtManager *self = GABBLE_FT_MANAGER (object); switch (property_id) { case PROP_CONNECTION: g_value_set_object (value, self->priv->connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_ft_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GabbleFtManager *self = GABBLE_FT_MANAGER (object); switch (property_id) { case PROP_CONNECTION: self->priv->connection = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_ft_manager_class_init (GabbleFtManagerClass *gabble_ft_manager_class) { GObjectClass *object_class = G_OBJECT_CLASS (gabble_ft_manager_class); GParamSpec *param_spec; g_type_class_add_private (gabble_ft_manager_class, sizeof (GabbleFtManagerPrivate)); object_class->constructed = gabble_ft_manager_constructed; object_class->get_property = gabble_ft_manager_get_property; object_class->set_property = gabble_ft_manager_set_property; object_class->dispose = gabble_ft_manager_dispose; object_class->finalize = gabble_ft_manager_finalize; param_spec = g_param_spec_object ("connection", "GabbleConnection object", "Gabble Connection that owns the connection for this FT manager", GABBLE_TYPE_CONNECTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); } static void gabble_ft_manager_constructed (GObject *object) { void (*chain_up) (GObject *) = G_OBJECT_CLASS (gabble_ft_manager_parent_class)->constructed; GabbleFtManager *self = GABBLE_FT_MANAGER (object); if (chain_up != NULL) chain_up (object); self->priv->status_changed_id = g_signal_connect (self->priv->connection, "status-changed", (GCallback) connection_status_changed_cb, object); } static void ft_manager_close_all (GabbleFtManager *self) { GList *l; while ((l = self->priv->channels) != NULL) { tp_base_channel_close (l->data); /* Channels should have closed and disappeared from the list */ g_assert (l != self->priv->channels); } } void gabble_ft_manager_dispose (GObject *object) { GabbleFtManager *self = GABBLE_FT_MANAGER (object); if (self->priv->dispose_has_run) return; self->priv->dispose_has_run = TRUE; g_assert (self->priv->channels == NULL); if (G_OBJECT_CLASS (gabble_ft_manager_parent_class)->dispose) G_OBJECT_CLASS (gabble_ft_manager_parent_class)->dispose (object); } void gabble_ft_manager_finalize (GObject *object) { GabbleFtManager *self = GABBLE_FT_MANAGER (object); if (self->priv->tmp_dir != NULL) { if (g_rmdir (self->priv->tmp_dir) != 0) { DEBUG ("rmdir failed: %s", g_strerror (errno)); } } g_free (self->priv->tmp_dir); G_OBJECT_CLASS (gabble_ft_manager_parent_class)->finalize (object); } /* Channel Manager interface */ struct foreach_data { TpExportableChannelFunc func; gpointer data; }; static void gabble_ft_manager_iface_foreach_one (gpointer value, gpointer data) { TpExportableChannel *chan; struct foreach_data *f = (struct foreach_data *) data; chan = TP_EXPORTABLE_CHANNEL (value); f->func (chan, f->data); } static void gabble_ft_manager_foreach_channel (TpChannelManager *iface, TpExportableChannelFunc func, gpointer data) { GabbleFtManager *self = GABBLE_FT_MANAGER (iface); struct foreach_data f; f.func = func; f.data = data; g_list_foreach (self->priv->channels, (GFunc) gabble_ft_manager_iface_foreach_one, &f); } static void file_channel_closed_cb (GabbleFileTransferChannel *chan, gpointer user_data) { GabbleFtManager *self = GABBLE_FT_MANAGER (user_data); if (self->priv->channels != NULL) { gchar *path, *id; g_object_get (chan, "target-id", &id, "object-path", &path, NULL); DEBUG ("Removing channel %s with %s", path, id); self->priv->channels = g_list_remove (self->priv->channels, chan); g_object_unref (chan); g_free (id); g_free (path); } } #ifdef ENABLE_JINGLE_FILE_TRANSFER static void gabble_ft_manager_channels_created (GabbleFtManager *self, GList *channels) { GList *i; for (i = channels; i ; i = i->next) { GabbleFileTransferChannel *chan = i->data; tp_base_channel_register (TP_BASE_CHANNEL (chan)); gabble_signal_connect_weak (chan, "closed", G_CALLBACK (file_channel_closed_cb), G_OBJECT (self)); self->priv->channels = g_list_append (self->priv->channels, chan); /* The channels can't satisfy a request because this will always be called when we receive an incoming jingle-share session */ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan), NULL); } } #endif static void gabble_ft_manager_channel_created (GabbleFtManager *self, GabbleFileTransferChannel *chan, gpointer request_token) { GSList *requests = NULL; tp_base_channel_register (TP_BASE_CHANNEL (chan)); gabble_signal_connect_weak (chan, "closed", G_CALLBACK (file_channel_closed_cb), G_OBJECT (self)); self->priv->channels = g_list_append (self->priv->channels, chan); if (request_token != NULL) requests = g_slist_prepend (requests, request_token); tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan), requests); g_slist_free (requests); } #ifdef ENABLE_JINGLE_FILE_TRANSFER static void new_jingle_session_cb (GabbleJingleMint *jm, WockyJingleSession *sess, gpointer data) { GabbleFtManager *self = GABBLE_FT_MANAGER (data); GTalkFileCollection *gtalk_fc = NULL; WockyJingleContent *content = NULL; GabbleJingleShareManifest *manifest = NULL; GList *channels = NULL; GList *cs, *i; if (wocky_jingle_session_get_content_type (sess) == GABBLE_TYPE_JINGLE_SHARE) { cs = wocky_jingle_session_get_contents (sess); if (cs != NULL) { content = WOCKY_JINGLE_CONTENT (cs->data); g_list_free (cs); } if (content == NULL) return; gtalk_fc = gtalk_file_collection_new_from_session ( gabble_jingle_mint_get_factory (jm), sess); if (gtalk_fc) { gchar *token = NULL; g_object_get (gtalk_fc, "token", &token, NULL); manifest = gabble_jingle_share_get_manifest ( GABBLE_JINGLE_SHARE (content)); for (i = manifest->entries; i; i = i->next) { GabbleJingleShareManifestEntry *entry = i->data; GabbleFileTransferChannel *channel = NULL; gchar *filename = NULL; TpHandleRepoIface *contacts = tp_base_connection_get_handles ( TP_BASE_CONNECTION (self->priv->connection), TP_HANDLE_TYPE_CONTACT); TpHandle peer = tp_handle_ensure (contacts, wocky_jingle_session_get_peer_jid (sess), NULL, NULL); filename = g_strdup_printf ("%s%s", entry->name, entry->folder? ".tar":""); channel = gabble_file_transfer_channel_new (self->priv->connection, peer, peer, TP_FILE_TRANSFER_STATE_PENDING, NULL, filename, entry->size, TP_FILE_HASH_TYPE_NONE, NULL, NULL, 0, 0, FALSE, NULL, gtalk_fc, token, NULL, NULL, NULL); g_free (filename); gtalk_file_collection_add_channel (gtalk_fc, channel); channels = g_list_prepend (channels, channel); } if (channels != NULL) gabble_ft_manager_channels_created (self, channels); g_list_free (channels); /* Channels will hold the reference to the gtalk file collection, so we can drop ours already. If no channels were created, then we need to destroy it anyways */ g_object_unref (gtalk_fc); } } } #endif static void connection_status_changed_cb (GabbleConnection *conn, guint status, guint reason, GabbleFtManager *self) { switch (status) { #ifdef ENABLE_JINGLE_FILE_TRANSFER case TP_CONNECTION_STATUS_CONNECTING: g_signal_connect (self->priv->connection->jingle_mint, "incoming-session", G_CALLBACK (new_jingle_session_cb), self); break; #endif case TP_CONNECTION_STATUS_DISCONNECTED: ft_manager_close_all (self); if (self->priv->status_changed_id != 0) { g_signal_handler_disconnect (self->priv->connection, self->priv->status_changed_id); self->priv->status_changed_id = 0; } break; } } static gboolean gabble_ft_manager_handle_request (TpChannelManager *manager, gpointer request_token, GHashTable *request_properties) { GabbleFtManager *self = GABBLE_FT_MANAGER (manager); GabbleFileTransferChannel *chan; TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->connection); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); TpHandle handle; const gchar *content_type, *filename, *content_hash, *description; const gchar *file_uri, *service_name; guint64 size, date, initial_offset; TpFileHashType content_hash_type; const GHashTable *metadata; GError *error = NULL; gboolean valid; DEBUG ("File transfer request"); /* We only support file transfer channels */ if (tp_strdiff (tp_asv_get_string (request_properties, TP_IFACE_CHANNEL ".ChannelType"), TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER)) return FALSE; /* And only contact handles */ if (tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT) return FALSE; handle = tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL ".TargetHandle", NULL); /* Must be a valid contact handle */ if (!tp_handle_is_valid (contact_repo, handle, &error)) goto error; /* Don't support opening a channel to our self handle */ if (handle == tp_base_connection_get_self_handle (base_conn)) { g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Can't open a file transfer channel to yourself"); goto error; } content_type = tp_asv_get_string (request_properties, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType"); if (content_type == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "ContentType property is mandatory"); goto error; } filename = tp_asv_get_string (request_properties, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename"); if (filename == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Filename property is mandatory"); goto error; } size = tp_asv_get_uint64 (request_properties, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", NULL); if (size == 0) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Size property is mandatory"); goto error; } content_hash_type = tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", &valid); if (!valid) { /* Assume File_Hash_Type_None */ content_hash_type = TP_FILE_HASH_TYPE_NONE; } else { if (content_hash_type >= TP_NUM_FILE_HASH_TYPES) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "%u is not a valid ContentHashType", content_hash_type); goto error; } } if (content_hash_type != TP_FILE_HASH_TYPE_NONE) { content_hash = tp_asv_get_string (request_properties, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash"); if (content_hash == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "ContentHash property is mandatory if ContentHashType is " "not None"); goto error; } } else { content_hash = NULL; } description = tp_asv_get_string (request_properties, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Description"); date = tp_asv_get_uint64 (request_properties, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", NULL); initial_offset = tp_asv_get_uint64 (request_properties, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".InitialOffset", NULL); file_uri = tp_asv_get_string (request_properties, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI); service_name = tp_asv_get_string (request_properties, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME); metadata = tp_asv_get_boxed (request_properties, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA, TP_HASH_TYPE_METADATA); if (metadata != NULL && g_hash_table_lookup ((GHashTable *) metadata, "FORM_TYPE")) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Metadata cannot contain an item with key 'FORM_TYPE'"); goto error; } DEBUG ("Requested outgoing channel with contact: %s", tp_handle_inspect (contact_repo, handle)); chan = gabble_file_transfer_channel_new (self->priv->connection, handle, tp_base_connection_get_self_handle (base_conn), TP_FILE_TRANSFER_STATE_PENDING, content_type, filename, size, content_hash_type, content_hash, description, date, initial_offset, TRUE, NULL, NULL, NULL, file_uri, service_name, metadata); if (!gabble_file_transfer_channel_offer_file (chan, &error)) { g_object_unref (chan); goto error; } gabble_ft_manager_channel_created (self, chan, request_token); return TRUE; error: tp_channel_manager_emit_request_failed (self, request_token, error->domain, error->code, error->message); g_error_free (error); return TRUE; } /* Keep in sync with values set in gabble_ft_manager_type_foreach_channel_class */ static const gchar * const file_transfer_channel_fixed_properties[] = { TP_IFACE_CHANNEL ".ChannelType", TP_IFACE_CHANNEL ".TargetHandleType", NULL }; /* ContentHashType has to be first so we can easily skip it when needed */ #define STANDARD_PROPERTIES \ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE, \ TP_PROP_CHANNEL_TARGET_HANDLE, \ TP_PROP_CHANNEL_TARGET_ID, \ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE, \ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME, \ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, \ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH, \ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DESCRIPTION, \ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DATE, \ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI static const gchar * const file_transfer_channel_allowed_properties[] = { STANDARD_PROPERTIES, NULL }; static const gchar * const file_transfer_channel_allowed_properties_with_metadata_prop[] = { STANDARD_PROPERTIES, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA, NULL }; static const gchar * const file_transfer_channel_allowed_properties_with_both_metadata_props[] = { STANDARD_PROPERTIES, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA, NULL }; static void gabble_ft_manager_type_foreach_channel_class (GType type, TpChannelManagerTypeChannelClassFunc func, gpointer user_data) { GHashTable *table; /* general FT class */ table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType" , tp_g_value_slice_new_string (TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER)); g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", tp_g_value_slice_new_uint (TP_HANDLE_TYPE_CONTACT)); func (type, table, file_transfer_channel_allowed_properties_with_both_metadata_props, user_data); /* MD5 HashType class */ g_hash_table_insert (table, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", tp_g_value_slice_new_uint (TP_FILE_HASH_TYPE_MD5)); /* skip ContentHashType in allowed properties */ func (type, table, file_transfer_channel_allowed_properties_with_both_metadata_props + 1, user_data); g_hash_table_unref (table); } static WockyNode * hyvaa_vappua ( WockyNode *si_node, const gchar **filename, const gchar **size_str, GError **error) { #define die_if_null(var, msg) \ if ((var) == NULL) \ { \ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST, \ msg); \ return NULL; \ } WockyNode *file_node = wocky_node_get_child_ns (si_node, "file", NS_FILE_TRANSFER); die_if_null (file_node, "Invalid file transfer SI request: no ") die_if_null (*filename = wocky_node_get_attribute (file_node, "name"), "Invalid file transfer SI request: missing file name") die_if_null (*size_str = wocky_node_get_attribute (file_node, "size"), "Invalid file transfer SI request: missing file size") return file_node; #undef die_if_null } static WockyDataForm * find_data_form (WockyNode *file, const gchar *form_type) { WockyNodeIter iter; WockyNode *x; wocky_node_iter_init (&iter, file, "x", NS_X_DATA); while (wocky_node_iter_next (&iter, &x)) { GError *error = NULL; WockyDataForm *form = wocky_data_form_new_from_node (x, &error); WockyDataFormField *field; if (form == NULL) { DEBUG ("Failed to parse data form: %s", error->message); g_clear_error (&error); continue; } field = g_hash_table_lookup (form->fields, "FORM_TYPE"); if (field == NULL) { DEBUG ("Data form doesn't have FORM_TYPE field!"); g_object_unref (form); continue; } /* found it! */ if (!tp_strdiff (field->raw_value_contents[0], form_type)) return form; g_object_unref (form); } return NULL; } static gchar * extract_service_name (WockyNode *file) { WockyDataForm *form = find_data_form (file, NS_TP_FT_METADATA_SERVICE); WockyDataFormField *field; gchar *service_name = NULL; if (form == NULL) return NULL; field = g_hash_table_lookup (form->fields, "ServiceName"); if (field == NULL) { DEBUG ("ServiceName property not present in data form; odd..."); goto out; } if (field->raw_value_contents == NULL || field->raw_value_contents[0] == NULL) { DEBUG ("ServiceName property doesn't have a real value; odd..."); } else { service_name = g_strdup (field->raw_value_contents[0]); } out: g_object_unref (form); return service_name; } static GHashTable * extract_metadata (WockyNode *file) { WockyDataForm *form = find_data_form (file, NS_TP_FT_METADATA); GHashTable *metadata; GHashTableIter iter; gpointer key, value; if (form == NULL) return NULL; metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_strfreev); g_hash_table_iter_init (&iter, form->fields); while (g_hash_table_iter_next (&iter, &key, &value)) { const gchar *var = key; WockyDataFormField *field = value; if (!tp_strdiff (var, "FORM_TYPE")) continue; g_hash_table_insert (metadata, g_strdup (var), g_strdupv (field->raw_value_contents)); } g_object_unref (form); return metadata; } void gabble_ft_manager_handle_si_request (GabbleFtManager *self, GabbleBytestreamIface *bytestream, TpHandle handle, const gchar *stream_id, WockyStanza *msg) { WockyNode *si_node, *file_node, *desc_node; const gchar *filename, *size_str, *content_type, *content_hash, *description; const gchar *date_str; gchar *service_name; GHashTable *metadata; guint64 size; guint64 date = 0; TpFileHashType content_hash_type; GabbleFileTransferChannel *chan; gboolean resume_supported; GError *error = NULL; si_node = wocky_node_get_child_ns ( wocky_stanza_get_top_node (msg), "si", NS_SI); g_assert (si_node != NULL); file_node = hyvaa_vappua (si_node, &filename, &size_str, &error); if (file_node == NULL) { DEBUG ("%s", error->message); gabble_bytestream_iface_close (bytestream, error); g_clear_error (&error); return; } size = g_ascii_strtoull (size_str, NULL, 0); content_type = wocky_node_get_attribute (file_node, "mime-type"); if (content_type == NULL) content_type = "application/octet-stream"; /* The hash is always an MD5-sum, if present. */ content_hash = wocky_node_get_attribute (file_node, "hash"); if (content_hash != NULL) content_hash_type = TP_FILE_HASH_TYPE_MD5; else content_hash_type = TP_FILE_HASH_TYPE_NONE; desc_node = wocky_node_get_child (file_node, "desc"); if (desc_node != NULL) description = desc_node->content; else description = NULL; date_str = wocky_node_get_attribute (file_node, "date"); if (date_str != NULL) { GTimeVal val; /* FIXME: this assume the timezone is always UTC */ if (g_time_val_from_iso8601 (date_str, &val)) date = val.tv_sec; } resume_supported = (wocky_node_get_child (file_node, "range") != NULL); /* metadata */ service_name = extract_service_name (file_node); metadata = extract_metadata (file_node); chan = gabble_file_transfer_channel_new (self->priv->connection, handle, handle, TP_FILE_TRANSFER_STATE_PENDING, content_type, filename, size, content_hash_type, content_hash, description, date, 0, resume_supported, bytestream, NULL, NULL, NULL, service_name, metadata); gabble_ft_manager_channel_created (self, chan, NULL); g_free (service_name); if (metadata != NULL) g_hash_table_unref (metadata); } static void channel_manager_iface_init (gpointer g_iface, gpointer iface_data) { TpChannelManagerIface *iface = g_iface; iface->foreach_channel = gabble_ft_manager_foreach_channel; iface->type_foreach_channel_class = gabble_ft_manager_type_foreach_channel_class; iface->create_channel = gabble_ft_manager_handle_request; iface->ensure_channel = gabble_ft_manager_handle_request; } /* public functions */ GabbleFtManager * gabble_ft_manager_new (GabbleConnection *connection) { g_assert (connection != NULL); return g_object_new (GABBLE_TYPE_FT_MANAGER, "connection", connection, NULL); } #ifdef G_OS_UNIX /* We assume that all Unixes have mkdtemp */ const gchar * gabble_ft_manager_get_tmp_dir (GabbleFtManager *self) { if (self->priv->tmp_dir != NULL) return self->priv->tmp_dir; self->priv->tmp_dir = g_strdup_printf ("%s/gabble-ft-XXXXXX", g_get_tmp_dir ()); self->priv->tmp_dir = mkdtemp (self->priv->tmp_dir); if (self->priv->tmp_dir == NULL) DEBUG ("mkdtemp failed: %s\n", g_strerror (errno)); return self->priv->tmp_dir; } #endif static void add_file_transfer_channel_class (GPtrArray *arr, gboolean include_metadata_properties, const gchar *service_name_str) { GValue monster = {0, }; GHashTable *fixed_properties; GValue *channel_type_value; GValue *target_handle_type_value; GValue *service_name_value; const gchar * const *allowed_properties; g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS); g_value_take_boxed (&monster, dbus_g_type_specialized_construct ( TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS)); fixed_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); channel_type_value = tp_g_value_slice_new_static_string ( TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER); g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType", channel_type_value); target_handle_type_value = tp_g_value_slice_new_uint (TP_HANDLE_TYPE_CONTACT); g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".TargetHandleType", target_handle_type_value); if (service_name_str != NULL) { service_name_value = tp_g_value_slice_new_string (service_name_str); g_hash_table_insert (fixed_properties, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME, service_name_value); } if (include_metadata_properties) { if (service_name_str == NULL) allowed_properties = file_transfer_channel_allowed_properties_with_both_metadata_props; else allowed_properties = file_transfer_channel_allowed_properties_with_metadata_prop; } else { allowed_properties = file_transfer_channel_allowed_properties; } dbus_g_type_struct_set (&monster, 0, fixed_properties, 1, allowed_properties, G_MAXUINT); g_hash_table_unref (fixed_properties); g_ptr_array_add (arr, g_value_get_boxed (&monster)); } static void get_contact_caps_foreach (gpointer data, gpointer user_data) { const gchar *ns = data; GPtrArray *arr = user_data; if (!g_str_has_prefix (ns, NS_TP_FT_METADATA "#")) return; add_file_transfer_channel_class (arr, TRUE, ns + strlen (NS_TP_FT_METADATA "#")); } static void gabble_ft_manager_get_contact_caps ( GabbleCapsChannelManager *manager G_GNUC_UNUSED, TpHandle handle G_GNUC_UNUSED, const GabbleCapabilitySet *caps, GPtrArray *arr) { if (gabble_capability_set_has (caps, NS_FILE_TRANSFER) || gabble_capability_set_has (caps, NS_GOOGLE_FEAT_SHARE)) { add_file_transfer_channel_class (arr, gabble_capability_set_has (caps, NS_TP_FT_METADATA), NULL); } gabble_capability_set_foreach (caps, get_contact_caps_foreach, arr); } static void gabble_ft_manager_represent_client ( GabbleCapsChannelManager *manager G_GNUC_UNUSED, const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens G_GNUC_UNUSED, GabbleCapabilitySet *cap_set, GPtrArray *data_forms G_GNUC_UNUSED) { guint i; for (i = 0; i < filters->len; i++) { GHashTable *channel_class = g_ptr_array_index (filters, i); const gchar *service_name; gchar *ns; if (tp_strdiff (tp_asv_get_string (channel_class, TP_IFACE_CHANNEL ".ChannelType"), TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER)) continue; if (tp_asv_get_uint32 (channel_class, TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT) continue; DEBUG ("client %s supports file transfer", client_name); gabble_capability_set_add (cap_set, NS_FILE_TRANSFER); gabble_capability_set_add (cap_set, NS_GOOGLE_FEAT_SHARE); gabble_capability_set_add (cap_set, NS_TP_FT_METADATA); /* now look at service names */ /* capabilities mean being able to RECEIVE said kinds of * FTs. hence, skip Requested=true (locally initiated) channel * classes */ if (tp_asv_get_boolean (channel_class, TP_PROP_CHANNEL_REQUESTED, FALSE)) continue; service_name = tp_asv_get_string (channel_class, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME); if (service_name == NULL) continue; ns = g_strconcat (NS_TP_FT_METADATA "#", service_name, NULL); DEBUG ("%s: adding capability %s", client_name, ns); gabble_capability_set_add (cap_set, ns); g_free (ns); } } static void caps_channel_manager_iface_init (gpointer g_iface, gpointer iface_data) { GabbleCapsChannelManagerInterface *iface = g_iface; iface->get_contact_caps = gabble_ft_manager_get_contact_caps; iface->represent_client = gabble_ft_manager_represent_client; }