--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/xfer.c Thu Sep 05 21:34:15 2013 +0530 @@ -0,0 +1,1981 @@ +/** + * @file xfer.c File Transfer API + */ + +/* purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ +#include "internal.h" +#include "dbus-maybe.h" +#include "xfer.h" +#include "network.h" +#include "notify.h" +#include "prefs.h" +#include "proxy.h" +#include "request.h" +#include "util.h" +#include "debug.h" + +#define FT_INITIAL_BUFFER_SIZE 4096 +#define FT_MAX_BUFFER_SIZE 65535 + +#define PURPLE_XFER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_XFER, PurpleXferPrivate)) + +/** @copydoc _PurpleXferPrivate */ +typedef struct _PurpleXferPrivate PurpleXferPrivate; + +static PurpleXferUiOps *xfer_ui_ops = NULL; +static GList *xfers; + +/* TODO remove. + * A hack to store more data since we can't extend the size of PurpleXfer + * easily. + */ +static GHashTable *xfers_data = NULL; + +/** Private data for a file transfer */ +struct _PurpleXferPrivate { + PurpleXferType type; /**< The type of transfer. */ + + PurpleAccount *account; /**< The account. */ + + char *who; /**< The person on the other end of the + transfer. */ + + char *message; /**< A message sent with the request */ + char *filename; /**< The name sent over the network. */ + char *local_filename; /**< The name on the local hard drive. */ + goffset size; /**< The size of the file. */ + + FILE *dest_fp; /**< The destination file pointer. */ + + char *remote_ip; /**< The remote IP address. */ + int local_port; /**< The local port. */ + int remote_port; /**< The remote port. */ + + int fd; /**< The socket file descriptor. */ + int watcher; /**< Watcher. */ + + goffset bytes_sent; /**< The number of bytes sent. */ + goffset bytes_remaining; /**< The number of bytes remaining. */ + time_t start_time; /**< When the transfer of data began. */ + time_t end_time; /**< When the transfer of data ended. */ + + size_t current_buffer_size; /**< This gradually increases for fast + network connections. */ + + PurpleXferStatus status; /**< File Transfer's status. */ + + PurpleXferIoOps *ops; /**< I/O operations. */ + PurpleXferUiOps *ui_ops; /**< UI-specific operations. */ + + void *proto_data; /**< prpl-specific data. + TODO Remove this, and use + protocol-specific subclasses */ + + /* + * Used to moderate the file transfer when either the read/write ui_ops are + * set or fd is not set. In those cases, the UI/prpl call the respective + * function, which is somewhat akin to a fd watch being triggered. + */ + enum { + PURPLE_XFER_READY_NONE = 0x0, + PURPLE_XFER_READY_UI = 0x1, + PURPLE_XFER_READY_PRPL = 0x2, + } ready; + + /* TODO: Should really use a PurpleCircBuffer for this. */ + GByteArray *buffer; + + gpointer thumbnail_data; /**< thumbnail image */ + gsize thumbnail_size; + gchar *thumbnail_mimetype; +}; + +static int purple_xfer_choose_file(PurpleXfer *xfer); + +static void +purple_xfer_priv_data_destroy(gpointer data) +{ + PurpleXferPrivate *priv = data; + + if (priv->buffer) + g_byte_array_free(priv->buffer, TRUE); + + g_free(priv->thumbnail_data); + + g_free(priv->thumbnail_mimetype); + + g_free(priv); +} + +static const gchar * +purple_xfer_status_type_to_string(PurpleXferStatus type) +{ + static const struct { + PurpleXferStatus type; + const char *name; + } type_names[] = { + { PURPLE_XFER_STATUS_UNKNOWN, "unknown" }, + { PURPLE_XFER_STATUS_NOT_STARTED, "not started" }, + { PURPLE_XFER_STATUS_ACCEPTED, "accepted" }, + { PURPLE_XFER_STATUS_STARTED, "started" }, + { PURPLE_XFER_STATUS_DONE, "done" }, + { PURPLE_XFER_STATUS_CANCEL_LOCAL, "cancelled locally" }, + { PURPLE_XFER_STATUS_CANCEL_REMOTE, "cancelled remotely" } + }; + gsize i; + + for (i = 0; i < G_N_ELEMENTS(type_names); ++i) + if (type_names[i].type == type) + return type_names[i].name; + + return "invalid state"; +} + +GList * +purple_xfers_get_all() +{ + return xfers; +} + +PurpleXfer * +purple_xfer_new(PurpleAccount *account, PurpleXferType type, const char *who) +{ + PurpleXfer *xfer; + PurpleXferUiOps *ui_ops; + PurpleXferPrivate *priv; + + g_return_val_if_fail(type != PURPLE_XFER_UNKNOWN, NULL); + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(who != NULL, NULL); + + xfer = g_new0(PurpleXfer, 1); + PURPLE_DBUS_REGISTER_POINTER(xfer, PurpleXfer); + + xfer->ref = 1; + xfer->type = type; + xfer->account = account; + xfer->who = g_strdup(who); + xfer->ui_ops = ui_ops = purple_xfers_get_ui_ops(); + xfer->message = NULL; + xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE; + xfer->fd = -1; + + priv = g_new0(PurpleXferPrivate, 1); + priv->ready = PURPLE_XFER_READY_NONE; + + if (ui_ops && ui_ops->data_not_sent) { + /* If the ui will handle unsent data no need for buffer */ + priv->buffer = NULL; + } else { + priv->buffer = g_byte_array_sized_new(FT_INITIAL_BUFFER_SIZE); + } + + g_hash_table_insert(xfers_data, xfer, priv); + + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->new_xfer != NULL) + ui_ops->new_xfer(xfer); + + xfers = g_list_prepend(xfers, xfer); + + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "new %p [%d]\n", xfer, xfer->ref); + + return xfer; +} + +static void +purple_xfer_destroy(PurpleXfer *xfer) +{ + PurpleXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "destroyed %p [%d]\n", xfer, xfer->ref); + + /* Close the file browser, if it's open */ + purple_request_close_with_handle(xfer); + + if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_STARTED) + purple_xfer_cancel_local(xfer); + + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->destroy != NULL) + ui_ops->destroy(xfer); + + g_free(xfer->who); + g_free(xfer->filename); + g_free(xfer->remote_ip); + g_free(xfer->local_filename); + + g_hash_table_remove(xfers_data, xfer); + + PURPLE_DBUS_UNREGISTER_POINTER(xfer); + xfers = g_list_remove(xfers, xfer); + g_free(xfer); +} + +void +purple_xfer_ref(PurpleXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + xfer->ref++; + + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "ref'd %p [%d]\n", xfer, xfer->ref); +} + +void +purple_xfer_unref(PurpleXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->ref > 0); + + xfer->ref--; + + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "unref'd %p [%d]\n", xfer, xfer->ref); + + if (xfer->ref == 0) + purple_xfer_destroy(xfer); +} + +void +purple_xfer_set_status(PurpleXfer *xfer, PurpleXferStatus status) +{ + g_return_if_fail(xfer != NULL); + + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "Changing status of xfer %p from %s to %s\n", + xfer, purple_xfer_status_type_to_string(xfer->status), + purple_xfer_status_type_to_string(status)); + + if (xfer->status == status) + return; + + xfer->status = status; + + if(xfer->type == PURPLE_XFER_SEND) { + switch(status) { + case PURPLE_XFER_STATUS_ACCEPTED: + purple_signal_emit(purple_xfers_get_handle(), "file-send-accept", xfer); + break; + case PURPLE_XFER_STATUS_STARTED: + purple_signal_emit(purple_xfers_get_handle(), "file-send-start", xfer); + break; + case PURPLE_XFER_STATUS_DONE: + purple_signal_emit(purple_xfers_get_handle(), "file-send-complete", xfer); + break; + case PURPLE_XFER_STATUS_CANCEL_LOCAL: + case PURPLE_XFER_STATUS_CANCEL_REMOTE: + purple_signal_emit(purple_xfers_get_handle(), "file-send-cancel", xfer); + break; + default: + break; + } + } else if(xfer->type == PURPLE_XFER_RECEIVE) { + switch(status) { + case PURPLE_XFER_STATUS_ACCEPTED: + purple_signal_emit(purple_xfers_get_handle(), "file-recv-accept", xfer); + break; + case PURPLE_XFER_STATUS_STARTED: + purple_signal_emit(purple_xfers_get_handle(), "file-recv-start", xfer); + break; + case PURPLE_XFER_STATUS_DONE: + purple_signal_emit(purple_xfers_get_handle(), "file-recv-complete", xfer); + break; + case PURPLE_XFER_STATUS_CANCEL_LOCAL: + case PURPLE_XFER_STATUS_CANCEL_REMOTE: + purple_signal_emit(purple_xfers_get_handle(), "file-recv-cancel", xfer); + break; + default: + break; + } + } +} + +static void +purple_xfer_conversation_write_internal(PurpleXfer *xfer, + const char *message, gboolean is_error, gboolean print_thumbnail) +{ + PurpleIMConversation *im = NULL; + PurpleMessageFlags flags = PURPLE_MESSAGE_SYSTEM; + char *escaped; + gconstpointer thumbnail_data; + gsize size; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(message != NULL); + + thumbnail_data = purple_xfer_get_thumbnail(xfer, &size); + + im = purple_conversations_find_im_with_account(xfer->who, + purple_xfer_get_account(xfer)); + + if (im == NULL) + return; + + escaped = g_markup_escape_text(message, -1); + + if (is_error) + flags |= PURPLE_MESSAGE_ERROR; + + if (print_thumbnail && thumbnail_data) { + gchar *message_with_img; + gpointer data = g_memdup(thumbnail_data, size); + int id = purple_imgstore_new_with_id(data, size, NULL); + + message_with_img = + g_strdup_printf("<img src='" PURPLE_STORED_IMAGE_PROTOCOL "%d'> %s", + id, escaped); + purple_conversation_write(PURPLE_CONVERSATION(im), NULL, + message_with_img, flags, time(NULL)); + purple_imgstore_unref_by_id(id); + g_free(message_with_img); + } else { + purple_conversation_write(PURPLE_CONVERSATION(im), NULL, escaped, flags, + time(NULL)); + } + g_free(escaped); +} + +void +purple_xfer_conversation_write(PurpleXfer *xfer, const gchar *message, + gboolean is_error) +{ + purple_xfer_conversation_write_internal(xfer, message, is_error, FALSE); +} + +/* maybe this one should be exported publically? */ +static void +purple_xfer_conversation_write_with_thumbnail(PurpleXfer *xfer, + const gchar *message) +{ + purple_xfer_conversation_write_internal(xfer, message, FALSE, TRUE); +} + + +static void purple_xfer_show_file_error(PurpleXfer *xfer, const char *filename) +{ + int err = errno; + gchar *msg = NULL, *utf8; + PurpleXferType xfer_type = purple_xfer_get_type(xfer); + PurpleAccount *account = purple_xfer_get_account(xfer); + + utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + switch(xfer_type) { + case PURPLE_XFER_SEND: + msg = g_strdup_printf(_("Error reading %s: \n%s.\n"), + utf8, g_strerror(err)); + break; + case PURPLE_XFER_RECEIVE: + msg = g_strdup_printf(_("Error writing %s: \n%s.\n"), + utf8, g_strerror(err)); + break; + default: + msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"), + utf8, g_strerror(err)); + break; + } + g_free(utf8); + + purple_xfer_conversation_write(xfer, msg, TRUE); + purple_xfer_error(xfer_type, account, xfer->who, msg); + g_free(msg); +} + +static void +purple_xfer_choose_file_ok_cb(void *user_data, const char *filename) +{ + PurpleXfer *xfer; + PurpleXferType type; + GStatBuf st; + gchar *dir; + + xfer = (PurpleXfer *)user_data; + type = purple_xfer_get_type(xfer); + + if (g_stat(filename, &st) != 0) { + /* File not found. */ + if (type == PURPLE_XFER_RECEIVE) { +#ifndef _WIN32 + int mode = W_OK; +#else + int mode = F_OK; +#endif + dir = g_path_get_dirname(filename); + + if (g_access(dir, mode) == 0) { + purple_xfer_request_accepted(xfer, filename); + } else { + purple_xfer_ref(xfer); + purple_notify_message( + NULL, PURPLE_NOTIFY_MSG_ERROR, NULL, + _("Directory is not writable."), NULL, + (PurpleNotifyCloseCallback)purple_xfer_choose_file, xfer); + } + + g_free(dir); + } + else { + purple_xfer_show_file_error(xfer, filename); + purple_xfer_cancel_local(xfer); + } + } + else if ((type == PURPLE_XFER_SEND) && (st.st_size == 0)) { + + purple_notify_error(NULL, NULL, + _("Cannot send a file of 0 bytes."), NULL); + + purple_xfer_cancel_local(xfer); + } + else if ((type == PURPLE_XFER_SEND) && S_ISDIR(st.st_mode)) { + /* + * XXX - Sending a directory should be valid for some protocols. + */ + purple_notify_error(NULL, NULL, + _("Cannot send a directory."), NULL); + + purple_xfer_cancel_local(xfer); + } + else if ((type == PURPLE_XFER_RECEIVE) && S_ISDIR(st.st_mode)) { + char *msg, *utf8; + utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + msg = g_strdup_printf( + _("%s is not a regular file. Cowardly refusing to overwrite it.\n"), utf8); + g_free(utf8); + purple_notify_error(NULL, NULL, msg, NULL); + g_free(msg); + purple_xfer_request_denied(xfer); + } + else if (type == PURPLE_XFER_SEND) { +#ifndef _WIN32 + int mode = R_OK; +#else + int mode = F_OK; +#endif + + if (g_access(filename, mode) == 0) { + purple_xfer_request_accepted(xfer, filename); + } else { + purple_xfer_ref(xfer); + purple_notify_message( + NULL, PURPLE_NOTIFY_MSG_ERROR, NULL, + _("File is not readable."), NULL, + (PurpleNotifyCloseCallback)purple_xfer_choose_file, xfer); + } + } + else { + purple_xfer_request_accepted(xfer, filename); + } + + purple_xfer_unref(xfer); +} + +static void +purple_xfer_choose_file_cancel_cb(void *user_data, const char *filename) +{ + PurpleXfer *xfer = (PurpleXfer *)user_data; + + purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL); + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) + purple_xfer_cancel_local(xfer); + else + purple_xfer_request_denied(xfer); + purple_xfer_unref(xfer); +} + +static int +purple_xfer_choose_file(PurpleXfer *xfer) +{ + purple_request_file(xfer, NULL, purple_xfer_get_filename(xfer), + (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE), + G_CALLBACK(purple_xfer_choose_file_ok_cb), + G_CALLBACK(purple_xfer_choose_file_cancel_cb), + purple_xfer_get_account(xfer), xfer->who, NULL, + xfer); + + return 0; +} + +static int +cancel_recv_cb(PurpleXfer *xfer) +{ + purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL); + purple_xfer_request_denied(xfer); + purple_xfer_unref(xfer); + + return 0; +} + +static void +purple_xfer_ask_recv(PurpleXfer *xfer) +{ + char *buf, *size_buf; + goffset size; + gconstpointer thumb; + gsize thumb_size; + + /* If we have already accepted the request, ask the destination file + name directly */ + if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_ACCEPTED) { + PurpleBuddy *buddy = purple_blist_find_buddy(xfer->account, xfer->who); + + if (purple_xfer_get_filename(xfer) != NULL) + { + size = purple_xfer_get_size(xfer); + size_buf = purple_str_size_to_units(size); + buf = g_strdup_printf(_("%s wants to send you %s (%s)"), + buddy ? purple_buddy_get_alias(buddy) : xfer->who, + purple_xfer_get_filename(xfer), size_buf); + g_free(size_buf); + } + else + { + buf = g_strdup_printf(_("%s wants to send you a file"), + buddy ? purple_buddy_get_alias(buddy) : xfer->who); + } + + if (xfer->message != NULL) + serv_got_im(purple_account_get_connection(xfer->account), + xfer->who, xfer->message, 0, time(NULL)); + + if ((thumb = purple_xfer_get_thumbnail(xfer, &thumb_size))) { + purple_request_accept_cancel_with_icon(xfer, NULL, buf, NULL, + PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL, + thumb, thumb_size, xfer, + G_CALLBACK(purple_xfer_choose_file), + G_CALLBACK(cancel_recv_cb)); + } else { + purple_request_accept_cancel(xfer, NULL, buf, NULL, + PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL, + xfer, G_CALLBACK(purple_xfer_choose_file), + G_CALLBACK(cancel_recv_cb)); + } + + g_free(buf); + } else + purple_xfer_choose_file(xfer); +} + +static int +ask_accept_ok(PurpleXfer *xfer) +{ + purple_xfer_request_accepted(xfer, NULL); + + return 0; +} + +static int +ask_accept_cancel(PurpleXfer *xfer) +{ + purple_xfer_request_denied(xfer); + purple_xfer_unref(xfer); + + return 0; +} + +static void +purple_xfer_ask_accept(PurpleXfer *xfer) +{ + char *buf, *buf2 = NULL; + PurpleBuddy *buddy = purple_blist_find_buddy(xfer->account, xfer->who); + + buf = g_strdup_printf(_("Accept file transfer request from %s?"), + buddy ? purple_buddy_get_alias(buddy) : xfer->who); + if (purple_xfer_get_remote_ip(xfer) && + purple_xfer_get_remote_port(xfer)) + buf2 = g_strdup_printf(_("A file is available for download from:\n" + "Remote host: %s\nRemote port: %d"), + purple_xfer_get_remote_ip(xfer), + purple_xfer_get_remote_port(xfer)); + purple_request_accept_cancel(xfer, NULL, buf, buf2, + PURPLE_DEFAULT_ACTION_NONE, + xfer->account, xfer->who, NULL, + xfer, + G_CALLBACK(ask_accept_ok), + G_CALLBACK(ask_accept_cancel)); + g_free(buf); + g_free(buf2); +} + +void +purple_xfer_request(PurpleXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->ops.init != NULL); + + purple_xfer_ref(xfer); + + if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) + { + purple_signal_emit(purple_xfers_get_handle(), "file-recv-request", xfer); + if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) + { + /* The file-transfer was cancelled by a plugin */ + purple_xfer_cancel_local(xfer); + } + else if (purple_xfer_get_filename(xfer) || + purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_ACCEPTED) + { + gchar* message = NULL; + PurpleBuddy *buddy = purple_blist_find_buddy(xfer->account, xfer->who); + + message = g_strdup_printf(_("%s is offering to send file %s"), + buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer)); + purple_xfer_conversation_write_with_thumbnail(xfer, message); + g_free(message); + + /* Ask for a filename to save to if it's not already given by a plugin */ + if (xfer->local_filename == NULL) + purple_xfer_ask_recv(xfer); + } + else + { + purple_xfer_ask_accept(xfer); + } + } + else + { + purple_xfer_choose_file(xfer); + } +} + +void +purple_xfer_request_accepted(PurpleXfer *xfer, const char *filename) +{ + PurpleXferType type; + GStatBuf st; + char *msg, *utf8, *base; + PurpleAccount *account; + PurpleBuddy *buddy; + + if (xfer == NULL) + return; + + type = purple_xfer_get_type(xfer); + account = purple_xfer_get_account(xfer); + + purple_debug_misc("xfer", "request accepted for %p\n", xfer); + + if (!filename && type == PURPLE_XFER_RECEIVE) { + xfer->status = PURPLE_XFER_STATUS_ACCEPTED; + xfer->ops.init(xfer); + return; + } + + buddy = purple_blist_find_buddy(account, xfer->who); + + if (type == PURPLE_XFER_SEND) { + /* Sending a file */ + /* Check the filename. */ + PurpleXferUiOps *ui_ops; + ui_ops = purple_xfer_get_ui_ops(xfer); + +#ifdef _WIN32 + if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\")) +#else + if (g_strrstr(filename, "../")) +#endif + { + utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + + msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8); + purple_xfer_error(type, account, xfer->who, msg); + g_free(utf8); + g_free(msg); + + purple_xfer_unref(xfer); + return; + } + + if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) { + if (g_stat(filename, &st) == -1) { + purple_xfer_show_file_error(xfer, filename); + purple_xfer_unref(xfer); + return; + } + + purple_xfer_set_local_filename(xfer, filename); + purple_xfer_set_size(xfer, st.st_size); + } else { + purple_xfer_set_local_filename(xfer, filename); + } + + base = g_path_get_basename(filename); + utf8 = g_filename_to_utf8(base, -1, NULL, NULL, NULL); + g_free(base); + purple_xfer_set_filename(xfer, utf8); + + msg = g_strdup_printf(_("Offering to send %s to %s"), + utf8, buddy ? purple_buddy_get_alias(buddy) : xfer->who); + g_free(utf8); + purple_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + } + else { + /* Receiving a file */ + xfer->status = PURPLE_XFER_STATUS_ACCEPTED; + purple_xfer_set_local_filename(xfer, filename); + + msg = g_strdup_printf(_("Starting transfer of %s from %s"), + xfer->filename, buddy ? purple_buddy_get_alias(buddy) : xfer->who); + purple_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + } + + purple_xfer_add(xfer); + xfer->ops.init(xfer); + +} + +void +purple_xfer_request_denied(PurpleXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + purple_debug_misc("xfer", "xfer %p denied\n", xfer); + + if (xfer->ops.request_denied != NULL) + xfer->ops.request_denied(xfer); + + purple_xfer_unref(xfer); +} + +int purple_xfer_get_fd(PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->fd; +} + +int purple_xfer_get_watcher(PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->watcher; +} + +PurpleXferType +purple_xfer_get_type(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, PURPLE_XFER_UNKNOWN); + + return xfer->type; +} + +PurpleAccount * +purple_xfer_get_account(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->account; +} + +const char * +purple_xfer_get_remote_user(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + return xfer->who; +} + +PurpleXferStatus +purple_xfer_get_status(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, PURPLE_XFER_STATUS_UNKNOWN); + + return xfer->status; +} + +gboolean +purple_xfer_is_cancelled(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, TRUE); + + if ((purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) || + (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_REMOTE)) + return TRUE; + else + return FALSE; +} + +gboolean +purple_xfer_is_completed(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, TRUE); + + return (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_DONE); +} + +const char * +purple_xfer_get_filename(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->filename; +} + +const char * +purple_xfer_get_local_filename(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->local_filename; +} + +goffset +purple_xfer_get_bytes_sent(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->bytes_sent; +} + +goffset +purple_xfer_get_bytes_remaining(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->bytes_remaining; +} + +goffset +purple_xfer_get_size(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->size; +} + +double +purple_xfer_get_progress(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0.0); + + if (purple_xfer_get_size(xfer) == 0) + return 0.0; + + return ((double)purple_xfer_get_bytes_sent(xfer) / + (double)purple_xfer_get_size(xfer)); +} + +unsigned int +purple_xfer_get_local_port(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, -1); + + return xfer->local_port; +} + +const char * +purple_xfer_get_remote_ip(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->remote_ip; +} + +unsigned int +purple_xfer_get_remote_port(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, -1); + + return xfer->remote_port; +} + +time_t +purple_xfer_get_start_time(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->start_time; +} + +time_t +purple_xfer_get_end_time(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->end_time; +} + +void purple_xfer_set_fd(PurpleXfer *xfer, int fd) +{ + g_return_if_fail(xfer != NULL); + + xfer->fd = fd; +} + +void purple_xfer_set_watcher(PurpleXfer *xfer, int watcher) +{ + g_return_if_fail(xfer != NULL); + + xfer->watcher = watcher; +} + +void +purple_xfer_set_completed(PurpleXfer *xfer, gboolean completed) +{ + PurpleXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + if (completed == TRUE) { + char *msg = NULL; + PurpleIMConversation *im; + + purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_DONE); + + if (purple_xfer_get_filename(xfer) != NULL) + { + char *filename = g_markup_escape_text(purple_xfer_get_filename(xfer), -1); + if (purple_xfer_get_local_filename(xfer) + && purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) + { + char *local = g_markup_escape_text(purple_xfer_get_local_filename(xfer), -1); + msg = g_strdup_printf(_("Transfer of file <A HREF=\"file://%s\">%s</A> complete"), + local, filename); + g_free(local); + } + else + msg = g_strdup_printf(_("Transfer of file %s complete"), + filename); + g_free(filename); + } + else + msg = g_strdup(_("File transfer complete")); + + im = purple_conversations_find_im_with_account(xfer->who, + purple_xfer_get_account(xfer)); + + if (im != NULL) + purple_conversation_write(PURPLE_CONVERSATION(im), NULL, msg, + PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_free(msg); + } + + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer)); +} + +void +purple_xfer_set_message(PurpleXfer *xfer, const char *message) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->message); + xfer->message = g_strdup(message); +} + +void +purple_xfer_set_filename(PurpleXfer *xfer, const char *filename) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->filename); + xfer->filename = g_strdup(filename); +} + +void +purple_xfer_set_local_filename(PurpleXfer *xfer, const char *filename) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->local_filename); + xfer->local_filename = g_strdup(filename); +} + +void +purple_xfer_set_size(PurpleXfer *xfer, goffset size) +{ + g_return_if_fail(xfer != NULL); + + xfer->size = size; + xfer->bytes_remaining = xfer->size - purple_xfer_get_bytes_sent(xfer); +} + +void +purple_xfer_set_local_port(PurpleXfer *xfer, unsigned int local_port) +{ + g_return_if_fail(xfer != NULL); + + xfer->local_port = local_port; +} + +void +purple_xfer_set_bytes_sent(PurpleXfer *xfer, goffset bytes_sent) +{ + g_return_if_fail(xfer != NULL); + + xfer->bytes_sent = bytes_sent; + xfer->bytes_remaining = purple_xfer_get_size(xfer) - bytes_sent; +} + +PurpleXferUiOps * +purple_xfer_get_ui_ops(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->ui_ops; +} + +void +purple_xfer_set_init_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.init = fnc; +} + +void purple_xfer_set_request_denied_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.request_denied = fnc; +} + +void +purple_xfer_set_read_fnc(PurpleXfer *xfer, gssize (*fnc)(guchar **, PurpleXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.read = fnc; +} + +void +purple_xfer_set_write_fnc(PurpleXfer *xfer, + gssize (*fnc)(const guchar *, size_t, PurpleXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.write = fnc; +} + +void +purple_xfer_set_ack_fnc(PurpleXfer *xfer, + void (*fnc)(PurpleXfer *, const guchar *, size_t)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.ack = fnc; +} + +void +purple_xfer_set_start_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.start = fnc; +} + +void +purple_xfer_set_end_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.end = fnc; +} + +void +purple_xfer_set_cancel_send_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.cancel_send = fnc; +} + +void +purple_xfer_set_cancel_recv_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.cancel_recv = fnc; +} + +static void +purple_xfer_increase_buffer_size(PurpleXfer *xfer) +{ + xfer->current_buffer_size = MIN(xfer->current_buffer_size * 1.5, + FT_MAX_BUFFER_SIZE); +} + +gssize +purple_xfer_read(PurpleXfer *xfer, guchar **buffer) +{ + gssize s, r; + + g_return_val_if_fail(xfer != NULL, 0); + g_return_val_if_fail(buffer != NULL, 0); + + if (purple_xfer_get_size(xfer) == 0) + s = xfer->current_buffer_size; + else + s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size); + + if (xfer->ops.read != NULL) { + r = (xfer->ops.read)(buffer, xfer); + } + else { + *buffer = g_malloc0(s); + + r = read(xfer->fd, *buffer, s); + if (r < 0 && errno == EAGAIN) + r = 0; + else if (r < 0) + r = -1; + else if (r == 0) + r = -1; + } + + if (r >= 0 && (gsize)r == xfer->current_buffer_size) + /* + * We managed to read the entire buffer. This means our this + * network is fast and our buffer is too small, so make it + * bigger. + */ + purple_xfer_increase_buffer_size(xfer); + + return r; +} + +gssize +purple_xfer_write(PurpleXfer *xfer, const guchar *buffer, gsize size) +{ + gssize r, s; + + g_return_val_if_fail(xfer != NULL, 0); + g_return_val_if_fail(buffer != NULL, 0); + g_return_val_if_fail(size != 0, 0); + + s = MIN(purple_xfer_get_bytes_remaining(xfer), size); + + if (xfer->ops.write != NULL) { + r = (xfer->ops.write)(buffer, s, xfer); + } else { + r = write(xfer->fd, buffer, s); + if (r < 0 && errno == EAGAIN) + r = 0; + } + if (r >= 0 && (purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer) && + !purple_xfer_is_completed(xfer)) + purple_xfer_set_completed(xfer, TRUE); + + + return r; +} + +gboolean +purple_xfer_write_file(PurpleXfer *xfer, const guchar *buffer, gsize size) +{ + PurpleXferUiOps *ui_ops; + gsize wc; + gboolean fs_known; + + g_return_val_if_fail(xfer != NULL, FALSE); + g_return_val_if_fail(buffer != NULL, FALSE); + + ui_ops = purple_xfer_get_ui_ops(xfer); + fs_known = (purple_xfer_get_size(xfer) > 0); + + if (fs_known && size > purple_xfer_get_bytes_remaining(xfer)) { + purple_debug_warning("filetransfer", + "Got too much data (truncating at %" G_GOFFSET_FORMAT + ").\n", purple_xfer_get_size(xfer)); + size = purple_xfer_get_bytes_remaining(xfer); + } + + if (ui_ops && ui_ops->ui_write) + wc = ui_ops->ui_write(xfer, buffer, size); + else { + if (xfer->dest_fp == NULL) { + purple_debug_error("filetransfer", + "File is not opened for writing\n"); + purple_xfer_cancel_local(xfer); + return FALSE; + } + wc = fwrite(buffer, 1, size, xfer->dest_fp); + } + + if (wc != size) { + purple_debug_error("filetransfer", + "Unable to write whole buffer.\n"); + purple_xfer_cancel_local(xfer); + return FALSE; + } + + purple_xfer_set_bytes_sent(xfer, purple_xfer_get_bytes_sent(xfer) + + size); + + return TRUE; +} + +gssize +purple_xfer_read_file(PurpleXfer *xfer, guchar *buffer, gsize size) +{ + PurpleXferUiOps *ui_ops; + gssize got_len; + + g_return_val_if_fail(xfer != NULL, FALSE); + g_return_val_if_fail(buffer != NULL, FALSE); + + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (ui_ops && ui_ops->ui_read) { + guchar *buffer_got = NULL; + + got_len = ui_ops->ui_read(xfer, &buffer_got, size); + + if (got_len >= 0 && (gsize)got_len > size) { + g_free(buffer_got); + purple_debug_error("filetransfer", + "Got too much data from UI.\n"); + purple_xfer_cancel_local(xfer); + return -1; + } + + if (got_len > 0) + memcpy(buffer, buffer_got, got_len); + g_free(buffer_got); + } else { + if (xfer->dest_fp == NULL) { + purple_debug_error("filetransfer", + "File is not opened for reading\n"); + purple_xfer_cancel_local(xfer); + return -1; + } + got_len = fread(buffer, 1, size, xfer->dest_fp); + if ((got_len < 0 || (gsize)got_len != size) && + ferror(xfer->dest_fp)) + { + purple_debug_error("filetransfer", + "Unable to read file.\n"); + purple_xfer_cancel_local(xfer); + return -1; + } + } + + if (got_len > 0) { + purple_xfer_set_bytes_sent(xfer, + purple_xfer_get_bytes_sent(xfer) + got_len); + } + + return got_len; +} + +static void +do_transfer(PurpleXfer *xfer) +{ + PurpleXferUiOps *ui_ops; + guchar *buffer = NULL; + gssize r = 0; + + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (xfer->type == PURPLE_XFER_RECEIVE) { + r = purple_xfer_read(xfer, &buffer); + if (r > 0) { + if (!purple_xfer_write_file(xfer, buffer, r)) { + g_free(buffer); + return; + } + + if ((purple_xfer_get_size(xfer) > 0) && + ((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer))) + purple_xfer_set_completed(xfer, TRUE); + } else if(r < 0) { + purple_xfer_cancel_remote(xfer); + g_free(buffer); + return; + } + } else if (xfer->type == PURPLE_XFER_SEND) { + gssize result = 0; + size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size); + PurpleXferPrivate *priv = g_hash_table_lookup(xfers_data, xfer); + gboolean read = TRUE; + + /* this is so the prpl can keep the connection open + if it needs to for some odd reason. */ + if (s == 0) { + if (xfer->watcher) { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + } + return; + } + + if (priv->buffer) { + if (priv->buffer->len < s) { + s -= priv->buffer->len; + read = TRUE; + } else { + read = FALSE; + } + } + + if (read) { + buffer = g_new(guchar, s); + result = purple_xfer_read_file(xfer, buffer, s); + if (result == 0) { + /* + * The UI claimed it was ready, but didn't have any data for + * us... It will call purple_xfer_ui_ready when ready, which + * sets back up this watcher. + */ + if (xfer->watcher != 0) { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + } + + /* Need to indicate the prpl is still ready... */ + priv->ready |= PURPLE_XFER_READY_PRPL; + + g_return_if_reached(); + } + if (result < 0) + return; + } + + if (priv->buffer) { + g_byte_array_append(priv->buffer, buffer, result); + g_free(buffer); + buffer = priv->buffer->data; + result = priv->buffer->len; + } + + r = purple_xfer_write(xfer, buffer, result); + + if (r == -1) { + purple_xfer_cancel_remote(xfer); + if (!priv->buffer) + /* We don't free buffer if priv->buffer is set, because in + that case buffer doesn't belong to us. */ + g_free(buffer); + return; + } else if (r == result) { + /* + * We managed to write the entire buffer. This means our + * network is fast and our buffer is too small, so make it + * bigger. + */ + purple_xfer_increase_buffer_size(xfer); + } else { + if (ui_ops && ui_ops->data_not_sent) + ui_ops->data_not_sent(xfer, buffer + r, result - r); + } + + if (priv->buffer) { + /* + * Remove what we wrote + * If we wrote the whole buffer the byte array will be empty + * Otherwise we'll keep what wasn't sent for next time. + */ + buffer = NULL; + g_byte_array_remove_range(priv->buffer, 0, r); + } + } + + if (r > 0) { + if (purple_xfer_get_size(xfer) > 0) + xfer->bytes_remaining -= r; + + xfer->bytes_sent += r; + + if (xfer->ops.ack != NULL) + xfer->ops.ack(xfer, buffer, r); + + g_free(buffer); + + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, + purple_xfer_get_progress(xfer)); + } + + if (purple_xfer_is_completed(xfer)) + purple_xfer_end(xfer); +} + +static void +transfer_cb(gpointer data, gint source, PurpleInputCondition condition) +{ + PurpleXfer *xfer = data; + + if (xfer->dest_fp == NULL) { + /* The UI is moderating its side manually */ + PurpleXferPrivate *priv = g_hash_table_lookup(xfers_data, xfer); + if (0 == (priv->ready & PURPLE_XFER_READY_UI)) { + priv->ready |= PURPLE_XFER_READY_PRPL; + + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + + purple_debug_misc("xfer", "prpl is ready on ft %p, waiting for UI\n", xfer); + return; + } + + priv->ready = PURPLE_XFER_READY_NONE; + } + + do_transfer(xfer); +} + +static void +begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond) +{ + PurpleXferType type = purple_xfer_get_type(xfer); + PurpleXferUiOps *ui_ops = purple_xfer_get_ui_ops(xfer); + + if (xfer->start_time != 0) { + purple_debug_error("xfer", "Transfer is being started multiple times\n"); + g_return_if_reached(); + } + + if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) { + xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer), + type == PURPLE_XFER_RECEIVE ? "wb" : "rb"); + + if (xfer->dest_fp == NULL) { + purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer)); + purple_xfer_cancel_local(xfer); + return; + } + + fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET); + } + + if (xfer->fd != -1) + xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer); + + xfer->start_time = time(NULL); + + if (xfer->ops.start != NULL) + xfer->ops.start(xfer); +} + +static void +connect_cb(gpointer data, gint source, const gchar *error_message) +{ + PurpleXfer *xfer = (PurpleXfer *)data; + + if (source < 0) { + purple_xfer_cancel_local(xfer); + return; + } + + xfer->fd = source; + + begin_transfer(xfer, PURPLE_INPUT_READ); +} + +void +purple_xfer_ui_ready(PurpleXfer *xfer) +{ + PurpleInputCondition cond; + PurpleXferType type; + PurpleXferPrivate *priv; + + g_return_if_fail(xfer != NULL); + + priv = g_hash_table_lookup(xfers_data, xfer); + priv->ready |= PURPLE_XFER_READY_UI; + + if (0 == (priv->ready & PURPLE_XFER_READY_PRPL)) { + purple_debug_misc("xfer", "UI is ready on ft %p, waiting for prpl\n", xfer); + return; + } + + purple_debug_misc("xfer", "UI (and prpl) ready on ft %p, so proceeding\n", xfer); + + type = purple_xfer_get_type(xfer); + if (type == PURPLE_XFER_SEND) + cond = PURPLE_INPUT_WRITE; + else /* if (type == PURPLE_XFER_RECEIVE) */ + cond = PURPLE_INPUT_READ; + + if (xfer->watcher == 0 && xfer->fd != -1) + xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer); + + priv->ready = PURPLE_XFER_READY_NONE; + + do_transfer(xfer); +} + +void +purple_xfer_prpl_ready(PurpleXfer *xfer) +{ + PurpleXferPrivate *priv; + + g_return_if_fail(xfer != NULL); + + priv = g_hash_table_lookup(xfers_data, xfer); + priv->ready |= PURPLE_XFER_READY_PRPL; + + /* I don't think fwrite/fread are ever *not* ready */ + if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI)) { + purple_debug_misc("xfer", "prpl is ready on ft %p, waiting for UI\n", xfer); + return; + } + + purple_debug_misc("xfer", "Prpl (and UI) ready on ft %p, so proceeding\n", xfer); + + priv->ready = PURPLE_XFER_READY_NONE; + + do_transfer(xfer); +} + +void +purple_xfer_start(PurpleXfer *xfer, int fd, const char *ip, + unsigned int port) +{ + PurpleInputCondition cond; + PurpleXferType type; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(purple_xfer_get_type(xfer) != PURPLE_XFER_UNKNOWN); + + type = purple_xfer_get_type(xfer); + + purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_STARTED); + + if (type == PURPLE_XFER_RECEIVE) { + cond = PURPLE_INPUT_READ; + + if (ip != NULL) { + xfer->remote_ip = g_strdup(ip); + xfer->remote_port = port; + + /* Establish a file descriptor. */ + purple_proxy_connect(NULL, xfer->account, xfer->remote_ip, + xfer->remote_port, connect_cb, xfer); + + return; + } + else { + xfer->fd = fd; + } + } + else { + cond = PURPLE_INPUT_WRITE; + + xfer->fd = fd; + } + + begin_transfer(xfer, cond); +} + +void +purple_xfer_end(PurpleXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + /* See if we are actually trying to cancel this. */ + if (!purple_xfer_is_completed(xfer)) { + purple_xfer_cancel_local(xfer); + return; + } + + xfer->end_time = time(NULL); + if (xfer->ops.end != NULL) + xfer->ops.end(xfer); + + if (xfer->watcher != 0) { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + } + + if (xfer->fd != -1) + close(xfer->fd); + + if (xfer->dest_fp != NULL) { + fclose(xfer->dest_fp); + xfer->dest_fp = NULL; + } + + purple_xfer_unref(xfer); +} + +void +purple_xfer_add(PurpleXfer *xfer) +{ + PurpleXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->add_xfer != NULL) + ui_ops->add_xfer(xfer); +} + +void +purple_xfer_cancel_local(PurpleXfer *xfer) +{ + PurpleXferUiOps *ui_ops; + char *msg = NULL; + + g_return_if_fail(xfer != NULL); + + /* TODO: We definitely want to close any open request dialogs associated + with this transfer. However, in some cases the request dialog might + own a reference on the xfer. This happens at least with the "%s wants + to send you %s" dialog from purple_xfer_ask_recv(). In these cases + the ref count will not be decremented when the request dialog is + closed, so the ref count will never reach 0 and the xfer will never + be freed. This is a memleak and should be fixed. It's not clear what + the correct fix is. Probably requests should have a destroy function + that is called when the request is destroyed. But also, ref counting + xfer objects makes this code REALLY complicated. An alternate fix is + to not ref count and instead just make sure the object still exists + when we try to use it. */ + purple_request_close_with_handle(xfer); + + purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL); + xfer->end_time = time(NULL); + + if (purple_xfer_get_filename(xfer) != NULL) + { + msg = g_strdup_printf(_("You cancelled the transfer of %s"), + purple_xfer_get_filename(xfer)); + } + else + { + msg = g_strdup(_("File transfer cancelled")); + } + purple_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) + { + if (xfer->ops.cancel_send != NULL) + xfer->ops.cancel_send(xfer); + } + else + { + if (xfer->ops.cancel_recv != NULL) + xfer->ops.cancel_recv(xfer); + } + + if (xfer->watcher != 0) { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + } + + if (xfer->fd != -1) + close(xfer->fd); + + if (xfer->dest_fp != NULL) { + fclose(xfer->dest_fp); + xfer->dest_fp = NULL; + } + + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->cancel_local != NULL) + ui_ops->cancel_local(xfer); + + xfer->bytes_remaining = 0; + + purple_xfer_unref(xfer); +} + +void +purple_xfer_cancel_remote(PurpleXfer *xfer) +{ + PurpleXferUiOps *ui_ops; + gchar *msg; + PurpleAccount *account; + PurpleBuddy *buddy; + + g_return_if_fail(xfer != NULL); + + purple_request_close_with_handle(xfer); + purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_REMOTE); + xfer->end_time = time(NULL); + + account = purple_xfer_get_account(xfer); + buddy = purple_blist_find_buddy(account, xfer->who); + + if (purple_xfer_get_filename(xfer) != NULL) + { + msg = g_strdup_printf(_("%s cancelled the transfer of %s"), + buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer)); + } + else + { + msg = g_strdup_printf(_("%s cancelled the file transfer"), + buddy ? purple_buddy_get_alias(buddy) : xfer->who); + } + purple_xfer_conversation_write(xfer, msg, TRUE); + purple_xfer_error(purple_xfer_get_type(xfer), account, xfer->who, msg); + g_free(msg); + + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) + { + if (xfer->ops.cancel_send != NULL) + xfer->ops.cancel_send(xfer); + } + else + { + if (xfer->ops.cancel_recv != NULL) + xfer->ops.cancel_recv(xfer); + } + + if (xfer->watcher != 0) { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + } + + if (xfer->fd != -1) + close(xfer->fd); + + if (xfer->dest_fp != NULL) { + fclose(xfer->dest_fp); + xfer->dest_fp = NULL; + } + + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->cancel_remote != NULL) + ui_ops->cancel_remote(xfer); + + xfer->bytes_remaining = 0; + + purple_xfer_unref(xfer); +} + +void +purple_xfer_error(PurpleXferType type, PurpleAccount *account, const char *who, const char *msg) +{ + char *title; + + g_return_if_fail(msg != NULL); + g_return_if_fail(type != PURPLE_XFER_UNKNOWN); + + if (account) { + PurpleBuddy *buddy; + buddy = purple_blist_find_buddy(account, who); + if (buddy) + who = purple_buddy_get_alias(buddy); + } + + if (type == PURPLE_XFER_SEND) + title = g_strdup_printf(_("File transfer to %s failed."), who); + else + title = g_strdup_printf(_("File transfer from %s failed."), who); + + purple_notify_error(NULL, NULL, title, msg); + + g_free(title); +} + +void +purple_xfer_update_progress(PurpleXfer *xfer) +{ + PurpleXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + ui_ops = purple_xfer_get_ui_ops(xfer); + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer)); +} + +gconstpointer +purple_xfer_get_thumbnail(const PurpleXfer *xfer, gsize *len) +{ + PurpleXferPrivate *priv = g_hash_table_lookup(xfers_data, xfer); + + if (len) + *len = priv->thumbnail_size; + + return priv->thumbnail_data; +} + +const gchar * +purple_xfer_get_thumbnail_mimetype(const PurpleXfer *xfer) +{ + PurpleXferPrivate *priv = g_hash_table_lookup(xfers_data, xfer); + + return priv->thumbnail_mimetype; +} + +void +purple_xfer_set_thumbnail(PurpleXfer *xfer, gconstpointer thumbnail, + gsize size, const gchar *mimetype) +{ + PurpleXferPrivate *priv = g_hash_table_lookup(xfers_data, xfer); + + g_free(priv->thumbnail_data); + g_free(priv->thumbnail_mimetype); + + if (thumbnail && size > 0) { + priv->thumbnail_data = g_memdup(thumbnail, size); + priv->thumbnail_size = size; + priv->thumbnail_mimetype = g_strdup(mimetype); + } else { + priv->thumbnail_data = NULL; + priv->thumbnail_size = 0; + priv->thumbnail_mimetype = NULL; + } +} + +void +purple_xfer_prepare_thumbnail(PurpleXfer *xfer, const gchar *formats) +{ + if (xfer->ui_ops->add_thumbnail) { + xfer->ui_ops->add_thumbnail(xfer, formats); + } +} + +void +purple_xfer_set_protocol_data(PurpleXfer *xfer, gpointer proto_data) +{ + g_return_if_fail(xfer != NULL); + + xfer->proto_data = proto_data; +} + +gpointer +purple_xfer_get_protocol_data(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->proto_data; +} + +void purple_xfer_set_ui_data(PurpleXfer *xfer, gpointer ui_data) +{ + g_return_if_fail(xfer != NULL); + + xfer->ui_data = ui_data; +} + +gpointer purple_xfer_get_ui_data(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->ui_data; +} + +static PurpleXfer * +purple_xfer_copy(PurpleXfer *xfer) +{ + PurpleXfer *xfer_copy; + + g_return_val_if_fail(xfer != NULL, NULL); + + xfer_copy = g_new(PurpleXfer, 1); + *xfer_copy = *xfer; + + return xfer_copy; +} + +GType +purple_xfer_get_g_type(void) +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static("PurpleXfer", + (GBoxedCopyFunc)purple_xfer_copy, + (GBoxedFreeFunc)g_free); + } + + return type; +} + + +/************************************************************************** + * File Transfer Subsystem API + **************************************************************************/ +void * +purple_xfers_get_handle(void) { + static int handle = 0; + + return &handle; +} + +void +purple_xfers_init(void) { + void *handle = purple_xfers_get_handle(); + + xfers_data = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, purple_xfer_priv_data_destroy); + + /* register signals */ + purple_signal_register(handle, "file-recv-accept", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); + purple_signal_register(handle, "file-send-accept", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); + purple_signal_register(handle, "file-recv-start", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); + purple_signal_register(handle, "file-send-start", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); + purple_signal_register(handle, "file-send-cancel", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); + purple_signal_register(handle, "file-recv-cancel", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); + purple_signal_register(handle, "file-send-complete", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); + purple_signal_register(handle, "file-recv-complete", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); + purple_signal_register(handle, "file-recv-request", + purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, + PURPLE_TYPE_XFER); +} + +void +purple_xfers_uninit(void) +{ + void *handle = purple_xfers_get_handle(); + + purple_signals_disconnect_by_handle(handle); + purple_signals_unregister_by_instance(handle); + + g_hash_table_destroy(xfers_data); + xfers_data = NULL; +} + +void +purple_xfers_set_ui_ops(PurpleXferUiOps *ops) { + xfer_ui_ops = ops; +} + +PurpleXferUiOps * +purple_xfers_get_ui_ops(void) { + return xfer_ui_ops; +}