Mercurial > pidgin
diff libgaim/ft.c @ 14192:60b1bc8dbf37
[gaim-migrate @ 16863]
Renamed 'core' to 'libgaim'
committer: Tailor Script <tailor@pidgin.im>
author | Evan Schoenberg <evan.s@dreskin.net> |
---|---|
date | Sat, 19 Aug 2006 01:50:10 +0000 |
parents | |
children | 2f0b4d0de5bb |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libgaim/ft.c Sat Aug 19 01:50:10 2006 +0000 @@ -0,0 +1,1236 @@ +/** + * @file ft.c File Transfer API + * + * gaim + * + * Gaim 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "internal.h" +#include "ft.h" +#include "network.h" +#include "notify.h" +#include "prefs.h" +#include "proxy.h" +#include "request.h" +#include "util.h" + +static GaimXferUiOps *xfer_ui_ops = NULL; + +GaimXfer * +gaim_xfer_new(GaimAccount *account, GaimXferType type, const char *who) +{ + GaimXfer *xfer; + GaimXferUiOps *ui_ops; + + g_return_val_if_fail(type != GAIM_XFER_UNKNOWN, NULL); + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(who != NULL, NULL); + + xfer = g_new0(GaimXfer, 1); + + xfer->ref = 1; + xfer->type = type; + xfer->account = account; + xfer->who = g_strdup(who); + xfer->ui_ops = gaim_xfers_get_ui_ops(); + xfer->message = NULL; + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->new_xfer != NULL) + ui_ops->new_xfer(xfer); + + return xfer; +} + +static void +gaim_xfer_destroy(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + /* Close the file browser, if it's open */ + gaim_request_close_with_handle(xfer); + + if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_STARTED) + gaim_xfer_cancel_local(xfer); + + ui_ops = gaim_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_free(xfer); +} + +void +gaim_xfer_ref(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + xfer->ref++; +} + +void +gaim_xfer_unref(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->ref > 0); + + xfer->ref--; + + if (xfer->ref == 0) + gaim_xfer_destroy(xfer); +} + +static void +gaim_xfer_set_status(GaimXfer *xfer, GaimXferStatusType status) +{ + g_return_if_fail(xfer != NULL); + + if(xfer->type == GAIM_XFER_SEND) { + switch(status) { + case GAIM_XFER_STATUS_ACCEPTED: + gaim_signal_emit(gaim_xfers_get_handle(), "file-send-accept", xfer); + break; + case GAIM_XFER_STATUS_STARTED: + gaim_signal_emit(gaim_xfers_get_handle(), "file-send-start", xfer); + break; + case GAIM_XFER_STATUS_DONE: + gaim_signal_emit(gaim_xfers_get_handle(), "file-send-complete", xfer); + break; + case GAIM_XFER_STATUS_CANCEL_LOCAL: + case GAIM_XFER_STATUS_CANCEL_REMOTE: + gaim_signal_emit(gaim_xfers_get_handle(), "file-send-cancel", xfer); + break; + default: + break; + } + } else if(xfer->type == GAIM_XFER_RECEIVE) { + switch(status) { + case GAIM_XFER_STATUS_ACCEPTED: + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-accept", xfer); + break; + case GAIM_XFER_STATUS_STARTED: + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-start", xfer); + break; + case GAIM_XFER_STATUS_DONE: + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-complete", xfer); + break; + case GAIM_XFER_STATUS_CANCEL_LOCAL: + case GAIM_XFER_STATUS_CANCEL_REMOTE: + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-cancel", xfer); + break; + default: + break; + } + } + + xfer->status = status; +} + +void gaim_xfer_conversation_write(GaimXfer *xfer, char *message, gboolean is_error) +{ + GaimConversation *conv = NULL; + GaimMessageFlags flags = GAIM_MESSAGE_SYSTEM; + char *escaped; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(message != NULL); + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, xfer->who, + gaim_xfer_get_account(xfer)); + + if (conv == NULL) + return; + + escaped = g_markup_escape_text(message, -1); + + if (is_error) + flags = GAIM_MESSAGE_ERROR; + + gaim_conversation_write(conv, NULL, escaped, flags, time(NULL)); + g_free(escaped); +} + +static void gaim_xfer_show_file_error(GaimXfer *xfer, const char *filename) +{ + int err = errno; + gchar *msg = NULL, *utf8; + GaimXferType xfer_type = gaim_xfer_get_type(xfer); + GaimAccount *account = gaim_xfer_get_account(xfer); + + utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + switch(xfer_type) { + case GAIM_XFER_SEND: + msg = g_strdup_printf(_("Error reading %s: \n%s.\n"), + utf8, strerror(err)); + break; + case GAIM_XFER_RECEIVE: + msg = g_strdup_printf(_("Error writing %s: \n%s.\n"), + utf8, strerror(err)); + break; + default: + msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"), + utf8, strerror(err)); + break; + } + g_free(utf8); + + gaim_xfer_conversation_write(xfer, msg, TRUE); + gaim_xfer_error(xfer_type, account, xfer->who, msg); + g_free(msg); +} + +static void +gaim_xfer_choose_file_ok_cb(void *user_data, const char *filename) +{ + GaimXfer *xfer; + struct stat st; + + xfer = (GaimXfer *)user_data; + + if (g_stat(filename, &st) != 0) { + /* File not found. */ + if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) { + gaim_xfer_request_accepted(xfer, filename); + } + else { + gaim_xfer_show_file_error(xfer, filename); + gaim_xfer_request_denied(xfer); + } + } + else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) && + (st.st_size == 0)) { + + gaim_notify_error(NULL, NULL, + _("Cannot send a file of 0 bytes."), NULL); + + gaim_xfer_request_denied(xfer); + } + else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) && + S_ISDIR(st.st_mode)) { + /* + * XXX - Sending a directory should be valid for some protocols. + */ + gaim_notify_error(NULL, NULL, + _("Cannot send a directory."), NULL); + + gaim_xfer_request_denied(xfer); + } + else if ((gaim_xfer_get_type(xfer) == GAIM_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); + gaim_notify_error(NULL, NULL, msg, NULL); + g_free(msg); + gaim_xfer_request_denied(xfer); + } + else { + gaim_xfer_request_accepted(xfer, filename); + } + + gaim_xfer_unref(xfer); +} + +static void +gaim_xfer_choose_file_cancel_cb(void *user_data, const char *filename) +{ + GaimXfer *xfer = (GaimXfer *)user_data; + + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL); + gaim_xfer_request_denied(xfer); +} + +static int +gaim_xfer_choose_file(GaimXfer *xfer) +{ + gaim_request_file(xfer, NULL, gaim_xfer_get_filename(xfer), + (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE), + G_CALLBACK(gaim_xfer_choose_file_ok_cb), + G_CALLBACK(gaim_xfer_choose_file_cancel_cb), xfer); + + return 0; +} + +static int +cancel_recv_cb(GaimXfer *xfer) +{ + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL); + gaim_xfer_request_denied(xfer); + gaim_xfer_unref(xfer); + + return 0; +} + +static void +gaim_xfer_ask_recv(GaimXfer *xfer) +{ + char *buf, *size_buf; + size_t size; + + /* If we have already accepted the request, ask the destination file + name directly */ + if (gaim_xfer_get_status(xfer) != GAIM_XFER_STATUS_ACCEPTED) { + GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who); + + if (gaim_xfer_get_filename(xfer) != NULL) + { + size = gaim_xfer_get_size(xfer); + size_buf = gaim_str_size_to_units(size); + buf = g_strdup_printf(_("%s wants to send you %s (%s)"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who, + gaim_xfer_get_filename(xfer), size_buf); + g_free(size_buf); + } + else + { + buf = g_strdup_printf(_("%s wants to send you a file"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + } + + if (xfer->message != NULL) + serv_got_im(gaim_account_get_connection(xfer->account), + xfer->who, xfer->message, 0, time(NULL)); + + gaim_request_accept_cancel(xfer, NULL, buf, NULL, + GAIM_DEFAULT_ACTION_NONE, xfer, + G_CALLBACK(gaim_xfer_choose_file), + G_CALLBACK(cancel_recv_cb)); + + g_free(buf); + } else + gaim_xfer_choose_file(xfer); +} + +static int +ask_accept_ok(GaimXfer *xfer) +{ + gaim_xfer_request_accepted(xfer, NULL); + + return 0; +} + +static int +ask_accept_cancel(GaimXfer *xfer) +{ + gaim_xfer_request_denied(xfer); + gaim_xfer_unref(xfer); + + return 0; +} + +static void +gaim_xfer_ask_accept(GaimXfer *xfer) +{ + char *buf, *buf2 = NULL; + GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who); + + buf = g_strdup_printf(_("Accept file transfer request from %s?"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + if (gaim_xfer_get_remote_ip(xfer) && + gaim_xfer_get_remote_port(xfer)) + buf2 = g_strdup_printf(_("A file is available for download from:\n" + "Remote host: %s\nRemote port: %d"), + gaim_xfer_get_remote_ip(xfer), + gaim_xfer_get_remote_port(xfer)); + gaim_request_accept_cancel(xfer, NULL, buf, buf2, + GAIM_DEFAULT_ACTION_NONE, xfer, + G_CALLBACK(ask_accept_ok), + G_CALLBACK(ask_accept_cancel)); + g_free(buf); + g_free(buf2); +} + +void +gaim_xfer_request(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->ops.init != NULL); + + gaim_xfer_ref(xfer); + + if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) + { + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-request", xfer); + if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) + { + /* The file-transfer was cancelled by a plugin */ + gaim_xfer_cancel_local(xfer); + } + else if (gaim_xfer_get_filename(xfer) || + gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_ACCEPTED) + { + gchar* message = NULL; + GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who); + message = g_strdup_printf(_("%s is offering to send file %s"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who, gaim_xfer_get_filename(xfer)); + gaim_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) + gaim_xfer_ask_recv(xfer); + } + else + { + gaim_xfer_ask_accept(xfer); + } + } + else + { + gaim_xfer_choose_file(xfer); + } +} + +void +gaim_xfer_request_accepted(GaimXfer *xfer, const char *filename) +{ + GaimXferType type; + struct stat st; + char *msg, *utf8; + GaimAccount *account; + GaimBuddy *buddy; + + if (xfer == NULL) + return; + + type = gaim_xfer_get_type(xfer); + account = gaim_xfer_get_account(xfer); + + if (!filename && type == GAIM_XFER_RECEIVE) { + xfer->status = GAIM_XFER_STATUS_ACCEPTED; + xfer->ops.init(xfer); + return; + } + + buddy = gaim_find_buddy(account, xfer->who); + + if (type == GAIM_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); + gaim_xfer_error(type, account, xfer->who, msg); + g_free(utf8); + g_free(msg); + + gaim_xfer_unref(xfer); + return; + } + + if (g_stat(filename, &st) == -1) { + gaim_xfer_show_file_error(xfer, filename); + gaim_xfer_unref(xfer); + return; + } + + gaim_xfer_set_local_filename(xfer, filename); + gaim_xfer_set_size(xfer, st.st_size); + + utf8 = g_filename_to_utf8(g_basename(filename), -1, NULL, NULL, NULL); + gaim_xfer_set_filename(xfer, utf8); + + msg = g_strdup_printf(_("Offering to send %s to %s"), + utf8, buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + g_free(utf8); + + gaim_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + } + else { + /* Receiving a file */ + xfer->status = GAIM_XFER_STATUS_ACCEPTED; + gaim_xfer_set_local_filename(xfer, filename); + + msg = g_strdup_printf(_("Starting transfer of %s from %s"), + xfer->filename, buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + gaim_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + } + + gaim_xfer_add(xfer); + xfer->ops.init(xfer); + +} + +void +gaim_xfer_request_denied(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + if (xfer->ops.request_denied != NULL) + xfer->ops.request_denied(xfer); + + gaim_xfer_unref(xfer); +} + +GaimXferType +gaim_xfer_get_type(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, GAIM_XFER_UNKNOWN); + + return xfer->type; +} + +GaimAccount * +gaim_xfer_get_account(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->account; +} + +GaimXferStatusType +gaim_xfer_get_status(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, GAIM_XFER_STATUS_UNKNOWN); + + return xfer->status; +} + +gboolean +gaim_xfer_is_canceled(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, TRUE); + + if ((gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) || + (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_REMOTE)) + return TRUE; + else + return FALSE; +} + +gboolean +gaim_xfer_is_completed(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, TRUE); + + return (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_DONE); +} + +const char * +gaim_xfer_get_filename(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->filename; +} + +const char * +gaim_xfer_get_local_filename(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->local_filename; +} + +size_t +gaim_xfer_get_bytes_sent(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->bytes_sent; +} + +size_t +gaim_xfer_get_bytes_remaining(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->bytes_remaining; +} + +size_t +gaim_xfer_get_size(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->size; +} + +double +gaim_xfer_get_progress(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0.0); + + if (gaim_xfer_get_size(xfer) == 0) + return 0.0; + + return ((double)gaim_xfer_get_bytes_sent(xfer) / + (double)gaim_xfer_get_size(xfer)); +} + +unsigned int +gaim_xfer_get_local_port(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, -1); + + return xfer->local_port; +} + +const char * +gaim_xfer_get_remote_ip(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->remote_ip; +} + +unsigned int +gaim_xfer_get_remote_port(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, -1); + + return xfer->remote_port; +} + +void +gaim_xfer_set_completed(GaimXfer *xfer, gboolean completed) +{ + GaimXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + if (completed == TRUE) { + char *msg = NULL; + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_DONE); + + if (gaim_xfer_get_filename(xfer) != NULL) + msg = g_strdup_printf(_("Transfer of file %s complete"), + gaim_xfer_get_filename(xfer)); + else + msg = g_strdup_printf(_("File transfer complete")); + gaim_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + } + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, gaim_xfer_get_progress(xfer)); +} + +void +gaim_xfer_set_message(GaimXfer *xfer, const char *message) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->message); + xfer->message = g_strdup(message); +} + +void +gaim_xfer_set_filename(GaimXfer *xfer, const char *filename) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->filename); + xfer->filename = g_strdup(filename); +} + +void +gaim_xfer_set_local_filename(GaimXfer *xfer, const char *filename) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->local_filename); + xfer->local_filename = g_strdup(filename); +} + +void +gaim_xfer_set_size(GaimXfer *xfer, size_t size) +{ + g_return_if_fail(xfer != NULL); + + if (xfer->size == 0) + xfer->bytes_remaining = size - xfer->bytes_sent; + + xfer->size = size; +} + +GaimXferUiOps * +gaim_xfer_get_ui_ops(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->ui_ops; +} + +void +gaim_xfer_set_init_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.init = fnc; +} + +void gaim_xfer_set_request_denied_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.request_denied = fnc; +} + +void +gaim_xfer_set_read_fnc(GaimXfer *xfer, gssize (*fnc)(guchar **, GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.read = fnc; +} + +void +gaim_xfer_set_write_fnc(GaimXfer *xfer, + gssize (*fnc)(const guchar *, size_t, GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.write = fnc; +} + +void +gaim_xfer_set_ack_fnc(GaimXfer *xfer, + void (*fnc)(GaimXfer *, const guchar *, size_t)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.ack = fnc; +} + +void +gaim_xfer_set_start_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.start = fnc; +} + +void +gaim_xfer_set_end_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.end = fnc; +} + +void +gaim_xfer_set_cancel_send_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.cancel_send = fnc; +} + +void +gaim_xfer_set_cancel_recv_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.cancel_recv = fnc; +} + +gssize +gaim_xfer_read(GaimXfer *xfer, guchar **buffer) +{ + gssize s, r; + + g_return_val_if_fail(xfer != NULL, 0); + g_return_val_if_fail(buffer != NULL, 0); + + if (gaim_xfer_get_size(xfer) == 0) + s = 4096; + else + s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096); + + 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 ((gaim_xfer_get_size(xfer) > 0) && + ((gaim_xfer_get_bytes_sent(xfer)+r) >= gaim_xfer_get_size(xfer))) + gaim_xfer_set_completed(xfer, TRUE); + else if (r == 0) + r = -1; + } + + return r; +} + +gssize +gaim_xfer_write(GaimXfer *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(gaim_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 ((gaim_xfer_get_bytes_sent(xfer)+r) >= gaim_xfer_get_size(xfer)) + gaim_xfer_set_completed(xfer, TRUE); + } + + return r; +} + +static void +transfer_cb(gpointer data, gint source, GaimInputCondition condition) +{ + GaimXferUiOps *ui_ops; + GaimXfer *xfer = (GaimXfer *)data; + guchar *buffer = NULL; + gssize r = 0; + + if (condition & GAIM_INPUT_READ) { + r = gaim_xfer_read(xfer, &buffer); + if (r > 0) { + fwrite(buffer, 1, r, xfer->dest_fp); + } else if(r <= 0) { + gaim_xfer_cancel_remote(xfer); + return; + } + } + + if (condition & GAIM_INPUT_WRITE) { + size_t s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096); + + /* this is so the prpl can keep the connection open + if it needs to for some odd reason. */ + if (s == 0) { + if (xfer->watcher) { + gaim_input_remove(xfer->watcher); + xfer->watcher = 0; + } + return; + } + + buffer = g_malloc0(s); + + fread(buffer, 1, s, xfer->dest_fp); + + /* Write as much as we're allowed to. */ + r = gaim_xfer_write(xfer, buffer, s); + + if (r == -1) { + gaim_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); + } + } + + if (r > 0) { + if (gaim_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 = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, + gaim_xfer_get_progress(xfer)); + } + + if (gaim_xfer_is_completed(xfer)) + gaim_xfer_end(xfer); +} + +static void +begin_transfer(GaimXfer *xfer, GaimInputCondition cond) +{ + GaimXferType type = gaim_xfer_get_type(xfer); + + xfer->dest_fp = g_fopen(gaim_xfer_get_local_filename(xfer), + type == GAIM_XFER_RECEIVE ? "wb" : "rb"); + + if (xfer->dest_fp == NULL) { + gaim_xfer_show_file_error(xfer, gaim_xfer_get_local_filename(xfer)); + gaim_xfer_cancel_local(xfer); + return; + } + + xfer->watcher = gaim_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) +{ + GaimXfer *xfer = (GaimXfer *)data; + + xfer->fd = source; + + begin_transfer(xfer, GAIM_INPUT_READ); +} + +void +gaim_xfer_start(GaimXfer *xfer, int fd, const char *ip, + unsigned int port) +{ + GaimInputCondition cond; + GaimXferType type; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(gaim_xfer_get_type(xfer) != GAIM_XFER_UNKNOWN); + + type = gaim_xfer_get_type(xfer); + + xfer->bytes_remaining = gaim_xfer_get_size(xfer); + xfer->bytes_sent = 0; + + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_STARTED); + + if (type == GAIM_XFER_RECEIVE) { + cond = GAIM_INPUT_READ; + + if (ip != NULL) { + xfer->remote_ip = g_strdup(ip); + xfer->remote_port = port; + + /* Establish a file descriptor. */ + gaim_proxy_connect(xfer->account, xfer->remote_ip, + xfer->remote_port, connect_cb, xfer); + + return; + } + else { + xfer->fd = fd; + } + } + else { + cond = GAIM_INPUT_WRITE; + + xfer->fd = fd; + } + + begin_transfer(xfer, cond); +} + +void +gaim_xfer_end(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + /* See if we are actually trying to cancel this. */ + if (!gaim_xfer_is_completed(xfer)) { + gaim_xfer_cancel_local(xfer); + return; + } + + xfer->end_time = time(NULL); + if (xfer->ops.end != NULL) + xfer->ops.end(xfer); + + if (xfer->watcher != 0) { + gaim_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; + } + + gaim_xfer_unref(xfer); +} + +void +gaim_xfer_add(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->add_xfer != NULL) + ui_ops->add_xfer(xfer); +} + +void +gaim_xfer_cancel_local(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + char *msg = NULL; + + g_return_if_fail(xfer != NULL); + + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL); + xfer->end_time = time(NULL); + + if (gaim_xfer_get_filename(xfer) != NULL) + { + msg = g_strdup_printf(_("You canceled the transfer of %s"), + gaim_xfer_get_filename(xfer)); + } + else + { + msg = g_strdup_printf(_("File transfer cancelled")); + } + gaim_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + + if (gaim_xfer_get_type(xfer) == GAIM_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) { + gaim_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 = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->cancel_local != NULL) + ui_ops->cancel_local(xfer); + + xfer->bytes_remaining = 0; + + gaim_xfer_unref(xfer); +} + +void +gaim_xfer_cancel_remote(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + gchar *msg; + GaimAccount *account; + GaimBuddy *buddy; + + g_return_if_fail(xfer != NULL); + + gaim_request_close_with_handle(xfer); + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_REMOTE); + xfer->end_time = time(NULL); + + account = gaim_xfer_get_account(xfer); + buddy = gaim_find_buddy(account, xfer->who); + + if (gaim_xfer_get_filename(xfer) != NULL) + { + msg = g_strdup_printf(_("%s canceled the transfer of %s"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who, gaim_xfer_get_filename(xfer)); + } + else + { + msg = g_strdup_printf(_("%s canceled the file transfer"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + } + gaim_xfer_conversation_write(xfer, msg, TRUE); + gaim_xfer_error(gaim_xfer_get_type(xfer), account, xfer->who, msg); + g_free(msg); + + if (gaim_xfer_get_type(xfer) == GAIM_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) { + gaim_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 = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->cancel_remote != NULL) + ui_ops->cancel_remote(xfer); + + xfer->bytes_remaining = 0; + + gaim_xfer_unref(xfer); +} + +void +gaim_xfer_error(GaimXferType type, GaimAccount *account, const char *who, const char *msg) +{ + char *title; + + g_return_if_fail(msg != NULL); + g_return_if_fail(type != GAIM_XFER_UNKNOWN); + + if (account) { + GaimBuddy *buddy; + buddy = gaim_find_buddy(account, who); + if (buddy) + who = gaim_buddy_get_alias(buddy); + } + + if (type == GAIM_XFER_SEND) + title = g_strdup_printf(_("File transfer to %s failed."), who); + else + title = g_strdup_printf(_("File transfer from %s failed."), who); + + gaim_notify_error(NULL, NULL, title, msg); + + g_free(title); +} + +void +gaim_xfer_update_progress(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + ui_ops = gaim_xfer_get_ui_ops(xfer); + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, gaim_xfer_get_progress(xfer)); +} + + +/************************************************************************** + * File Transfer Subsystem API + **************************************************************************/ +void * +gaim_xfers_get_handle(void) { + static int handle = 0; + + return &handle; +} + +void +gaim_xfers_init(void) { + void *handle = gaim_xfers_get_handle(); + + /* register signals */ + gaim_signal_register(handle, "file-recv-accept", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-send-accept", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-recv-start", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-send-start", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-send-cancel", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-recv-cancel", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-send-complete", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-recv-complete", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-recv-request", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); +} + +void +gaim_xfers_uninit(void) { + gaim_signals_disconnect_by_handle(gaim_xfers_get_handle()); +} + +void +gaim_xfers_set_ui_ops(GaimXferUiOps *ops) { + xfer_ui_ops = ops; +} + +GaimXferUiOps * +gaim_xfers_get_ui_ops(void) { + return xfer_ui_ops; +}