Fix a crash when remote users have certain characters in their
[pidgin-git.git] / libpurple / ft.c
blob598a7c2cf302721c2d8390f79030745c676b6dd6
1 /**
2 * @file ft.c File Transfer API
3 */
5 /* purple
7 * Purple is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "internal.h"
27 #include "dbus-maybe.h"
28 #include "ft.h"
29 #include "network.h"
30 #include "notify.h"
31 #include "prefs.h"
32 #include "proxy.h"
33 #include "request.h"
34 #include "util.h"
35 #include "debug.h"
37 #define FT_INITIAL_BUFFER_SIZE 4096
38 #define FT_MAX_BUFFER_SIZE 65535
40 static PurpleXferUiOps *xfer_ui_ops = NULL;
41 static GList *xfers;
44 * A hack to store more data since we can't extend the size of PurpleXfer
45 * easily.
47 static GHashTable *xfers_data = NULL;
49 typedef struct _PurpleXferPrivData {
51 * Used to moderate the file transfer when either the read/write ui_ops are
52 * set or fd is not set. In those cases, the UI/prpl call the respective
53 * function, which is somewhat akin to a fd watch being triggered.
55 enum {
56 PURPLE_XFER_READY_NONE = 0x0,
57 PURPLE_XFER_READY_UI = 0x1,
58 PURPLE_XFER_READY_PRPL = 0x2,
59 } ready;
61 /* TODO: Should really use a PurpleCircBuffer for this. */
62 GByteArray *buffer;
64 gpointer thumbnail_data; /**< thumbnail image */
65 gsize thumbnail_size;
66 gchar *thumbnail_mimetype;
67 } PurpleXferPrivData;
69 static int purple_xfer_choose_file(PurpleXfer *xfer);
71 static void
72 purple_xfer_priv_data_destroy(gpointer data)
74 PurpleXferPrivData *priv = data;
76 if (priv->buffer)
77 g_byte_array_free(priv->buffer, TRUE);
79 g_free(priv->thumbnail_data);
81 g_free(priv->thumbnail_mimetype);
83 g_free(priv);
86 static const gchar *
87 purple_xfer_status_type_to_string(PurpleXferStatusType type)
89 static const struct {
90 PurpleXferStatusType type;
91 const char *name;
92 } type_names[] = {
93 { PURPLE_XFER_STATUS_UNKNOWN, "unknown" },
94 { PURPLE_XFER_STATUS_NOT_STARTED, "not started" },
95 { PURPLE_XFER_STATUS_ACCEPTED, "accepted" },
96 { PURPLE_XFER_STATUS_STARTED, "started" },
97 { PURPLE_XFER_STATUS_DONE, "done" },
98 { PURPLE_XFER_STATUS_CANCEL_LOCAL, "cancelled locally" },
99 { PURPLE_XFER_STATUS_CANCEL_REMOTE, "cancelled remotely" }
101 int i;
103 for (i = 0; i < G_N_ELEMENTS(type_names); ++i)
104 if (type_names[i].type == type)
105 return type_names[i].name;
107 return "invalid state";
110 GList *
111 purple_xfers_get_all()
113 return xfers;
116 PurpleXfer *
117 purple_xfer_new(PurpleAccount *account, PurpleXferType type, const char *who)
119 PurpleXfer *xfer;
120 PurpleXferUiOps *ui_ops;
121 PurpleXferPrivData *priv;
123 g_return_val_if_fail(type != PURPLE_XFER_UNKNOWN, NULL);
124 g_return_val_if_fail(account != NULL, NULL);
125 g_return_val_if_fail(who != NULL, NULL);
127 xfer = g_new0(PurpleXfer, 1);
128 PURPLE_DBUS_REGISTER_POINTER(xfer, PurpleXfer);
130 xfer->ref = 1;
131 xfer->type = type;
132 xfer->account = account;
133 xfer->who = g_strdup(who);
134 xfer->ui_ops = ui_ops = purple_xfers_get_ui_ops();
135 xfer->message = NULL;
136 xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE;
137 xfer->fd = -1;
139 priv = g_new0(PurpleXferPrivData, 1);
140 priv->ready = PURPLE_XFER_READY_NONE;
142 if (ui_ops && ui_ops->data_not_sent) {
143 /* If the ui will handle unsent data no need for buffer */
144 priv->buffer = NULL;
145 } else {
146 priv->buffer = g_byte_array_sized_new(FT_INITIAL_BUFFER_SIZE);
149 g_hash_table_insert(xfers_data, xfer, priv);
151 ui_ops = purple_xfer_get_ui_ops(xfer);
153 if (ui_ops != NULL && ui_ops->new_xfer != NULL)
154 ui_ops->new_xfer(xfer);
156 xfers = g_list_prepend(xfers, xfer);
158 if (purple_debug_is_verbose())
159 purple_debug_info("xfer", "new %p [%d]\n", xfer, xfer->ref);
161 return xfer;
164 static void
165 purple_xfer_destroy(PurpleXfer *xfer)
167 PurpleXferUiOps *ui_ops;
169 g_return_if_fail(xfer != NULL);
171 if (purple_debug_is_verbose())
172 purple_debug_info("xfer", "destroyed %p [%d]\n", xfer, xfer->ref);
174 /* Close the file browser, if it's open */
175 purple_request_close_with_handle(xfer);
177 if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_STARTED)
178 purple_xfer_cancel_local(xfer);
180 ui_ops = purple_xfer_get_ui_ops(xfer);
182 if (ui_ops != NULL && ui_ops->destroy != NULL)
183 ui_ops->destroy(xfer);
185 g_free(xfer->who);
186 g_free(xfer->filename);
187 g_free(xfer->remote_ip);
188 g_free(xfer->local_filename);
190 g_hash_table_remove(xfers_data, xfer);
192 PURPLE_DBUS_UNREGISTER_POINTER(xfer);
193 xfers = g_list_remove(xfers, xfer);
194 g_free(xfer);
197 void
198 purple_xfer_ref(PurpleXfer *xfer)
200 g_return_if_fail(xfer != NULL);
202 xfer->ref++;
204 if (purple_debug_is_verbose())
205 purple_debug_info("xfer", "ref'd %p [%d]\n", xfer, xfer->ref);
208 void
209 purple_xfer_unref(PurpleXfer *xfer)
211 g_return_if_fail(xfer != NULL);
212 g_return_if_fail(xfer->ref > 0);
214 xfer->ref--;
216 if (purple_debug_is_verbose())
217 purple_debug_info("xfer", "unref'd %p [%d]\n", xfer, xfer->ref);
219 if (xfer->ref == 0)
220 purple_xfer_destroy(xfer);
223 static void
224 purple_xfer_set_status(PurpleXfer *xfer, PurpleXferStatusType status)
226 g_return_if_fail(xfer != NULL);
228 if (purple_debug_is_verbose())
229 purple_debug_info("xfer", "Changing status of xfer %p from %s to %s\n",
230 xfer, purple_xfer_status_type_to_string(xfer->status),
231 purple_xfer_status_type_to_string(status));
233 if (xfer->status == status)
234 return;
236 xfer->status = status;
238 if(xfer->type == PURPLE_XFER_SEND) {
239 switch(status) {
240 case PURPLE_XFER_STATUS_ACCEPTED:
241 purple_signal_emit(purple_xfers_get_handle(), "file-send-accept", xfer);
242 break;
243 case PURPLE_XFER_STATUS_STARTED:
244 purple_signal_emit(purple_xfers_get_handle(), "file-send-start", xfer);
245 break;
246 case PURPLE_XFER_STATUS_DONE:
247 purple_signal_emit(purple_xfers_get_handle(), "file-send-complete", xfer);
248 break;
249 case PURPLE_XFER_STATUS_CANCEL_LOCAL:
250 case PURPLE_XFER_STATUS_CANCEL_REMOTE:
251 purple_signal_emit(purple_xfers_get_handle(), "file-send-cancel", xfer);
252 break;
253 default:
254 break;
256 } else if(xfer->type == PURPLE_XFER_RECEIVE) {
257 switch(status) {
258 case PURPLE_XFER_STATUS_ACCEPTED:
259 purple_signal_emit(purple_xfers_get_handle(), "file-recv-accept", xfer);
260 break;
261 case PURPLE_XFER_STATUS_STARTED:
262 purple_signal_emit(purple_xfers_get_handle(), "file-recv-start", xfer);
263 break;
264 case PURPLE_XFER_STATUS_DONE:
265 purple_signal_emit(purple_xfers_get_handle(), "file-recv-complete", xfer);
266 break;
267 case PURPLE_XFER_STATUS_CANCEL_LOCAL:
268 case PURPLE_XFER_STATUS_CANCEL_REMOTE:
269 purple_signal_emit(purple_xfers_get_handle(), "file-recv-cancel", xfer);
270 break;
271 default:
272 break;
277 static void
278 purple_xfer_conversation_write_internal(PurpleXfer *xfer,
279 const char *message, gboolean is_error, gboolean print_thumbnail)
281 PurpleConversation *conv = NULL;
282 PurpleMessageFlags flags = PURPLE_MESSAGE_SYSTEM;
283 char *escaped;
284 gconstpointer thumbnail_data;
285 gsize size;
287 g_return_if_fail(xfer != NULL);
288 g_return_if_fail(message != NULL);
290 thumbnail_data = purple_xfer_get_thumbnail(xfer, &size);
292 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, xfer->who,
293 purple_xfer_get_account(xfer));
295 if (conv == NULL)
296 return;
298 escaped = g_markup_escape_text(message, -1);
300 if (is_error)
301 flags |= PURPLE_MESSAGE_ERROR;
303 if (print_thumbnail && thumbnail_data) {
304 gchar *message_with_img;
305 gpointer data = g_memdup(thumbnail_data, size);
306 int id = purple_imgstore_add_with_id(data, size, NULL);
308 message_with_img =
309 g_strdup_printf("<img id='%d'> %s", id, escaped);
310 purple_conversation_write(conv, NULL, message_with_img, flags,
311 time(NULL));
312 purple_imgstore_unref_by_id(id);
313 g_free(message_with_img);
314 } else {
315 purple_conversation_write(conv, NULL, escaped, flags, time(NULL));
317 g_free(escaped);
320 void
321 purple_xfer_conversation_write(PurpleXfer *xfer, gchar *message,
322 gboolean is_error)
324 purple_xfer_conversation_write_internal(xfer, message, is_error, FALSE);
327 /* maybe this one should be exported publically? */
328 static void
329 purple_xfer_conversation_write_with_thumbnail(PurpleXfer *xfer,
330 const gchar *message)
332 purple_xfer_conversation_write_internal(xfer, message, FALSE, TRUE);
336 static void purple_xfer_show_file_error(PurpleXfer *xfer, const char *filename)
338 int err = errno;
339 gchar *msg = NULL, *utf8;
340 PurpleXferType xfer_type = purple_xfer_get_type(xfer);
341 PurpleAccount *account = purple_xfer_get_account(xfer);
343 utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
344 switch(xfer_type) {
345 case PURPLE_XFER_SEND:
346 msg = g_strdup_printf(_("Error reading %s: \n%s.\n"),
347 utf8, g_strerror(err));
348 break;
349 case PURPLE_XFER_RECEIVE:
350 msg = g_strdup_printf(_("Error writing %s: \n%s.\n"),
351 utf8, g_strerror(err));
352 break;
353 default:
354 msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"),
355 utf8, g_strerror(err));
356 break;
358 g_free(utf8);
360 purple_xfer_conversation_write(xfer, msg, TRUE);
361 purple_xfer_error(xfer_type, account, xfer->who, msg);
362 g_free(msg);
365 static void
366 purple_xfer_choose_file_ok_cb(void *user_data, const char *filename)
368 PurpleXfer *xfer;
369 PurpleXferType type;
370 struct stat st;
371 gchar *dir;
373 xfer = (PurpleXfer *)user_data;
374 type = purple_xfer_get_type(xfer);
376 if (g_stat(filename, &st) != 0) {
377 /* File not found. */
378 if (type == PURPLE_XFER_RECEIVE) {
379 #ifndef _WIN32
380 int mode = W_OK;
381 #else
382 int mode = F_OK;
383 #endif
384 dir = g_path_get_dirname(filename);
386 if (g_access(dir, mode) == 0) {
387 purple_xfer_request_accepted(xfer, filename);
388 } else {
389 purple_xfer_ref(xfer);
390 purple_notify_message(
391 NULL, PURPLE_NOTIFY_MSG_ERROR, NULL,
392 _("Directory is not writable."), NULL,
393 (PurpleNotifyCloseCallback)purple_xfer_choose_file, xfer);
396 g_free(dir);
398 else {
399 purple_xfer_show_file_error(xfer, filename);
400 purple_xfer_cancel_local(xfer);
403 else if ((type == PURPLE_XFER_SEND) && (st.st_size == 0)) {
405 purple_notify_error(NULL, NULL,
406 _("Cannot send a file of 0 bytes."), NULL);
408 purple_xfer_cancel_local(xfer);
410 else if ((type == PURPLE_XFER_SEND) && S_ISDIR(st.st_mode)) {
412 * XXX - Sending a directory should be valid for some protocols.
414 purple_notify_error(NULL, NULL,
415 _("Cannot send a directory."), NULL);
417 purple_xfer_cancel_local(xfer);
419 else if ((type == PURPLE_XFER_RECEIVE) && S_ISDIR(st.st_mode)) {
420 char *msg, *utf8;
421 utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
422 msg = g_strdup_printf(
423 _("%s is not a regular file. Cowardly refusing to overwrite it.\n"), utf8);
424 g_free(utf8);
425 purple_notify_error(NULL, NULL, msg, NULL);
426 g_free(msg);
427 purple_xfer_request_denied(xfer);
429 else if (type == PURPLE_XFER_SEND) {
430 #ifndef _WIN32
431 int mode = R_OK;
432 #else
433 int mode = F_OK;
434 #endif
436 if (g_access(filename, mode) == 0) {
437 purple_xfer_request_accepted(xfer, filename);
438 } else {
439 purple_xfer_ref(xfer);
440 purple_notify_message(
441 NULL, PURPLE_NOTIFY_MSG_ERROR, NULL,
442 _("File is not readable."), NULL,
443 (PurpleNotifyCloseCallback)purple_xfer_choose_file, xfer);
446 else {
447 purple_xfer_request_accepted(xfer, filename);
450 purple_xfer_unref(xfer);
453 static void
454 purple_xfer_choose_file_cancel_cb(void *user_data, const char *filename)
456 PurpleXfer *xfer = (PurpleXfer *)user_data;
458 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
459 if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
460 purple_xfer_cancel_local(xfer);
461 else
462 purple_xfer_request_denied(xfer);
463 purple_xfer_unref(xfer);
466 static int
467 purple_xfer_choose_file(PurpleXfer *xfer)
469 purple_request_file(xfer, NULL, purple_xfer_get_filename(xfer),
470 (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE),
471 G_CALLBACK(purple_xfer_choose_file_ok_cb),
472 G_CALLBACK(purple_xfer_choose_file_cancel_cb),
473 purple_xfer_get_account(xfer), xfer->who, NULL,
474 xfer);
476 return 0;
479 static int
480 cancel_recv_cb(PurpleXfer *xfer)
482 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
483 purple_xfer_request_denied(xfer);
484 purple_xfer_unref(xfer);
486 return 0;
489 static void
490 purple_xfer_ask_recv(PurpleXfer *xfer)
492 char *buf, *size_buf;
493 size_t size;
494 gconstpointer thumb;
495 gsize thumb_size;
497 /* If we have already accepted the request, ask the destination file
498 name directly */
499 if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_ACCEPTED) {
500 PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
502 if (purple_xfer_get_filename(xfer) != NULL)
504 size = purple_xfer_get_size(xfer);
505 size_buf = purple_str_size_to_units(size);
506 buf = g_strdup_printf(_("%s wants to send you %s (%s)"),
507 buddy ? purple_buddy_get_alias(buddy) : xfer->who,
508 purple_xfer_get_filename(xfer), size_buf);
509 g_free(size_buf);
511 else
513 buf = g_strdup_printf(_("%s wants to send you a file"),
514 buddy ? purple_buddy_get_alias(buddy) : xfer->who);
517 if (xfer->message != NULL)
518 serv_got_im(purple_account_get_connection(xfer->account),
519 xfer->who, xfer->message, 0, time(NULL));
521 if ((thumb = purple_xfer_get_thumbnail(xfer, &thumb_size))) {
522 purple_request_accept_cancel_with_icon(xfer, NULL, buf, NULL,
523 PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL,
524 thumb, thumb_size, xfer,
525 G_CALLBACK(purple_xfer_choose_file),
526 G_CALLBACK(cancel_recv_cb));
527 } else {
528 purple_request_accept_cancel(xfer, NULL, buf, NULL,
529 PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL,
530 xfer, G_CALLBACK(purple_xfer_choose_file),
531 G_CALLBACK(cancel_recv_cb));
534 g_free(buf);
535 } else
536 purple_xfer_choose_file(xfer);
539 static int
540 ask_accept_ok(PurpleXfer *xfer)
542 purple_xfer_request_accepted(xfer, NULL);
544 return 0;
547 static int
548 ask_accept_cancel(PurpleXfer *xfer)
550 purple_xfer_request_denied(xfer);
551 purple_xfer_unref(xfer);
553 return 0;
556 static void
557 purple_xfer_ask_accept(PurpleXfer *xfer)
559 char *buf, *buf2 = NULL;
560 PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
562 buf = g_strdup_printf(_("Accept file transfer request from %s?"),
563 buddy ? purple_buddy_get_alias(buddy) : xfer->who);
564 if (purple_xfer_get_remote_ip(xfer) &&
565 purple_xfer_get_remote_port(xfer))
566 buf2 = g_strdup_printf(_("A file is available for download from:\n"
567 "Remote host: %s\nRemote port: %d"),
568 purple_xfer_get_remote_ip(xfer),
569 purple_xfer_get_remote_port(xfer));
570 purple_request_accept_cancel(xfer, NULL, buf, buf2,
571 PURPLE_DEFAULT_ACTION_NONE,
572 xfer->account, xfer->who, NULL,
573 xfer,
574 G_CALLBACK(ask_accept_ok),
575 G_CALLBACK(ask_accept_cancel));
576 g_free(buf);
577 g_free(buf2);
580 void
581 purple_xfer_request(PurpleXfer *xfer)
583 g_return_if_fail(xfer != NULL);
584 g_return_if_fail(xfer->ops.init != NULL);
586 purple_xfer_ref(xfer);
588 if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE)
590 purple_signal_emit(purple_xfers_get_handle(), "file-recv-request", xfer);
591 if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL)
593 /* The file-transfer was cancelled by a plugin */
594 purple_xfer_cancel_local(xfer);
596 else if (purple_xfer_get_filename(xfer) ||
597 purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_ACCEPTED)
599 gchar* message = NULL;
600 PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
602 message = g_strdup_printf(_("%s is offering to send file %s"),
603 buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer));
604 purple_xfer_conversation_write_with_thumbnail(xfer, message);
605 g_free(message);
607 /* Ask for a filename to save to if it's not already given by a plugin */
608 if (xfer->local_filename == NULL)
609 purple_xfer_ask_recv(xfer);
611 else
613 purple_xfer_ask_accept(xfer);
616 else
618 purple_xfer_choose_file(xfer);
622 void
623 purple_xfer_request_accepted(PurpleXfer *xfer, const char *filename)
625 PurpleXferType type;
626 struct stat st;
627 char *msg, *utf8, *base;
628 PurpleAccount *account;
629 PurpleBuddy *buddy;
631 if (xfer == NULL)
632 return;
634 type = purple_xfer_get_type(xfer);
635 account = purple_xfer_get_account(xfer);
637 purple_debug_misc("xfer", "request accepted for %p\n", xfer);
639 if (!filename && type == PURPLE_XFER_RECEIVE) {
640 xfer->status = PURPLE_XFER_STATUS_ACCEPTED;
641 xfer->ops.init(xfer);
642 return;
645 buddy = purple_find_buddy(account, xfer->who);
647 if (type == PURPLE_XFER_SEND) {
648 /* Sending a file */
649 /* Check the filename. */
650 PurpleXferUiOps *ui_ops;
651 ui_ops = purple_xfer_get_ui_ops(xfer);
653 #ifdef _WIN32
654 if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\"))
655 #else
656 if (g_strrstr(filename, "../"))
657 #endif
659 utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
661 msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8);
662 purple_xfer_error(type, account, xfer->who, msg);
663 g_free(utf8);
664 g_free(msg);
666 purple_xfer_unref(xfer);
667 return;
670 if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
671 if (g_stat(filename, &st) == -1) {
672 purple_xfer_show_file_error(xfer, filename);
673 purple_xfer_unref(xfer);
674 return;
677 purple_xfer_set_local_filename(xfer, filename);
678 purple_xfer_set_size(xfer, st.st_size);
679 } else {
680 purple_xfer_set_local_filename(xfer, filename);
683 base = g_path_get_basename(filename);
684 utf8 = g_filename_to_utf8(base, -1, NULL, NULL, NULL);
685 g_free(base);
686 purple_xfer_set_filename(xfer, utf8);
688 msg = g_strdup_printf(_("Offering to send %s to %s"),
689 utf8, buddy ? purple_buddy_get_alias(buddy) : xfer->who);
690 g_free(utf8);
691 purple_xfer_conversation_write(xfer, msg, FALSE);
692 g_free(msg);
694 else {
695 /* Receiving a file */
696 xfer->status = PURPLE_XFER_STATUS_ACCEPTED;
697 purple_xfer_set_local_filename(xfer, filename);
699 msg = g_strdup_printf(_("Starting transfer of %s from %s"),
700 xfer->filename, buddy ? purple_buddy_get_alias(buddy) : xfer->who);
701 purple_xfer_conversation_write(xfer, msg, FALSE);
702 g_free(msg);
705 purple_xfer_add(xfer);
706 xfer->ops.init(xfer);
710 void
711 purple_xfer_request_denied(PurpleXfer *xfer)
713 g_return_if_fail(xfer != NULL);
715 purple_debug_misc("xfer", "xfer %p denied\n", xfer);
717 if (xfer->ops.request_denied != NULL)
718 xfer->ops.request_denied(xfer);
720 purple_xfer_unref(xfer);
723 PurpleXferType
724 purple_xfer_get_type(const PurpleXfer *xfer)
726 g_return_val_if_fail(xfer != NULL, PURPLE_XFER_UNKNOWN);
728 return xfer->type;
731 PurpleAccount *
732 purple_xfer_get_account(const PurpleXfer *xfer)
734 g_return_val_if_fail(xfer != NULL, NULL);
736 return xfer->account;
739 const char *
740 purple_xfer_get_remote_user(const PurpleXfer *xfer)
742 g_return_val_if_fail(xfer != NULL, NULL);
743 return xfer->who;
746 PurpleXferStatusType
747 purple_xfer_get_status(const PurpleXfer *xfer)
749 g_return_val_if_fail(xfer != NULL, PURPLE_XFER_STATUS_UNKNOWN);
751 return xfer->status;
754 /* FIXME: Rename with cancelled for 3.0.0. */
755 gboolean
756 purple_xfer_is_canceled(const PurpleXfer *xfer)
758 g_return_val_if_fail(xfer != NULL, TRUE);
760 if ((purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) ||
761 (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_REMOTE))
762 return TRUE;
763 else
764 return FALSE;
767 gboolean
768 purple_xfer_is_completed(const PurpleXfer *xfer)
770 g_return_val_if_fail(xfer != NULL, TRUE);
772 return (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_DONE);
775 const char *
776 purple_xfer_get_filename(const PurpleXfer *xfer)
778 g_return_val_if_fail(xfer != NULL, NULL);
780 return xfer->filename;
783 const char *
784 purple_xfer_get_local_filename(const PurpleXfer *xfer)
786 g_return_val_if_fail(xfer != NULL, NULL);
788 return xfer->local_filename;
791 size_t
792 purple_xfer_get_bytes_sent(const PurpleXfer *xfer)
794 g_return_val_if_fail(xfer != NULL, 0);
796 return xfer->bytes_sent;
799 size_t
800 purple_xfer_get_bytes_remaining(const PurpleXfer *xfer)
802 g_return_val_if_fail(xfer != NULL, 0);
804 return xfer->bytes_remaining;
807 size_t
808 purple_xfer_get_size(const PurpleXfer *xfer)
810 g_return_val_if_fail(xfer != NULL, 0);
812 return xfer->size;
815 double
816 purple_xfer_get_progress(const PurpleXfer *xfer)
818 g_return_val_if_fail(xfer != NULL, 0.0);
820 if (purple_xfer_get_size(xfer) == 0)
821 return 0.0;
823 return ((double)purple_xfer_get_bytes_sent(xfer) /
824 (double)purple_xfer_get_size(xfer));
827 unsigned int
828 purple_xfer_get_local_port(const PurpleXfer *xfer)
830 g_return_val_if_fail(xfer != NULL, -1);
832 return xfer->local_port;
835 const char *
836 purple_xfer_get_remote_ip(const PurpleXfer *xfer)
838 g_return_val_if_fail(xfer != NULL, NULL);
840 return xfer->remote_ip;
843 unsigned int
844 purple_xfer_get_remote_port(const PurpleXfer *xfer)
846 g_return_val_if_fail(xfer != NULL, -1);
848 return xfer->remote_port;
851 time_t
852 purple_xfer_get_start_time(const PurpleXfer *xfer)
854 g_return_val_if_fail(xfer != NULL, 0);
856 return xfer->start_time;
859 time_t
860 purple_xfer_get_end_time(const PurpleXfer *xfer)
862 g_return_val_if_fail(xfer != NULL, 0);
864 return xfer->end_time;
867 void
868 purple_xfer_set_completed(PurpleXfer *xfer, gboolean completed)
870 PurpleXferUiOps *ui_ops;
872 g_return_if_fail(xfer != NULL);
874 if (completed == TRUE) {
875 char *msg = NULL;
876 PurpleConversation *conv;
878 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_DONE);
880 if (purple_xfer_get_filename(xfer) != NULL)
882 char *filename = g_markup_escape_text(purple_xfer_get_filename(xfer), -1);
883 if (purple_xfer_get_local_filename(xfer)
884 && purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE)
886 char *local = g_markup_escape_text(purple_xfer_get_local_filename(xfer), -1);
887 msg = g_strdup_printf(_("Transfer of file <A HREF=\"file://%s\">%s</A> complete"),
888 local, filename);
889 g_free(local);
891 else
892 msg = g_strdup_printf(_("Transfer of file %s complete"),
893 filename);
894 g_free(filename);
896 else
897 msg = g_strdup(_("File transfer complete"));
899 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, xfer->who,
900 purple_xfer_get_account(xfer));
902 if (conv != NULL)
903 purple_conversation_write(conv, NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL));
904 g_free(msg);
907 ui_ops = purple_xfer_get_ui_ops(xfer);
909 if (ui_ops != NULL && ui_ops->update_progress != NULL)
910 ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer));
913 void
914 purple_xfer_set_message(PurpleXfer *xfer, const char *message)
916 g_return_if_fail(xfer != NULL);
918 g_free(xfer->message);
919 xfer->message = g_strdup(message);
922 void
923 purple_xfer_set_filename(PurpleXfer *xfer, const char *filename)
925 g_return_if_fail(xfer != NULL);
927 g_free(xfer->filename);
928 xfer->filename = g_strdup(filename);
931 void
932 purple_xfer_set_local_filename(PurpleXfer *xfer, const char *filename)
934 g_return_if_fail(xfer != NULL);
936 g_free(xfer->local_filename);
937 xfer->local_filename = g_strdup(filename);
940 void
941 purple_xfer_set_size(PurpleXfer *xfer, size_t size)
943 g_return_if_fail(xfer != NULL);
945 xfer->size = size;
946 xfer->bytes_remaining = xfer->size - purple_xfer_get_bytes_sent(xfer);
949 void
950 purple_xfer_set_bytes_sent(PurpleXfer *xfer, size_t bytes_sent)
952 g_return_if_fail(xfer != NULL);
954 xfer->bytes_sent = bytes_sent;
955 xfer->bytes_remaining = purple_xfer_get_size(xfer) - bytes_sent;
958 PurpleXferUiOps *
959 purple_xfer_get_ui_ops(const PurpleXfer *xfer)
961 g_return_val_if_fail(xfer != NULL, NULL);
963 return xfer->ui_ops;
966 void
967 purple_xfer_set_init_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
969 g_return_if_fail(xfer != NULL);
971 xfer->ops.init = fnc;
974 void purple_xfer_set_request_denied_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
976 g_return_if_fail(xfer != NULL);
978 xfer->ops.request_denied = fnc;
981 void
982 purple_xfer_set_read_fnc(PurpleXfer *xfer, gssize (*fnc)(guchar **, PurpleXfer *))
984 g_return_if_fail(xfer != NULL);
986 xfer->ops.read = fnc;
989 void
990 purple_xfer_set_write_fnc(PurpleXfer *xfer,
991 gssize (*fnc)(const guchar *, size_t, PurpleXfer *))
993 g_return_if_fail(xfer != NULL);
995 xfer->ops.write = fnc;
998 void
999 purple_xfer_set_ack_fnc(PurpleXfer *xfer,
1000 void (*fnc)(PurpleXfer *, const guchar *, size_t))
1002 g_return_if_fail(xfer != NULL);
1004 xfer->ops.ack = fnc;
1007 void
1008 purple_xfer_set_start_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
1010 g_return_if_fail(xfer != NULL);
1012 xfer->ops.start = fnc;
1015 void
1016 purple_xfer_set_end_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
1018 g_return_if_fail(xfer != NULL);
1020 xfer->ops.end = fnc;
1023 void
1024 purple_xfer_set_cancel_send_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
1026 g_return_if_fail(xfer != NULL);
1028 xfer->ops.cancel_send = fnc;
1031 void
1032 purple_xfer_set_cancel_recv_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
1034 g_return_if_fail(xfer != NULL);
1036 xfer->ops.cancel_recv = fnc;
1039 static void
1040 purple_xfer_increase_buffer_size(PurpleXfer *xfer)
1042 xfer->current_buffer_size = MIN(xfer->current_buffer_size * 1.5,
1043 FT_MAX_BUFFER_SIZE);
1046 gssize
1047 purple_xfer_read(PurpleXfer *xfer, guchar **buffer)
1049 gssize s, r;
1051 g_return_val_if_fail(xfer != NULL, 0);
1052 g_return_val_if_fail(buffer != NULL, 0);
1054 if (purple_xfer_get_size(xfer) == 0)
1055 s = xfer->current_buffer_size;
1056 else
1057 s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
1059 if (xfer->ops.read != NULL) {
1060 r = (xfer->ops.read)(buffer, xfer);
1062 else {
1063 *buffer = g_malloc0(s);
1065 r = read(xfer->fd, *buffer, s);
1066 if (r < 0 && errno == EAGAIN)
1067 r = 0;
1068 else if (r < 0)
1069 r = -1;
1070 else if (r == 0)
1071 r = -1;
1074 if (r == xfer->current_buffer_size)
1076 * We managed to read the entire buffer. This means our this
1077 * network is fast and our buffer is too small, so make it
1078 * bigger.
1080 purple_xfer_increase_buffer_size(xfer);
1082 return r;
1085 gssize
1086 purple_xfer_write(PurpleXfer *xfer, const guchar *buffer, gsize size)
1088 gssize r, s;
1090 g_return_val_if_fail(xfer != NULL, 0);
1091 g_return_val_if_fail(buffer != NULL, 0);
1092 g_return_val_if_fail(size != 0, 0);
1094 s = MIN(purple_xfer_get_bytes_remaining(xfer), size);
1096 if (xfer->ops.write != NULL) {
1097 r = (xfer->ops.write)(buffer, s, xfer);
1098 } else {
1099 r = write(xfer->fd, buffer, s);
1100 if (r < 0 && errno == EAGAIN)
1101 r = 0;
1103 if (r >= 0 && (purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer) &&
1104 !purple_xfer_is_completed(xfer))
1105 purple_xfer_set_completed(xfer, TRUE);
1108 return r;
1111 static void
1112 do_transfer(PurpleXfer *xfer)
1114 PurpleXferUiOps *ui_ops;
1115 guchar *buffer = NULL;
1116 gssize r = 0;
1118 ui_ops = purple_xfer_get_ui_ops(xfer);
1120 if (xfer->type == PURPLE_XFER_RECEIVE) {
1121 r = purple_xfer_read(xfer, &buffer);
1122 if (r > 0) {
1123 size_t wc;
1124 if (ui_ops && ui_ops->ui_write)
1125 wc = ui_ops->ui_write(xfer, buffer, r);
1126 else
1127 wc = fwrite(buffer, 1, r, xfer->dest_fp);
1129 if (wc != r) {
1130 purple_debug_error("filetransfer", "Unable to write whole buffer.\n");
1131 purple_xfer_cancel_local(xfer);
1132 g_free(buffer);
1133 return;
1136 if ((purple_xfer_get_size(xfer) > 0) &&
1137 ((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer)))
1138 purple_xfer_set_completed(xfer, TRUE);
1139 } else if(r < 0) {
1140 purple_xfer_cancel_remote(xfer);
1141 g_free(buffer);
1142 return;
1144 } else if (xfer->type == PURPLE_XFER_SEND) {
1145 size_t result = 0;
1146 size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
1147 PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1148 gboolean read = TRUE;
1150 /* this is so the prpl can keep the connection open
1151 if it needs to for some odd reason. */
1152 if (s == 0) {
1153 if (xfer->watcher) {
1154 purple_input_remove(xfer->watcher);
1155 xfer->watcher = 0;
1157 return;
1160 if (priv->buffer) {
1161 if (priv->buffer->len < s) {
1162 s -= priv->buffer->len;
1163 read = TRUE;
1164 } else {
1165 read = FALSE;
1169 if (read) {
1170 if (ui_ops && ui_ops->ui_read) {
1171 gssize tmp = ui_ops->ui_read(xfer, &buffer, s);
1172 if (tmp == 0) {
1174 * The UI claimed it was ready, but didn't have any data for
1175 * us... It will call purple_xfer_ui_ready when ready, which
1176 * sets back up this watcher.
1178 if (xfer->watcher != 0) {
1179 purple_input_remove(xfer->watcher);
1180 xfer->watcher = 0;
1183 /* Need to indicate the prpl is still ready... */
1184 priv->ready |= PURPLE_XFER_READY_PRPL;
1186 g_return_if_reached();
1187 } else if (tmp < 0) {
1188 purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
1189 purple_xfer_cancel_local(xfer);
1190 return;
1193 result = tmp;
1194 } else {
1195 buffer = g_malloc(s);
1196 result = fread(buffer, 1, s, xfer->dest_fp);
1197 if (result != s) {
1198 purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
1199 purple_xfer_cancel_local(xfer);
1200 g_free(buffer);
1201 return;
1206 if (priv->buffer) {
1207 g_byte_array_append(priv->buffer, buffer, result);
1208 g_free(buffer);
1209 buffer = priv->buffer->data;
1210 result = priv->buffer->len;
1213 r = purple_xfer_write(xfer, buffer, result);
1215 if (r == -1) {
1216 purple_xfer_cancel_remote(xfer);
1217 if (!priv->buffer)
1218 /* We don't free buffer if priv->buffer is set, because in
1219 that case buffer doesn't belong to us. */
1220 g_free(buffer);
1221 return;
1222 } else if (r == result) {
1224 * We managed to write the entire buffer. This means our
1225 * network is fast and our buffer is too small, so make it
1226 * bigger.
1228 purple_xfer_increase_buffer_size(xfer);
1229 } else {
1230 if (ui_ops && ui_ops->data_not_sent)
1231 ui_ops->data_not_sent(xfer, buffer + r, result - r);
1234 if (priv->buffer) {
1236 * Remove what we wrote
1237 * If we wrote the whole buffer the byte array will be empty
1238 * Otherwise we'll keep what wasn't sent for next time.
1240 buffer = NULL;
1241 g_byte_array_remove_range(priv->buffer, 0, r);
1245 if (r > 0) {
1246 if (purple_xfer_get_size(xfer) > 0)
1247 xfer->bytes_remaining -= r;
1249 xfer->bytes_sent += r;
1251 if (xfer->ops.ack != NULL)
1252 xfer->ops.ack(xfer, buffer, r);
1254 g_free(buffer);
1256 if (ui_ops != NULL && ui_ops->update_progress != NULL)
1257 ui_ops->update_progress(xfer,
1258 purple_xfer_get_progress(xfer));
1261 if (purple_xfer_is_completed(xfer))
1262 purple_xfer_end(xfer);
1265 static void
1266 transfer_cb(gpointer data, gint source, PurpleInputCondition condition)
1268 PurpleXfer *xfer = data;
1270 if (xfer->dest_fp == NULL) {
1271 /* The UI is moderating its side manually */
1272 PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1273 if (0 == (priv->ready & PURPLE_XFER_READY_UI)) {
1274 priv->ready |= PURPLE_XFER_READY_PRPL;
1276 purple_input_remove(xfer->watcher);
1277 xfer->watcher = 0;
1279 purple_debug_misc("xfer", "prpl is ready on ft %p, waiting for UI\n", xfer);
1280 return;
1283 priv->ready = PURPLE_XFER_READY_NONE;
1286 do_transfer(xfer);
1289 static void
1290 begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond)
1292 PurpleXferType type = purple_xfer_get_type(xfer);
1293 PurpleXferUiOps *ui_ops = purple_xfer_get_ui_ops(xfer);
1295 if (xfer->start_time != 0) {
1296 purple_debug_error("xfer", "Transfer is being started multiple times\n");
1297 g_return_if_reached();
1300 if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
1301 xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer),
1302 type == PURPLE_XFER_RECEIVE ? "wb" : "rb");
1304 if (xfer->dest_fp == NULL) {
1305 purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer));
1306 purple_xfer_cancel_local(xfer);
1307 return;
1310 fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET);
1313 if (xfer->fd != -1)
1314 xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
1316 xfer->start_time = time(NULL);
1318 if (xfer->ops.start != NULL)
1319 xfer->ops.start(xfer);
1322 static void
1323 connect_cb(gpointer data, gint source, const gchar *error_message)
1325 PurpleXfer *xfer = (PurpleXfer *)data;
1327 if (source < 0) {
1328 purple_xfer_cancel_local(xfer);
1329 return;
1332 xfer->fd = source;
1334 begin_transfer(xfer, PURPLE_INPUT_READ);
1337 void
1338 purple_xfer_ui_ready(PurpleXfer *xfer)
1340 PurpleInputCondition cond;
1341 PurpleXferType type;
1342 PurpleXferPrivData *priv;
1344 g_return_if_fail(xfer != NULL);
1346 priv = g_hash_table_lookup(xfers_data, xfer);
1347 priv->ready |= PURPLE_XFER_READY_UI;
1349 if (0 == (priv->ready & PURPLE_XFER_READY_PRPL)) {
1350 purple_debug_misc("xfer", "UI is ready on ft %p, waiting for prpl\n", xfer);
1351 return;
1354 purple_debug_misc("xfer", "UI (and prpl) ready on ft %p, so proceeding\n", xfer);
1356 type = purple_xfer_get_type(xfer);
1357 if (type == PURPLE_XFER_SEND)
1358 cond = PURPLE_INPUT_WRITE;
1359 else /* if (type == PURPLE_XFER_RECEIVE) */
1360 cond = PURPLE_INPUT_READ;
1362 if (xfer->watcher == 0 && xfer->fd != -1)
1363 xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
1365 priv->ready = PURPLE_XFER_READY_NONE;
1367 do_transfer(xfer);
1370 void
1371 purple_xfer_prpl_ready(PurpleXfer *xfer)
1373 PurpleXferPrivData *priv;
1375 g_return_if_fail(xfer != NULL);
1377 priv = g_hash_table_lookup(xfers_data, xfer);
1378 priv->ready |= PURPLE_XFER_READY_PRPL;
1380 /* I don't think fwrite/fread are ever *not* ready */
1381 if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI)) {
1382 purple_debug_misc("xfer", "prpl is ready on ft %p, waiting for UI\n", xfer);
1383 return;
1386 purple_debug_misc("xfer", "Prpl (and UI) ready on ft %p, so proceeding\n", xfer);
1388 priv->ready = PURPLE_XFER_READY_NONE;
1390 do_transfer(xfer);
1393 void
1394 purple_xfer_start(PurpleXfer *xfer, int fd, const char *ip,
1395 unsigned int port)
1397 PurpleInputCondition cond;
1398 PurpleXferType type;
1400 g_return_if_fail(xfer != NULL);
1401 g_return_if_fail(purple_xfer_get_type(xfer) != PURPLE_XFER_UNKNOWN);
1403 type = purple_xfer_get_type(xfer);
1405 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_STARTED);
1408 * FIXME 3.0.0 -- there's too much broken code depending on fd == 0
1409 * meaning "don't use a real fd"
1411 if (fd == 0)
1412 fd = -1;
1414 if (type == PURPLE_XFER_RECEIVE) {
1415 cond = PURPLE_INPUT_READ;
1417 if (ip != NULL) {
1418 xfer->remote_ip = g_strdup(ip);
1419 xfer->remote_port = port;
1421 /* Establish a file descriptor. */
1422 purple_proxy_connect(NULL, xfer->account, xfer->remote_ip,
1423 xfer->remote_port, connect_cb, xfer);
1425 return;
1427 else {
1428 xfer->fd = fd;
1431 else {
1432 cond = PURPLE_INPUT_WRITE;
1434 xfer->fd = fd;
1437 begin_transfer(xfer, cond);
1440 void
1441 purple_xfer_end(PurpleXfer *xfer)
1443 g_return_if_fail(xfer != NULL);
1445 /* See if we are actually trying to cancel this. */
1446 if (!purple_xfer_is_completed(xfer)) {
1447 purple_xfer_cancel_local(xfer);
1448 return;
1451 xfer->end_time = time(NULL);
1452 if (xfer->ops.end != NULL)
1453 xfer->ops.end(xfer);
1455 if (xfer->watcher != 0) {
1456 purple_input_remove(xfer->watcher);
1457 xfer->watcher = 0;
1460 if (xfer->fd != -1)
1461 close(xfer->fd);
1463 if (xfer->dest_fp != NULL) {
1464 fclose(xfer->dest_fp);
1465 xfer->dest_fp = NULL;
1468 purple_xfer_unref(xfer);
1471 void
1472 purple_xfer_add(PurpleXfer *xfer)
1474 PurpleXferUiOps *ui_ops;
1476 g_return_if_fail(xfer != NULL);
1478 ui_ops = purple_xfer_get_ui_ops(xfer);
1480 if (ui_ops != NULL && ui_ops->add_xfer != NULL)
1481 ui_ops->add_xfer(xfer);
1484 void
1485 purple_xfer_cancel_local(PurpleXfer *xfer)
1487 PurpleXferUiOps *ui_ops;
1488 char *msg = NULL;
1490 g_return_if_fail(xfer != NULL);
1492 /* TODO: We definitely want to close any open request dialogs associated
1493 with this transfer. However, in some cases the request dialog might
1494 own a reference on the xfer. This happens at least with the "%s wants
1495 to send you %s" dialog from purple_xfer_ask_recv(). In these cases
1496 the ref count will not be decremented when the request dialog is
1497 closed, so the ref count will never reach 0 and the xfer will never
1498 be freed. This is a memleak and should be fixed. It's not clear what
1499 the correct fix is. Probably requests should have a destroy function
1500 that is called when the request is destroyed. But also, ref counting
1501 xfer objects makes this code REALLY complicated. An alternate fix is
1502 to not ref count and instead just make sure the object still exists
1503 when we try to use it. */
1504 purple_request_close_with_handle(xfer);
1506 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
1507 xfer->end_time = time(NULL);
1509 if (purple_xfer_get_filename(xfer) != NULL)
1511 msg = g_strdup_printf(_("You cancelled the transfer of %s"),
1512 purple_xfer_get_filename(xfer));
1514 else
1516 msg = g_strdup(_("File transfer cancelled"));
1518 purple_xfer_conversation_write(xfer, msg, FALSE);
1519 g_free(msg);
1521 if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
1523 if (xfer->ops.cancel_send != NULL)
1524 xfer->ops.cancel_send(xfer);
1526 else
1528 if (xfer->ops.cancel_recv != NULL)
1529 xfer->ops.cancel_recv(xfer);
1532 if (xfer->watcher != 0) {
1533 purple_input_remove(xfer->watcher);
1534 xfer->watcher = 0;
1537 if (xfer->fd != -1)
1538 close(xfer->fd);
1540 if (xfer->dest_fp != NULL) {
1541 fclose(xfer->dest_fp);
1542 xfer->dest_fp = NULL;
1545 ui_ops = purple_xfer_get_ui_ops(xfer);
1547 if (ui_ops != NULL && ui_ops->cancel_local != NULL)
1548 ui_ops->cancel_local(xfer);
1550 xfer->bytes_remaining = 0;
1552 purple_xfer_unref(xfer);
1555 void
1556 purple_xfer_cancel_remote(PurpleXfer *xfer)
1558 PurpleXferUiOps *ui_ops;
1559 gchar *msg;
1560 PurpleAccount *account;
1561 PurpleBuddy *buddy;
1563 g_return_if_fail(xfer != NULL);
1565 purple_request_close_with_handle(xfer);
1566 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_REMOTE);
1567 xfer->end_time = time(NULL);
1569 account = purple_xfer_get_account(xfer);
1570 buddy = purple_find_buddy(account, xfer->who);
1572 if (purple_xfer_get_filename(xfer) != NULL)
1574 msg = g_strdup_printf(_("%s cancelled the transfer of %s"),
1575 buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer));
1577 else
1579 msg = g_strdup_printf(_("%s cancelled the file transfer"),
1580 buddy ? purple_buddy_get_alias(buddy) : xfer->who);
1582 purple_xfer_conversation_write(xfer, msg, TRUE);
1583 purple_xfer_error(purple_xfer_get_type(xfer), account, xfer->who, msg);
1584 g_free(msg);
1586 if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
1588 if (xfer->ops.cancel_send != NULL)
1589 xfer->ops.cancel_send(xfer);
1591 else
1593 if (xfer->ops.cancel_recv != NULL)
1594 xfer->ops.cancel_recv(xfer);
1597 if (xfer->watcher != 0) {
1598 purple_input_remove(xfer->watcher);
1599 xfer->watcher = 0;
1602 if (xfer->fd != -1)
1603 close(xfer->fd);
1605 if (xfer->dest_fp != NULL) {
1606 fclose(xfer->dest_fp);
1607 xfer->dest_fp = NULL;
1610 ui_ops = purple_xfer_get_ui_ops(xfer);
1612 if (ui_ops != NULL && ui_ops->cancel_remote != NULL)
1613 ui_ops->cancel_remote(xfer);
1615 xfer->bytes_remaining = 0;
1617 purple_xfer_unref(xfer);
1620 void
1621 purple_xfer_error(PurpleXferType type, PurpleAccount *account, const char *who, const char *msg)
1623 char *title;
1625 g_return_if_fail(msg != NULL);
1626 g_return_if_fail(type != PURPLE_XFER_UNKNOWN);
1628 if (account) {
1629 PurpleBuddy *buddy;
1630 buddy = purple_find_buddy(account, who);
1631 if (buddy)
1632 who = purple_buddy_get_alias(buddy);
1635 if (type == PURPLE_XFER_SEND)
1636 title = g_strdup_printf(_("File transfer to %s failed."), who);
1637 else
1638 title = g_strdup_printf(_("File transfer from %s failed."), who);
1640 purple_notify_error(NULL, NULL, title, msg);
1642 g_free(title);
1645 void
1646 purple_xfer_update_progress(PurpleXfer *xfer)
1648 PurpleXferUiOps *ui_ops;
1650 g_return_if_fail(xfer != NULL);
1652 ui_ops = purple_xfer_get_ui_ops(xfer);
1653 if (ui_ops != NULL && ui_ops->update_progress != NULL)
1654 ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer));
1657 gconstpointer
1658 purple_xfer_get_thumbnail(const PurpleXfer *xfer, gsize *len)
1660 PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1662 if (len)
1663 *len = priv->thumbnail_size;
1665 return priv->thumbnail_data;
1668 const gchar *
1669 purple_xfer_get_thumbnail_mimetype(const PurpleXfer *xfer)
1671 PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1673 return priv->thumbnail_mimetype;
1676 void
1677 purple_xfer_set_thumbnail(PurpleXfer *xfer, gconstpointer thumbnail,
1678 gsize size, const gchar *mimetype)
1680 PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1682 g_free(priv->thumbnail_data);
1683 g_free(priv->thumbnail_mimetype);
1685 if (thumbnail && size > 0) {
1686 priv->thumbnail_data = g_memdup(thumbnail, size);
1687 priv->thumbnail_size = size;
1688 priv->thumbnail_mimetype = g_strdup(mimetype);
1689 } else {
1690 priv->thumbnail_data = NULL;
1691 priv->thumbnail_size = 0;
1692 priv->thumbnail_mimetype = NULL;
1696 void
1697 purple_xfer_prepare_thumbnail(PurpleXfer *xfer, const gchar *formats)
1699 if (xfer->ui_ops->add_thumbnail) {
1700 xfer->ui_ops->add_thumbnail(xfer, formats);
1704 /**************************************************************************
1705 * File Transfer Subsystem API
1706 **************************************************************************/
1707 void *
1708 purple_xfers_get_handle(void) {
1709 static int handle = 0;
1711 return &handle;
1714 void
1715 purple_xfers_init(void) {
1716 void *handle = purple_xfers_get_handle();
1718 xfers_data = g_hash_table_new_full(g_direct_hash, g_direct_equal,
1719 NULL, purple_xfer_priv_data_destroy);
1721 /* register signals */
1722 purple_signal_register(handle, "file-recv-accept",
1723 purple_marshal_VOID__POINTER, NULL, 1,
1724 purple_value_new(PURPLE_TYPE_SUBTYPE,
1725 PURPLE_SUBTYPE_XFER));
1726 purple_signal_register(handle, "file-send-accept",
1727 purple_marshal_VOID__POINTER, NULL, 1,
1728 purple_value_new(PURPLE_TYPE_SUBTYPE,
1729 PURPLE_SUBTYPE_XFER));
1730 purple_signal_register(handle, "file-recv-start",
1731 purple_marshal_VOID__POINTER, NULL, 1,
1732 purple_value_new(PURPLE_TYPE_SUBTYPE,
1733 PURPLE_SUBTYPE_XFER));
1734 purple_signal_register(handle, "file-send-start",
1735 purple_marshal_VOID__POINTER, NULL, 1,
1736 purple_value_new(PURPLE_TYPE_SUBTYPE,
1737 PURPLE_SUBTYPE_XFER));
1738 purple_signal_register(handle, "file-send-cancel",
1739 purple_marshal_VOID__POINTER, NULL, 1,
1740 purple_value_new(PURPLE_TYPE_SUBTYPE,
1741 PURPLE_SUBTYPE_XFER));
1742 purple_signal_register(handle, "file-recv-cancel",
1743 purple_marshal_VOID__POINTER, NULL, 1,
1744 purple_value_new(PURPLE_TYPE_SUBTYPE,
1745 PURPLE_SUBTYPE_XFER));
1746 purple_signal_register(handle, "file-send-complete",
1747 purple_marshal_VOID__POINTER, NULL, 1,
1748 purple_value_new(PURPLE_TYPE_SUBTYPE,
1749 PURPLE_SUBTYPE_XFER));
1750 purple_signal_register(handle, "file-recv-complete",
1751 purple_marshal_VOID__POINTER, NULL, 1,
1752 purple_value_new(PURPLE_TYPE_SUBTYPE,
1753 PURPLE_SUBTYPE_XFER));
1754 purple_signal_register(handle, "file-recv-request",
1755 purple_marshal_VOID__POINTER, NULL, 1,
1756 purple_value_new(PURPLE_TYPE_SUBTYPE,
1757 PURPLE_SUBTYPE_XFER));
1760 void
1761 purple_xfers_uninit(void)
1763 void *handle = purple_xfers_get_handle();
1765 purple_signals_disconnect_by_handle(handle);
1766 purple_signals_unregister_by_instance(handle);
1768 g_hash_table_destroy(xfers_data);
1769 xfers_data = NULL;
1772 void
1773 purple_xfers_set_ui_ops(PurpleXferUiOps *ops) {
1774 xfer_ui_ops = ops;
1777 PurpleXferUiOps *
1778 purple_xfers_get_ui_ops(void) {
1779 return xfer_ui_ops;