diff options
-rw-r--r-- | libempathy/Makefile.am | 4 | ||||
-rw-r--r-- | libempathy/empathy-ft-factory.c | 13 | ||||
-rw-r--r-- | libempathy/empathy-ft-factory.h | 4 | ||||
-rw-r--r-- | libempathy/empathy-ft-handler.c | 377 |
4 files changed, 381 insertions, 17 deletions
diff --git a/libempathy/Makefile.am b/libempathy/Makefile.am index 2406a0f9d..c9f2c4206 100644 --- a/libempathy/Makefile.am +++ b/libempathy/Makefile.am @@ -34,6 +34,8 @@ libempathy_la_SOURCES = \ empathy-debug.c \ empathy-dispatcher.c \ empathy-dispatch-operation.c \ + empathy-ft-factory.c \ + empathy-ft-handler.c \ empathy-idle.c \ empathy-irc-network.c \ empathy-irc-network-manager.c \ @@ -81,6 +83,8 @@ libempathy_headers = \ empathy-debug.h \ empathy-dispatcher.h \ empathy-dispatch-operation.h \ + empathy-ft-factory.h \ + empathy-ft-handler.h \ empathy-idle.h \ empathy-irc-network.h \ empathy-irc-network-manager.h \ diff --git a/libempathy/empathy-ft-factory.c b/libempathy/empathy-ft-factory.c index 0356c32b6..5d8e62b22 100644 --- a/libempathy/empathy-ft-factory.c +++ b/libempathy/empathy-ft-factory.c @@ -21,18 +21,21 @@ /* empathy-ft-factory.c */ +#include <glib.h> + #include "empathy-ft-factory.h" +#include "empathy-ft-handler.h" #include "empathy-marshal.h" #include "empathy-utils.h" G_DEFINE_TYPE (EmpathyFTFactory, empathy_ft_factory, G_TYPE_OBJECT); -#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTFactoryPriv) +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTFactory) enum { NEW_FT_HANDLER, LAST_SIGNAL -} +}; typedef struct { gboolean dispose_run; @@ -93,7 +96,7 @@ empathy_ft_factory_class_init (EmpathyFTFactoryClass *klass) signals[NEW_FT_HANDLER] = g_signal_new ("new-ft-handler", - G_TYPE_FROM_CLASS (empathy_call_factory_class), + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, _empathy_marshal_VOID__OBJECT_BOOLEAN, @@ -139,8 +142,8 @@ void empathy_ft_factory_claim_channel (EmpathyFTFactory *factory, EmpathyDispatchOperation *operation) { - g_return_val_if_fail (EMPATHY_IS_FACTORY (factory)); - g_return_val_if_fail (EMPATHY_IS_DISPATCH_OPERATION (operation)); + g_return_if_fail (EMPATHY_IS_FT_FACTORY (factory)); + g_return_if_fail (EMPATHY_IS_DISPATCH_OPERATION (operation)); /* TODO */ } diff --git a/libempathy/empathy-ft-factory.h b/libempathy/empathy-ft-factory.h index 3f4f6b2b9..d838499f6 100644 --- a/libempathy/empathy-ft-factory.h +++ b/libempathy/empathy-ft-factory.h @@ -25,6 +25,10 @@ #define __EMPATHY_FT_FACTORY_H__ #include <glib-object.h> +#include <gio/gio.h> + +#include "empathy-contact.h" +#include "empathy-dispatch-operation.h" G_BEGIN_DECLS diff --git a/libempathy/empathy-ft-handler.c b/libempathy/empathy-ft-handler.c index b9e0f47dd..89258865c 100644 --- a/libempathy/empathy-ft-handler.c +++ b/libempathy/empathy-ft-handler.c @@ -21,11 +21,18 @@ /* empathy-ft-handler.c */ +#include <extensions/extensions.h> +#include <glib.h> +#include <telepathy-glib/util.h> + #include "empathy-ft-handler.h" +#include "empathy-utils.h" G_DEFINE_TYPE (EmpathyFTHandler, empathy_ft_handler, G_TYPE_OBJECT) -#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTHandlerPriv) +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTHandler) + +#define BUFFER_SIZE 4096 enum { PROP_TP_FILE = 1, @@ -33,13 +40,33 @@ enum { PROP_CONTACT }; -typedef struct EmpathyFTHandlerPriv { +typedef struct { + EmpathyFTHandler *handler; + GFile *gfile; + GHashTable *request; +} RequestData; + +typedef struct { + RequestData *req_data; + GInputStream *stream; + gboolean done_reading; + GError *error; + guchar *buffer; + GChecksum *checksum; +} HashingData; + +/* private data */ +typedef struct { gboolean dispose_run; EmpathyContact *contact; GFile *gfile; EmpathyTpFile *tpfile; -}; +} EmpathyFTHandlerPriv; +/* prototypes */ +static void schedule_hash_chunk (HashingData *hash_data); + +/* GObject implementations */ static void do_get_property (GObject *object, guint property_id, @@ -70,7 +97,7 @@ do_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - EmpathyCallHandlerPriv *priv = GET_PRIV (object); + EmpathyFTHandlerPriv *priv = GET_PRIV (object); switch (property_id) { @@ -129,7 +156,7 @@ empathy_ft_handler_class_init (EmpathyFTHandlerClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *param_spec; - g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPrivate)); + g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPriv)); object_class->get_property = do_get_property; object_class->set_property = do_set_property; @@ -164,16 +191,336 @@ empathy_ft_handler_init (EmpathyFTHandler *self) self->priv = priv; } +/* private functions */ + +static void +hash_data_free (HashingData *data) +{ + if (data->buffer != NULL) + { + g_free (data->buffer); + data->buffer = NULL; + } + + if (data->stream != NULL) + { + g_object_unref (data->stream); + data->stream = NULL; + } + + if (data->checksum != NULL) + { + g_checksum_free (data->checksum); + data->checksum = NULL; + } + + if (data->error != NULL) + { + g_error_free (data->error); + data->error = NULL; + } + + g_slice_free (HashingData, data); +} + +static void +request_data_free (RequestData *data) +{ + if (data->gfile != NULL) + { + g_object_unref (data->gfile); + data->gfile = NULL; + } + + if (data->request != NULL) + { + g_hash_table_unref (data->request); + data->request = NULL; + } + + g_slice_free (RequestData, data); +} + +static RequestData * +request_data_new (EmpathyFTHandler *handler, GFile *gfile) +{ + RequestData *ret; + + ret = g_slice_new0 (RequestData); + ret->request = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) tp_g_value_slice_free); + ret->handler = g_object_ref (handler); + ret->gfile = g_object_ref (gfile); + + return ret; +} + +static void +ft_handler_push_to_dispatcher (RequestData *req_data) +{ + /* TODO: */ +} + +static void +ft_handler_populate_outgoing_request (RequestData *req_data, + GFileInfo *file_info) +{ + guint contact_handle; + const char *content_type; + const char *display_name; + goffset size; + GTimeVal mtime; + GValue *value; + GHashTable *request = req_data->request; + EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler); + + /* gather all the information */ + contact_handle = empathy_contact_get_handle (priv->contact); + + content_type = g_file_info_get_content_type (file_info); + display_name = g_file_info_get_display_name (file_info); + size = g_file_info_get_size (file_info); + g_file_info_get_modification_time (file_info, &mtime); + + /* org.freedesktop.Telepathy.Channel.ChannelType */ + value = tp_g_value_slice_new (G_TYPE_STRING); + g_value_set_string (value, EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER); + g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value); + + /* org.freedesktop.Telepathy.Channel.TargetHandleType */ + value = tp_g_value_slice_new (G_TYPE_UINT); + g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); + g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value); + + /* org.freedesktop.Telepathy.Channel.TargetHandle */ + value = tp_g_value_slice_new (G_TYPE_UINT); + g_value_set_uint (value, contact_handle); + g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandle", value); + + /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentType */ + value = tp_g_value_slice_new (G_TYPE_STRING); + g_value_set_string (value, content_type); + g_hash_table_insert (request, + EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType", value); + + /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Filename */ + value = tp_g_value_slice_new (G_TYPE_STRING); + g_value_set_string (value, display_name); + g_hash_table_insert (request, + EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename", value); + + /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Size */ + value = tp_g_value_slice_new (G_TYPE_UINT64); + g_value_set_uint64 (value, (guint64) size); + g_hash_table_insert (request, + EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", value); + + /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Date */ + value = tp_g_value_slice_new (G_TYPE_UINT64); + g_value_set_uint64 (value, (guint64) mtime.tv_sec); + g_hash_table_insert (request, + EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", value); +} + +static void +hash_job_async_close_stream_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + HashingData *hash_data = user_data; + RequestData *req_data = hash_data->req_data; + GError *error = NULL; + GValue *value; + GHashTable *request; + + /* if we're here we for sure have done reading, check if we stopped due + * to an error. + */ + g_input_stream_close_finish (hash_data->stream, res, &error); + if (error != NULL) + { + if (hash_data->error != NULL) + { + /* if we already stopped due to an error, probably we're completely + * hosed for some reason. just return the first read error + * to the user. + */ + g_clear_error (&error); + error = hash_data->error; + } + + goto cleanup; + } + + if (hash_data->error != NULL) + { + error = hash_data->error; + goto cleanup; + } + + /* set the checksum in the request */ + request = req_data->request; + + /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash */ + value = tp_g_value_slice_new (G_TYPE_STRING); + g_value_set_string (value, g_checksum_get_string (hash_data->checksum)); + g_hash_table_insert (request, + EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash", value); + +cleanup: + hash_data_free (hash_data); + + if (error != NULL) + { + /* TODO: error handling. */ + } + else + { + /* the request is complete now, push it to the dispatcher */ + ft_handler_push_to_dispatcher (req_data); + } +} + +static void +hash_job_async_read_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + HashingData *hash_data = user_data; + gssize bytes_read; + GError *error = NULL; + + bytes_read = g_input_stream_read_finish (hash_data->stream, res, &error); + if (error != NULL) + { + hash_data->error = error; + hash_data->done_reading = TRUE; + goto out; + } + + /* TODO: notify progress */ + + /* we now have the chunk */ + if (bytes_read == 0) + { + hash_data->done_reading = TRUE; + schedule_hash_chunk (hash_data); + goto out; + } + else + { + g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read); + } + +out: + schedule_hash_chunk (hash_data); +} + +static void +schedule_hash_chunk (HashingData *hash_data) +{ + if (hash_data->done_reading) + { + g_input_stream_close_async (hash_data->stream, G_PRIORITY_DEFAULT, + NULL, hash_job_async_close_stream_cb, hash_data); + } + else + { + if (hash_data->buffer != NULL) + { + g_free (hash_data->buffer); + hash_data->buffer = g_malloc0 (BUFFER_SIZE); + } + + g_input_stream_read_async (hash_data->stream, hash_data->buffer, + BUFFER_SIZE, G_PRIORITY_DEFAULT, NULL, + hash_job_async_read_cb, hash_data); + } +} + +static void +ft_handler_read_async_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GFileInputStream *stream; + GError *error = NULL; + HashingData *hash_data; + GHashTable *request; + GValue *value; + RequestData *req_data = user_data; + + stream = g_file_read_finish (req_data->gfile, res, &error); + if (error != NULL) + { + /* TODO: error handling. */ + return; + } + + hash_data = g_slice_new0 (HashingData); + hash_data->stream = G_INPUT_STREAM (stream); + hash_data->done_reading = FALSE; + hash_data->req_data = req_data; + hash_data->error = NULL; + /* FIXME: should look at the CM capabilities before setting the + * checksum type? + */ + hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5); + + request = req_data->request; + + /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHashType */ + value = tp_g_value_slice_new (G_TYPE_UINT); + g_value_set_uint (value, EMP_FILE_HASH_TYPE_MD5); + g_hash_table_insert (request, + EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", value); + + schedule_hash_chunk (hash_data); +} + +static void +ft_handler_gfile_ready_cb (GObject *source, + GAsyncResult *res, + RequestData *req_data) +{ + GFileInfo *info; + GError *error = NULL; + + info = g_file_query_info_finish (req_data->gfile, res, &error); + if (error != NULL) + { + /* TODO: error handling. */ + return; + } + + ft_handler_populate_outgoing_request (req_data, info); + + /* now start hashing the file */ + g_file_read_async (req_data->gfile, G_PRIORITY_DEFAULT, + NULL, ft_handler_read_async_cb, req_data); +} + static void empathy_ft_handler_contact_ready_cb (EmpathyContact *contact, const GError *error, gpointer user_data, - GObject *weak_object); + GObject *weak_object) { - EmpathyFTHandler *handler = EMPATHY_FT_HANDLER (weak_object); - EmpathyFTHandlerPriv *priv = GET_PRIV (handler); + RequestData *req_data = user_data; + EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler); + + g_assert (priv->contact != NULL); + g_assert (priv->gfile != NULL); /* start collecting info about the file */ + g_file_query_info_async (req_data->gfile, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," + G_FILE_ATTRIBUTE_STANDARD_SIZE "," + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, + NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb, + req_data); } /* public methods */ @@ -186,7 +533,7 @@ empathy_ft_handler_new (EmpathyContact *contact, g_return_val_if_fail (G_IS_FILE (file), NULL); return g_object_new (EMPATHY_TYPE_FT_HANDLER, - "contact", contact, "gfile", file, NULL); + "contact", contact, "gfile", file, NULL); } EmpathyFTHandler * @@ -195,19 +542,25 @@ empathy_ft_handler_new_for_channel (EmpathyTpFile *file) g_return_val_if_fail (EMPATHY_IS_TP_FILE (file), NULL); return g_object_new (EMPATHY_TYPE_FT_HANDLER, - "tp-file", file, NULL); + "tp-file", file, NULL); } void empathy_ft_handler_start_transfer (EmpathyFTHandler *handler) { + RequestData *data; + EmpathyFTHandlerPriv *priv; + g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler)); + priv = GET_PRIV (handler); + if (priv->tpfile == NULL) { + data = request_data_new (handler, priv->gfile); empathy_contact_call_when_ready (priv->contact, - EMPATHY_CONTACT_READY_HANDLE, - empathy_ft_handler_contact_ready_cb, NULL, NULL, G_OBJECT (handler)); + EMPATHY_CONTACT_READY_HANDLE, + empathy_ft_handler_contact_ready_cb, data, NULL, G_OBJECT (handler)); } else { |