* @file ft.c File Transfer API * 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 * 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 static GaimXferUiOps *xfer_ui_ops = NULL; gaim_xfer_new(GaimAccount *account, GaimXferType type, const char *who) 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->who = g_strdup(who); xfer->ui_ops = gaim_xfers_get_ui_ops(); ui_ops = gaim_xfer_get_ui_ops(xfer); if (ui_ops != NULL && ui_ops->new_xfer != NULL) gaim_xfer_destroy(GaimXfer *xfer) 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) g_free(xfer->local_filename); gaim_xfer_ref(GaimXfer *xfer) g_return_if_fail(xfer != NULL); gaim_xfer_unref(GaimXfer *xfer) g_return_if_fail(xfer != NULL); gaim_xfer_set_status(GaimXfer *xfer, GaimXferStatusType status) g_return_if_fail(xfer != NULL); gaim_xfer_conversation_write(GaimXfer *xfer, char *message, gboolean is_error) GaimConversation *conv = NULL; GString *gs_message = NULL; GaimMessageFlags flags = GAIM_MESSAGE_SYSTEM; g_return_if_fail(xfer != NULL); g_return_if_fail(message != NULL); conv = gaim_find_conversation_with_account(xfer->who, gaim_xfer_get_account(xfer)); gs_message = g_string_new(message); flags = GAIM_MESSAGE_ERROR; gaim_conversation_write(conv, NULL, gs_message->str, flags, time(NULL)); g_string_free(gs_message, TRUE); static void gaim_xfer_show_file_error(GaimXfer *xfer, const char *filename) GaimXferType xfer_type = gaim_xfer_get_type(xfer); msg = g_strdup_printf(_("Error reading %s: \n%s.\n"), filename, strerror(errno)); msg = g_strdup_printf(_("Error writing %s: \n%s.\n"), filename, strerror(errno)); msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"), filename, strerror(errno)); gaim_xfer_conversation_write(xfer, msg, TRUE); gaim_xfer_error(xfer_type, xfer->who, msg); gaim_xfer_choose_file_ok_cb(void *user_data, const char *filename) xfer = (GaimXfer *)user_data; if (stat(filename, &st) != 0) { if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) { gaim_xfer_request_accepted(xfer, filename); gaim_xfer_show_file_error(xfer, filename); gaim_xfer_request_denied(xfer); else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) && 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) && * 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) && char *msg = g_strdup_printf( _("%s is not a regular file. Cowardly refusing to overwrite it.\n"), filename); gaim_notify_error(NULL, NULL, msg, NULL); gaim_xfer_request_denied(xfer); gaim_xfer_request_accepted(xfer, filename); 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); 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); cancel_recv_cb(GaimXfer *xfer) gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL); gaim_xfer_request_denied(xfer); gaim_xfer_ask_recv(GaimXfer *xfer) char *buf, *size_buf, *escaped; /* If we have already accepted the request, ask the destination file if (gaim_xfer_get_status(xfer) != GAIM_XFER_STATUS_ACCEPTED) { size = gaim_xfer_get_size(xfer); size_buf = gaim_str_size_to_units(size); escaped = g_markup_escape_text(gaim_xfer_get_filename(xfer), -1); buf = g_strdup_printf(_("%s wants to send you %s (%s)"), 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)); gaim_xfer_choose_file(xfer); ask_accept_ok(GaimXfer *xfer) gaim_xfer_request_accepted(xfer, NULL); ask_accept_cancel(GaimXfer *xfer) gaim_xfer_request_denied(xfer); gaim_xfer_ask_accept(GaimXfer *xfer) buf = g_strdup_printf(_("Accept file transfer request from %s?"), 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)); gaim_xfer_request(GaimXfer *xfer) g_return_if_fail(xfer != NULL); g_return_if_fail(xfer->ops.init != NULL); if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) { if (gaim_xfer_get_filename(xfer) || gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_ACCEPTED) gaim_xfer_ask_recv(xfer); gaim_xfer_ask_accept(xfer); gaim_xfer_choose_file(xfer); gaim_xfer_request_accepted(GaimXfer *xfer, const char *filename) type = gaim_xfer_get_type(xfer); if (!filename && type == GAIM_XFER_RECEIVE) { xfer->status = GAIM_XFER_STATUS_ACCEPTED; if (type == GAIM_XFER_SEND) { /* Check the filename. */ if (g_strrstr(filename, "..")) { msg = g_strdup_printf(_("%s is not a valid filename.\n"), filename); gaim_xfer_error(type, xfer->who, msg); if (stat(filename, &st) == -1) { gaim_xfer_show_file_error(xfer, filename); gaim_xfer_set_local_filename(xfer, filename); gaim_xfer_set_filename(xfer, g_basename(filename)); gaim_xfer_set_size(xfer, st.st_size); msg = g_strdup_printf(_("Offering to send %s to %s"), gaim_xfer_conversation_write(xfer, msg, FALSE); xfer->status = GAIM_XFER_STATUS_ACCEPTED; gaim_xfer_set_local_filename(xfer, filename); 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_get_type(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, GAIM_XFER_UNKNOWN); gaim_xfer_get_account(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, NULL); gaim_xfer_get_status(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, GAIM_XFER_STATUS_UNKNOWN); 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)) 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); gaim_xfer_get_filename(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, NULL); gaim_xfer_get_local_filename(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, NULL); return xfer->local_filename; gaim_xfer_get_bytes_sent(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, 0); gaim_xfer_get_bytes_remaining(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, 0); return xfer->bytes_remaining; gaim_xfer_get_size(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, 0); gaim_xfer_get_progress(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, 0.0); if (gaim_xfer_get_size(xfer) == 0) return ((double)gaim_xfer_get_bytes_sent(xfer) / (double)gaim_xfer_get_size(xfer)); gaim_xfer_get_local_port(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, -1); gaim_xfer_get_remote_ip(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, NULL); gaim_xfer_get_remote_port(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, -1); return xfer->remote_port; gaim_xfer_set_completed(GaimXfer *xfer, gboolean completed) g_return_if_fail(xfer != NULL); gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_DONE); 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)); gaim_xfer_set_message(GaimXfer *xfer, const char *message) g_return_if_fail(xfer != NULL); xfer->message = g_strdup(message); gaim_xfer_set_filename(GaimXfer *xfer, const char *filename) g_return_if_fail(xfer != NULL); if (xfer->filename != NULL) xfer->filename = (filename == NULL ? NULL : g_strdup(filename)); gaim_xfer_set_local_filename(GaimXfer *xfer, const char *filename) g_return_if_fail(xfer != NULL); if (xfer->local_filename != NULL) g_free(xfer->local_filename); xfer->local_filename = (filename == NULL ? NULL : g_strdup(filename)); gaim_xfer_set_size(GaimXfer *xfer, size_t size) g_return_if_fail(xfer != NULL); xfer->bytes_remaining = size - xfer->bytes_sent; gaim_xfer_get_ui_ops(const GaimXfer *xfer) g_return_val_if_fail(xfer != NULL, NULL); gaim_xfer_set_init_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) g_return_if_fail(xfer != NULL); void gaim_xfer_set_request_denied_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) g_return_if_fail(xfer != NULL); xfer->ops.request_denied = fnc; gaim_xfer_set_read_fnc(GaimXfer *xfer, ssize_t (*fnc)(char **, GaimXfer *)) g_return_if_fail(xfer != NULL); gaim_xfer_set_write_fnc(GaimXfer *xfer, ssize_t (*fnc)(const char *, size_t, GaimXfer *)) g_return_if_fail(xfer != NULL); gaim_xfer_set_ack_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *, const char *, size_t)) g_return_if_fail(xfer != NULL); gaim_xfer_set_start_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) g_return_if_fail(xfer != NULL); gaim_xfer_set_end_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) g_return_if_fail(xfer != NULL); gaim_xfer_set_cancel_send_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) g_return_if_fail(xfer != NULL); xfer->ops.cancel_send = fnc; gaim_xfer_set_cancel_recv_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) g_return_if_fail(xfer != NULL); xfer->ops.cancel_recv = fnc; gaim_xfer_read(GaimXfer *xfer, char **buffer) g_return_val_if_fail(xfer != NULL, 0); g_return_val_if_fail(buffer != NULL, 0); if (gaim_xfer_get_size(xfer) == 0) s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096); if (xfer->ops.read != NULL) r = (xfer->ops.read)(buffer, xfer); r = read(xfer->fd, *buffer, s); 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); gaim_xfer_write(GaimXfer *xfer, const char *buffer, size_t size) 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); r = write(xfer->fd, buffer, s); if ((gaim_xfer_get_bytes_sent(xfer)+r) >= gaim_xfer_get_size(xfer)) gaim_xfer_set_completed(xfer, TRUE); transfer_cb(gpointer data, gint source, GaimInputCondition condition) GaimXfer *xfer = (GaimXfer *)data; if (condition & GAIM_INPUT_READ) { r = gaim_xfer_read(xfer, &buffer); fwrite(buffer, 1, r, xfer->dest_fp); gaim_xfer_cancel_remote(xfer); 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. */ gaim_input_remove(xfer->watcher); fread(buffer, 1, s, xfer->dest_fp); /* Write as much as we're allowed to. */ r = gaim_xfer_write(xfer, buffer, s); gaim_xfer_cancel_remote(xfer); /* We have to seek back in the file now. */ fseek(xfer->dest_fp, r - s, SEEK_CUR); if (gaim_xfer_get_size(xfer) > 0) xfer->bytes_remaining -= r; if (xfer->ops.ack != NULL) xfer->ops.ack(xfer, buffer, r); 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)) begin_transfer(GaimXfer *xfer, GaimInputCondition cond) GaimXferType type = gaim_xfer_get_type(xfer); xfer->dest_fp = 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); xfer->watcher = gaim_input_add(xfer->fd, cond, transfer_cb, xfer); if (xfer->ops.start != NULL) connect_cb(gpointer data, gint source, GaimInputCondition condition) GaimXfer *xfer = (GaimXfer *)data; begin_transfer(xfer, condition); gaim_xfer_start(GaimXfer *xfer, int fd, const char *ip, 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); gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_STARTED); if (type == GAIM_XFER_RECEIVE) { 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); begin_transfer(xfer, cond); gaim_xfer_end(GaimXfer *xfer) g_return_if_fail(xfer != NULL); /* See if we are actually trying to cancel this. */ if (gaim_xfer_get_status(xfer) != GAIM_XFER_STATUS_DONE) { gaim_xfer_cancel_local(xfer); msg = g_strdup_printf(_("Transfer of %s complete"), gaim_xfer_get_filename(xfer)); gaim_xfer_conversation_write(xfer, msg, FALSE); if (xfer->ops.end != NULL) if (xfer->watcher != 0) { gaim_input_remove(xfer->watcher); if (xfer->dest_fp != NULL) { gaim_xfer_add(GaimXfer *xfer) g_return_if_fail(xfer != NULL); ui_ops = gaim_xfer_get_ui_ops(xfer); if (ui_ops != NULL && ui_ops->add_xfer != NULL) gaim_xfer_cancel_local(GaimXfer *xfer) char *msg = NULL, *escaped; g_return_if_fail(xfer != NULL); gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL); escaped = g_markup_escape_text(gaim_xfer_get_filename(xfer), -1); msg = g_strdup_printf(_("You canceled the transfer of %s"), gaim_xfer_conversation_write(xfer, msg, FALSE); if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) if (xfer->ops.cancel_send != NULL) xfer->ops.cancel_send(xfer); if (xfer->ops.cancel_recv != NULL) xfer->ops.cancel_recv(xfer); if (xfer->watcher != 0) { gaim_input_remove(xfer->watcher); if (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_cancel_remote(GaimXfer *xfer) g_return_if_fail(xfer != NULL); gaim_request_close_with_handle(xfer); gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_REMOTE); escaped = g_markup_escape_text(gaim_xfer_get_filename(xfer), -1); msg = g_strdup_printf(_("%s canceled the transfer of %s"), gaim_xfer_conversation_write(xfer, msg, TRUE); gaim_xfer_error(gaim_xfer_get_type(xfer), xfer->who, msg); if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) if (xfer->ops.cancel_send != NULL) xfer->ops.cancel_send(xfer); if (xfer->ops.cancel_recv != NULL) xfer->ops.cancel_recv(xfer); if (xfer->watcher != 0) { gaim_input_remove(xfer->watcher); if (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_error(GaimXferType type, const char *who, const char *msg) g_return_if_fail(msg != NULL); g_return_if_fail(type != GAIM_XFER_UNKNOWN); if (type == GAIM_XFER_SEND) title = g_strdup_printf(_("File transfer to %s aborted.\n"), who); title = g_strdup_printf(_("File transfer from %s aborted.\n"), who); gaim_notify_error(NULL, NULL, title, msg); gaim_xfer_update_progress(GaimXfer *xfer) 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 **************************************************************************/ gaim_xfers_set_ui_ops(GaimXferUiOps *ops) gaim_xfers_get_ui_ops(void)