Mercurial > pidgin
view libpurple/ft.c @ 25881:ca01403251f1
explicit merge of 'dabe77d56a2fc7c171caa81bd12d72ce8737a7a8'
and 'b299a165920a9d922e9b8c27b4c879c77b43efe1'
author | Richard Laager <rlaager@wiktel.com> |
---|---|
date | Fri, 02 Jan 2009 21:47:52 +0000 |
parents | fae699fece1f |
children | 568b786c36f9 |
line wrap: on
line source
/** * @file ft.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 "ft.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 static PurpleXferUiOps *xfer_ui_ops = NULL; static GList *xfers; static int purple_xfer_choose_file(PurpleXfer *xfer); GList * purple_xfers_get_all() { return xfers; } PurpleXfer * purple_xfer_new(PurpleAccount *account, PurpleXferType type, const char *who) { PurpleXfer *xfer; PurpleXferUiOps *ui_ops; 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 = purple_xfers_get_ui_ops(); xfer->message = NULL; xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE; xfer->fd = -1; 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); return xfer; } static void purple_xfer_destroy(PurpleXfer *xfer) { PurpleXferUiOps *ui_ops; g_return_if_fail(xfer != NULL); /* 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); PURPLE_DBUS_UNREGISTER_POINTER(xfer); g_free(xfer); xfers = g_list_remove(xfers, xfer); } void purple_xfer_ref(PurpleXfer *xfer) { g_return_if_fail(xfer != NULL); xfer->ref++; } void purple_xfer_unref(PurpleXfer *xfer) { g_return_if_fail(xfer != NULL); g_return_if_fail(xfer->ref > 0); xfer->ref--; if (xfer->ref == 0) purple_xfer_destroy(xfer); } static void purple_xfer_set_status(PurpleXfer *xfer, PurpleXferStatusType status) { g_return_if_fail(xfer != NULL); 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; } } xfer->status = status; } void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is_error) { PurpleConversation *conv = NULL; PurpleMessageFlags flags = PURPLE_MESSAGE_SYSTEM; char *escaped; g_return_if_fail(xfer != NULL); g_return_if_fail(message != NULL); conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, xfer->who, purple_xfer_get_account(xfer)); if (conv == NULL) return; escaped = g_markup_escape_text(message, -1); if (is_error) flags = PURPLE_MESSAGE_ERROR; purple_conversation_write(conv, NULL, escaped, flags, time(NULL)); g_free(escaped); } 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; struct stat st; gchar *dir; xfer = (PurpleXfer *)user_data; if (g_stat(filename, &st) != 0) { /* File not found. */ if (purple_xfer_get_type(xfer) == 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_request_denied(xfer); } } else if ((purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) && (st.st_size == 0)) { purple_notify_error(NULL, NULL, _("Cannot send a file of 0 bytes."), NULL); purple_xfer_request_denied(xfer); } else if ((purple_xfer_get_type(xfer) == 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_request_denied(xfer); } else if ((purple_xfer_get_type(xfer) == 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 { 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); purple_xfer_request_denied(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; size_t 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_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)); 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_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_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(xfer, message, FALSE); 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; struct stat st; char *msg, *utf8; PurpleAccount *account; PurpleBuddy *buddy; if (xfer == NULL) return; type = purple_xfer_get_type(xfer); account = purple_xfer_get_account(xfer); if (!filename && type == PURPLE_XFER_RECEIVE) { xfer->status = PURPLE_XFER_STATUS_ACCEPTED; xfer->ops.init(xfer); return; } buddy = purple_find_buddy(account, xfer->who); if (type == PURPLE_XFER_SEND) { /* Sending a file */ /* Check the filename. */ #ifdef _WIN32 if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\")) #else if (g_strrstr(filename, "../")) #endif { char *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 (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); utf8 = g_filename_to_utf8(g_basename(filename), -1, NULL, NULL, NULL); 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); if (xfer->ops.request_denied != NULL) xfer->ops.request_denied(xfer); purple_xfer_unref(xfer); } 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; } PurpleXferStatusType 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_canceled(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; } size_t purple_xfer_get_bytes_sent(const PurpleXfer *xfer) { g_return_val_if_fail(xfer != NULL, 0); return xfer->bytes_sent; } size_t purple_xfer_get_bytes_remaining(const PurpleXfer *xfer) { g_return_val_if_fail(xfer != NULL, 0); return xfer->bytes_remaining; } size_t 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_completed(PurpleXfer *xfer, gboolean completed) { PurpleXferUiOps *ui_ops; g_return_if_fail(xfer != NULL); if (completed == TRUE) { char *msg = NULL; purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_DONE); if (purple_xfer_get_filename(xfer) != NULL) msg = g_strdup_printf(_("Transfer of file %s complete"), purple_xfer_get_filename(xfer)); else msg = g_strdup_printf(_("File transfer complete")); purple_xfer_conversation_write(xfer, msg, FALSE); 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, size_t 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_bytes_sent(PurpleXfer *xfer, size_t 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 ((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) r = -1; } if (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 ((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer)) purple_xfer_set_completed(xfer, TRUE); } return r; } static void transfer_cb(gpointer data, gint source, PurpleInputCondition condition) { PurpleXferUiOps *ui_ops; PurpleXfer *xfer = (PurpleXfer *)data; guchar *buffer = NULL; gssize r = 0; if (condition & PURPLE_INPUT_READ) { r = purple_xfer_read(xfer, &buffer); if (r > 0) { const size_t wc = fwrite(buffer, 1, r, xfer->dest_fp); if (wc != r) { purple_debug_error("filetransfer", "Unable to write whole buffer.\n"); purple_xfer_cancel_remote(xfer); return; } } else if(r < 0) { purple_xfer_cancel_remote(xfer); return; } } if (condition & PURPLE_INPUT_WRITE) { size_t result; size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size); /* 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; } buffer = g_malloc0(s); result = fread(buffer, 1, s, xfer->dest_fp); if (result != s) { purple_debug_error("filetransfer", "Unable to read whole buffer.\n"); purple_xfer_cancel_remote(xfer); g_free(buffer); return; } /* Write as much as we're allowed to. */ r = purple_xfer_write(xfer, buffer, s); if (r == -1) { purple_xfer_cancel_remote(xfer); g_free(buffer); return; } else if (r < s) { /* We have to seek back in the file now. */ fseek(xfer->dest_fp, r - s, SEEK_CUR); } else { /* * 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); } } 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); 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)); } if (purple_xfer_is_completed(xfer)) purple_xfer_end(xfer); } static void begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond) { PurpleXferType type = purple_xfer_get_type(xfer); 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) 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_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 != 0) 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); 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 canceled the transfer of %s"), purple_xfer_get_filename(xfer)); } else { msg = g_strdup_printf(_("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 != 0) 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_find_buddy(account, xfer->who); if (purple_xfer_get_filename(xfer) != NULL) { msg = g_strdup_printf(_("%s canceled the transfer of %s"), buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer)); } else { msg = g_strdup_printf(_("%s canceled 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 != 0) 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_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)); } /************************************************************************** * 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(); /* register signals */ purple_signal_register(handle, "file-recv-accept", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); purple_signal_register(handle, "file-send-accept", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); purple_signal_register(handle, "file-recv-start", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); purple_signal_register(handle, "file-send-start", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); purple_signal_register(handle, "file-send-cancel", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); purple_signal_register(handle, "file-recv-cancel", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); purple_signal_register(handle, "file-send-complete", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); purple_signal_register(handle, "file-recv-complete", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); purple_signal_register(handle, "file-recv-request", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XFER)); } void purple_xfers_uninit(void) { void *handle = purple_xfers_get_handle(); purple_signals_disconnect_by_handle(handle); purple_signals_unregister_by_instance(handle); } void purple_xfers_set_ui_ops(PurpleXferUiOps *ops) { xfer_ui_ops = ops; } PurpleXferUiOps * purple_xfers_get_ui_ops(void) { return xfer_ui_ops; }