merge of 'd03c32ee4bd5625c28af8c8b33920944d499eef6'
[pidgin-git.git] / libpurple / ft.c
blob7df9671d0c22ac2bdf14b26f0e310341eb9ba014
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;
60 GByteArray *buffer;
61 } PurpleXferPrivData;
63 static int purple_xfer_choose_file(PurpleXfer *xfer);
65 static void
66 purple_xfer_priv_data_destroy(gpointer data)
68 PurpleXferPrivData *priv = data;
70 if (priv->buffer)
71 g_byte_array_free(priv->buffer, TRUE);
73 g_free(priv);
76 GList *
77 purple_xfers_get_all()
79 return xfers;
82 PurpleXfer *
83 purple_xfer_new(PurpleAccount *account, PurpleXferType type, const char *who)
85 PurpleXfer *xfer;
86 PurpleXferUiOps *ui_ops;
87 PurpleXferPrivData *priv;
89 g_return_val_if_fail(type != PURPLE_XFER_UNKNOWN, NULL);
90 g_return_val_if_fail(account != NULL, NULL);
91 g_return_val_if_fail(who != NULL, NULL);
93 xfer = g_new0(PurpleXfer, 1);
94 PURPLE_DBUS_REGISTER_POINTER(xfer, PurpleXfer);
96 xfer->ref = 1;
97 xfer->type = type;
98 xfer->account = account;
99 xfer->who = g_strdup(who);
100 xfer->ui_ops = ui_ops = purple_xfers_get_ui_ops();
101 xfer->message = NULL;
102 xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE;
103 xfer->fd = -1;
105 priv = g_new0(PurpleXferPrivData, 1);
106 priv->ready = PURPLE_XFER_READY_NONE;
108 if (ui_ops && ui_ops->data_not_sent) {
109 /* If the ui will handle unsent data no need for buffer */
110 priv->buffer = NULL;
111 } else {
112 priv->buffer = g_byte_array_sized_new(FT_INITIAL_BUFFER_SIZE);
115 g_hash_table_insert(xfers_data, xfer, priv);
117 ui_ops = purple_xfer_get_ui_ops(xfer);
119 if (ui_ops != NULL && ui_ops->new_xfer != NULL)
120 ui_ops->new_xfer(xfer);
122 xfers = g_list_prepend(xfers, xfer);
123 return xfer;
126 static void
127 purple_xfer_destroy(PurpleXfer *xfer)
129 PurpleXferUiOps *ui_ops;
130 PurpleXferPrivData *priv;
132 g_return_if_fail(xfer != NULL);
134 priv = g_hash_table_lookup(xfers_data, xfer);
136 /* Close the file browser, if it's open */
137 purple_request_close_with_handle(xfer);
139 if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_STARTED)
140 purple_xfer_cancel_local(xfer);
142 ui_ops = purple_xfer_get_ui_ops(xfer);
144 if (ui_ops != NULL && ui_ops->destroy != NULL)
145 ui_ops->destroy(xfer);
147 g_free(xfer->who);
148 g_free(xfer->filename);
149 g_free(xfer->remote_ip);
150 g_free(xfer->local_filename);
152 g_hash_table_remove(xfers_data, xfer);
154 PURPLE_DBUS_UNREGISTER_POINTER(xfer);
155 xfers = g_list_remove(xfers, xfer);
156 g_free(xfer);
159 void
160 purple_xfer_ref(PurpleXfer *xfer)
162 g_return_if_fail(xfer != NULL);
164 xfer->ref++;
167 void
168 purple_xfer_unref(PurpleXfer *xfer)
170 g_return_if_fail(xfer != NULL);
171 g_return_if_fail(xfer->ref > 0);
173 xfer->ref--;
175 if (xfer->ref == 0)
176 purple_xfer_destroy(xfer);
179 static void
180 purple_xfer_set_status(PurpleXfer *xfer, PurpleXferStatusType status)
182 g_return_if_fail(xfer != NULL);
184 if (xfer->status == status)
185 return;
187 xfer->status = status;
189 if(xfer->type == PURPLE_XFER_SEND) {
190 switch(status) {
191 case PURPLE_XFER_STATUS_ACCEPTED:
192 purple_signal_emit(purple_xfers_get_handle(), "file-send-accept", xfer);
193 break;
194 case PURPLE_XFER_STATUS_STARTED:
195 purple_signal_emit(purple_xfers_get_handle(), "file-send-start", xfer);
196 break;
197 case PURPLE_XFER_STATUS_DONE:
198 purple_signal_emit(purple_xfers_get_handle(), "file-send-complete", xfer);
199 break;
200 case PURPLE_XFER_STATUS_CANCEL_LOCAL:
201 case PURPLE_XFER_STATUS_CANCEL_REMOTE:
202 purple_signal_emit(purple_xfers_get_handle(), "file-send-cancel", xfer);
203 break;
204 default:
205 break;
207 } else if(xfer->type == PURPLE_XFER_RECEIVE) {
208 switch(status) {
209 case PURPLE_XFER_STATUS_ACCEPTED:
210 purple_signal_emit(purple_xfers_get_handle(), "file-recv-accept", xfer);
211 break;
212 case PURPLE_XFER_STATUS_STARTED:
213 purple_signal_emit(purple_xfers_get_handle(), "file-recv-start", xfer);
214 break;
215 case PURPLE_XFER_STATUS_DONE:
216 purple_signal_emit(purple_xfers_get_handle(), "file-recv-complete", xfer);
217 break;
218 case PURPLE_XFER_STATUS_CANCEL_LOCAL:
219 case PURPLE_XFER_STATUS_CANCEL_REMOTE:
220 purple_signal_emit(purple_xfers_get_handle(), "file-recv-cancel", xfer);
221 break;
222 default:
223 break;
228 void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is_error)
230 PurpleConversation *conv = NULL;
231 PurpleMessageFlags flags = PURPLE_MESSAGE_SYSTEM;
232 char *escaped;
234 g_return_if_fail(xfer != NULL);
235 g_return_if_fail(message != NULL);
237 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, xfer->who,
238 purple_xfer_get_account(xfer));
240 if (conv == NULL)
241 return;
243 escaped = g_markup_escape_text(message, -1);
245 if (is_error)
246 flags = PURPLE_MESSAGE_ERROR;
248 purple_conversation_write(conv, NULL, escaped, flags, time(NULL));
249 g_free(escaped);
252 static void purple_xfer_show_file_error(PurpleXfer *xfer, const char *filename)
254 int err = errno;
255 gchar *msg = NULL, *utf8;
256 PurpleXferType xfer_type = purple_xfer_get_type(xfer);
257 PurpleAccount *account = purple_xfer_get_account(xfer);
259 utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
260 switch(xfer_type) {
261 case PURPLE_XFER_SEND:
262 msg = g_strdup_printf(_("Error reading %s: \n%s.\n"),
263 utf8, g_strerror(err));
264 break;
265 case PURPLE_XFER_RECEIVE:
266 msg = g_strdup_printf(_("Error writing %s: \n%s.\n"),
267 utf8, g_strerror(err));
268 break;
269 default:
270 msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"),
271 utf8, g_strerror(err));
272 break;
274 g_free(utf8);
276 purple_xfer_conversation_write(xfer, msg, TRUE);
277 purple_xfer_error(xfer_type, account, xfer->who, msg);
278 g_free(msg);
281 static void
282 purple_xfer_choose_file_ok_cb(void *user_data, const char *filename)
284 PurpleXfer *xfer;
285 struct stat st;
286 gchar *dir;
288 xfer = (PurpleXfer *)user_data;
290 if (g_stat(filename, &st) != 0) {
291 /* File not found. */
292 if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) {
293 #ifndef _WIN32
294 int mode = W_OK;
295 #else
296 int mode = F_OK;
297 #endif
298 dir = g_path_get_dirname(filename);
300 if (g_access(dir, mode) == 0) {
301 purple_xfer_request_accepted(xfer, filename);
302 } else {
303 purple_xfer_ref(xfer);
304 purple_notify_message(
305 NULL, PURPLE_NOTIFY_MSG_ERROR, NULL,
306 _("Directory is not writable."), NULL,
307 (PurpleNotifyCloseCallback)purple_xfer_choose_file, xfer);
310 g_free(dir);
312 else {
313 purple_xfer_show_file_error(xfer, filename);
314 purple_xfer_request_denied(xfer);
317 else if ((purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) &&
318 (st.st_size == 0)) {
320 purple_notify_error(NULL, NULL,
321 _("Cannot send a file of 0 bytes."), NULL);
323 purple_xfer_request_denied(xfer);
325 else if ((purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) &&
326 S_ISDIR(st.st_mode)) {
328 * XXX - Sending a directory should be valid for some protocols.
330 purple_notify_error(NULL, NULL,
331 _("Cannot send a directory."), NULL);
333 purple_xfer_request_denied(xfer);
335 else if ((purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) &&
336 S_ISDIR(st.st_mode)) {
337 char *msg, *utf8;
338 utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
339 msg = g_strdup_printf(
340 _("%s is not a regular file. Cowardly refusing to overwrite it.\n"), utf8);
341 g_free(utf8);
342 purple_notify_error(NULL, NULL, msg, NULL);
343 g_free(msg);
344 purple_xfer_request_denied(xfer);
346 else {
347 purple_xfer_request_accepted(xfer, filename);
350 purple_xfer_unref(xfer);
353 static void
354 purple_xfer_choose_file_cancel_cb(void *user_data, const char *filename)
356 PurpleXfer *xfer = (PurpleXfer *)user_data;
358 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
359 purple_xfer_request_denied(xfer);
362 static int
363 purple_xfer_choose_file(PurpleXfer *xfer)
365 purple_request_file(xfer, NULL, purple_xfer_get_filename(xfer),
366 (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE),
367 G_CALLBACK(purple_xfer_choose_file_ok_cb),
368 G_CALLBACK(purple_xfer_choose_file_cancel_cb),
369 purple_xfer_get_account(xfer), xfer->who, NULL,
370 xfer);
372 return 0;
375 static int
376 cancel_recv_cb(PurpleXfer *xfer)
378 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
379 purple_xfer_request_denied(xfer);
380 purple_xfer_unref(xfer);
382 return 0;
385 static void
386 purple_xfer_ask_recv(PurpleXfer *xfer)
388 char *buf, *size_buf;
389 size_t size;
391 /* If we have already accepted the request, ask the destination file
392 name directly */
393 if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_ACCEPTED) {
394 PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
396 if (purple_xfer_get_filename(xfer) != NULL)
398 size = purple_xfer_get_size(xfer);
399 size_buf = purple_str_size_to_units(size);
400 buf = g_strdup_printf(_("%s wants to send you %s (%s)"),
401 buddy ? purple_buddy_get_alias(buddy) : xfer->who,
402 purple_xfer_get_filename(xfer), size_buf);
403 g_free(size_buf);
405 else
407 buf = g_strdup_printf(_("%s wants to send you a file"),
408 buddy ? purple_buddy_get_alias(buddy) : xfer->who);
411 if (xfer->message != NULL)
412 serv_got_im(purple_account_get_connection(xfer->account),
413 xfer->who, xfer->message, 0, time(NULL));
415 purple_request_accept_cancel(xfer, NULL, buf, NULL,
416 PURPLE_DEFAULT_ACTION_NONE,
417 xfer->account, xfer->who, NULL,
418 xfer,
419 G_CALLBACK(purple_xfer_choose_file),
420 G_CALLBACK(cancel_recv_cb));
422 g_free(buf);
423 } else
424 purple_xfer_choose_file(xfer);
427 static int
428 ask_accept_ok(PurpleXfer *xfer)
430 purple_xfer_request_accepted(xfer, NULL);
432 return 0;
435 static int
436 ask_accept_cancel(PurpleXfer *xfer)
438 purple_xfer_request_denied(xfer);
439 purple_xfer_unref(xfer);
441 return 0;
444 static void
445 purple_xfer_ask_accept(PurpleXfer *xfer)
447 char *buf, *buf2 = NULL;
448 PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
450 buf = g_strdup_printf(_("Accept file transfer request from %s?"),
451 buddy ? purple_buddy_get_alias(buddy) : xfer->who);
452 if (purple_xfer_get_remote_ip(xfer) &&
453 purple_xfer_get_remote_port(xfer))
454 buf2 = g_strdup_printf(_("A file is available for download from:\n"
455 "Remote host: %s\nRemote port: %d"),
456 purple_xfer_get_remote_ip(xfer),
457 purple_xfer_get_remote_port(xfer));
458 purple_request_accept_cancel(xfer, NULL, buf, buf2,
459 PURPLE_DEFAULT_ACTION_NONE,
460 xfer->account, xfer->who, NULL,
461 xfer,
462 G_CALLBACK(ask_accept_ok),
463 G_CALLBACK(ask_accept_cancel));
464 g_free(buf);
465 g_free(buf2);
468 void
469 purple_xfer_request(PurpleXfer *xfer)
471 g_return_if_fail(xfer != NULL);
472 g_return_if_fail(xfer->ops.init != NULL);
474 purple_xfer_ref(xfer);
476 if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE)
478 purple_signal_emit(purple_xfers_get_handle(), "file-recv-request", xfer);
479 if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL)
481 /* The file-transfer was cancelled by a plugin */
482 purple_xfer_cancel_local(xfer);
484 else if (purple_xfer_get_filename(xfer) ||
485 purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_ACCEPTED)
487 gchar* message = NULL;
488 PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who);
489 message = g_strdup_printf(_("%s is offering to send file %s"),
490 buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer));
491 purple_xfer_conversation_write(xfer, message, FALSE);
492 g_free(message);
493 /* Ask for a filename to save to if it's not already given by a plugin */
494 if (xfer->local_filename == NULL)
495 purple_xfer_ask_recv(xfer);
497 else
499 purple_xfer_ask_accept(xfer);
502 else
504 purple_xfer_choose_file(xfer);
508 void
509 purple_xfer_request_accepted(PurpleXfer *xfer, const char *filename)
511 PurpleXferType type;
512 struct stat st;
513 char *msg, *utf8, *base;
514 PurpleAccount *account;
515 PurpleBuddy *buddy;
517 if (xfer == NULL)
518 return;
520 type = purple_xfer_get_type(xfer);
521 account = purple_xfer_get_account(xfer);
523 if (!filename && type == PURPLE_XFER_RECEIVE) {
524 xfer->status = PURPLE_XFER_STATUS_ACCEPTED;
525 xfer->ops.init(xfer);
526 return;
529 buddy = purple_find_buddy(account, xfer->who);
531 if (type == PURPLE_XFER_SEND) {
532 /* Sending a file */
533 /* Check the filename. */
534 PurpleXferUiOps *ui_ops;
535 ui_ops = purple_xfer_get_ui_ops(xfer);
537 #ifdef _WIN32
538 if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\"))
539 #else
540 if (g_strrstr(filename, "../"))
541 #endif
543 utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
545 msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8);
546 purple_xfer_error(type, account, xfer->who, msg);
547 g_free(utf8);
548 g_free(msg);
550 purple_xfer_unref(xfer);
551 return;
554 if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
555 if (g_stat(filename, &st) == -1) {
556 purple_xfer_show_file_error(xfer, filename);
557 purple_xfer_unref(xfer);
558 return;
561 purple_xfer_set_local_filename(xfer, filename);
562 purple_xfer_set_size(xfer, st.st_size);
563 } else {
564 utf8 = g_strdup(filename);
565 purple_xfer_set_local_filename(xfer, filename);
568 base = g_path_get_basename(filename);
569 utf8 = g_filename_to_utf8(base, -1, NULL, NULL, NULL);
570 g_free(base);
571 purple_xfer_set_filename(xfer, utf8);
573 msg = g_strdup_printf(_("Offering to send %s to %s"),
574 utf8, buddy ? purple_buddy_get_alias(buddy) : xfer->who);
575 g_free(utf8);
576 purple_xfer_conversation_write(xfer, msg, FALSE);
577 g_free(msg);
579 else {
580 /* Receiving a file */
581 xfer->status = PURPLE_XFER_STATUS_ACCEPTED;
582 purple_xfer_set_local_filename(xfer, filename);
584 msg = g_strdup_printf(_("Starting transfer of %s from %s"),
585 xfer->filename, buddy ? purple_buddy_get_alias(buddy) : xfer->who);
586 purple_xfer_conversation_write(xfer, msg, FALSE);
587 g_free(msg);
590 purple_xfer_add(xfer);
591 xfer->ops.init(xfer);
595 void
596 purple_xfer_request_denied(PurpleXfer *xfer)
598 g_return_if_fail(xfer != NULL);
600 if (xfer->ops.request_denied != NULL)
601 xfer->ops.request_denied(xfer);
603 purple_xfer_unref(xfer);
606 PurpleXferType
607 purple_xfer_get_type(const PurpleXfer *xfer)
609 g_return_val_if_fail(xfer != NULL, PURPLE_XFER_UNKNOWN);
611 return xfer->type;
614 PurpleAccount *
615 purple_xfer_get_account(const PurpleXfer *xfer)
617 g_return_val_if_fail(xfer != NULL, NULL);
619 return xfer->account;
622 const char *
623 purple_xfer_get_remote_user(const PurpleXfer *xfer)
625 g_return_val_if_fail(xfer != NULL, NULL);
626 return xfer->who;
629 PurpleXferStatusType
630 purple_xfer_get_status(const PurpleXfer *xfer)
632 g_return_val_if_fail(xfer != NULL, PURPLE_XFER_STATUS_UNKNOWN);
634 return xfer->status;
637 gboolean
638 purple_xfer_is_canceled(const PurpleXfer *xfer)
640 g_return_val_if_fail(xfer != NULL, TRUE);
642 if ((purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) ||
643 (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_REMOTE))
644 return TRUE;
645 else
646 return FALSE;
649 gboolean
650 purple_xfer_is_completed(const PurpleXfer *xfer)
652 g_return_val_if_fail(xfer != NULL, TRUE);
654 return (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_DONE);
657 const char *
658 purple_xfer_get_filename(const PurpleXfer *xfer)
660 g_return_val_if_fail(xfer != NULL, NULL);
662 return xfer->filename;
665 const char *
666 purple_xfer_get_local_filename(const PurpleXfer *xfer)
668 g_return_val_if_fail(xfer != NULL, NULL);
670 return xfer->local_filename;
673 size_t
674 purple_xfer_get_bytes_sent(const PurpleXfer *xfer)
676 g_return_val_if_fail(xfer != NULL, 0);
678 return xfer->bytes_sent;
681 size_t
682 purple_xfer_get_bytes_remaining(const PurpleXfer *xfer)
684 g_return_val_if_fail(xfer != NULL, 0);
686 return xfer->bytes_remaining;
689 size_t
690 purple_xfer_get_size(const PurpleXfer *xfer)
692 g_return_val_if_fail(xfer != NULL, 0);
694 return xfer->size;
697 double
698 purple_xfer_get_progress(const PurpleXfer *xfer)
700 g_return_val_if_fail(xfer != NULL, 0.0);
702 if (purple_xfer_get_size(xfer) == 0)
703 return 0.0;
705 return ((double)purple_xfer_get_bytes_sent(xfer) /
706 (double)purple_xfer_get_size(xfer));
709 unsigned int
710 purple_xfer_get_local_port(const PurpleXfer *xfer)
712 g_return_val_if_fail(xfer != NULL, -1);
714 return xfer->local_port;
717 const char *
718 purple_xfer_get_remote_ip(const PurpleXfer *xfer)
720 g_return_val_if_fail(xfer != NULL, NULL);
722 return xfer->remote_ip;
725 unsigned int
726 purple_xfer_get_remote_port(const PurpleXfer *xfer)
728 g_return_val_if_fail(xfer != NULL, -1);
730 return xfer->remote_port;
733 time_t
734 purple_xfer_get_start_time(const PurpleXfer *xfer)
736 g_return_val_if_fail(xfer != NULL, 0);
738 return xfer->start_time;
741 time_t
742 purple_xfer_get_end_time(const PurpleXfer *xfer)
744 g_return_val_if_fail(xfer != NULL, 0);
746 return xfer->end_time;
749 void
750 purple_xfer_set_completed(PurpleXfer *xfer, gboolean completed)
752 PurpleXferUiOps *ui_ops;
754 g_return_if_fail(xfer != NULL);
756 if (completed == TRUE) {
757 char *msg = NULL;
758 PurpleConversation *conv;
760 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_DONE);
762 if (purple_xfer_get_filename(xfer) != NULL)
764 char *filename = g_markup_escape_text(purple_xfer_get_filename(xfer), -1);
765 if (purple_xfer_get_local_filename(xfer)
766 && purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE)
768 char *local = g_markup_escape_text(purple_xfer_get_local_filename(xfer), -1);
769 msg = g_strdup_printf(_("Transfer of file <A HREF=\"file://%s\">%s</A> complete"),
770 local, filename);
771 g_free(local);
773 else
774 msg = g_strdup_printf(_("Transfer of file %s complete"),
775 filename);
776 g_free(filename);
778 else
779 msg = g_strdup(_("File transfer complete"));
781 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, xfer->who,
782 purple_xfer_get_account(xfer));
784 if (conv != NULL)
785 purple_conversation_write(conv, NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL));
786 g_free(msg);
789 ui_ops = purple_xfer_get_ui_ops(xfer);
791 if (ui_ops != NULL && ui_ops->update_progress != NULL)
792 ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer));
795 void
796 purple_xfer_set_message(PurpleXfer *xfer, const char *message)
798 g_return_if_fail(xfer != NULL);
800 g_free(xfer->message);
801 xfer->message = g_strdup(message);
804 void
805 purple_xfer_set_filename(PurpleXfer *xfer, const char *filename)
807 g_return_if_fail(xfer != NULL);
809 g_free(xfer->filename);
810 xfer->filename = g_strdup(filename);
813 void
814 purple_xfer_set_local_filename(PurpleXfer *xfer, const char *filename)
816 g_return_if_fail(xfer != NULL);
818 g_free(xfer->local_filename);
819 xfer->local_filename = g_strdup(filename);
822 void
823 purple_xfer_set_size(PurpleXfer *xfer, size_t size)
825 g_return_if_fail(xfer != NULL);
827 xfer->size = size;
828 xfer->bytes_remaining = xfer->size - purple_xfer_get_bytes_sent(xfer);
831 void
832 purple_xfer_set_bytes_sent(PurpleXfer *xfer, size_t bytes_sent)
834 g_return_if_fail(xfer != NULL);
836 xfer->bytes_sent = bytes_sent;
837 xfer->bytes_remaining = purple_xfer_get_size(xfer) - bytes_sent;
840 PurpleXferUiOps *
841 purple_xfer_get_ui_ops(const PurpleXfer *xfer)
843 g_return_val_if_fail(xfer != NULL, NULL);
845 return xfer->ui_ops;
848 void
849 purple_xfer_set_init_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
851 g_return_if_fail(xfer != NULL);
853 xfer->ops.init = fnc;
856 void purple_xfer_set_request_denied_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
858 g_return_if_fail(xfer != NULL);
860 xfer->ops.request_denied = fnc;
863 void
864 purple_xfer_set_read_fnc(PurpleXfer *xfer, gssize (*fnc)(guchar **, PurpleXfer *))
866 g_return_if_fail(xfer != NULL);
868 xfer->ops.read = fnc;
871 void
872 purple_xfer_set_write_fnc(PurpleXfer *xfer,
873 gssize (*fnc)(const guchar *, size_t, PurpleXfer *))
875 g_return_if_fail(xfer != NULL);
877 xfer->ops.write = fnc;
880 void
881 purple_xfer_set_ack_fnc(PurpleXfer *xfer,
882 void (*fnc)(PurpleXfer *, const guchar *, size_t))
884 g_return_if_fail(xfer != NULL);
886 xfer->ops.ack = fnc;
889 void
890 purple_xfer_set_start_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
892 g_return_if_fail(xfer != NULL);
894 xfer->ops.start = fnc;
897 void
898 purple_xfer_set_end_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
900 g_return_if_fail(xfer != NULL);
902 xfer->ops.end = fnc;
905 void
906 purple_xfer_set_cancel_send_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
908 g_return_if_fail(xfer != NULL);
910 xfer->ops.cancel_send = fnc;
913 void
914 purple_xfer_set_cancel_recv_fnc(PurpleXfer *xfer, void (*fnc)(PurpleXfer *))
916 g_return_if_fail(xfer != NULL);
918 xfer->ops.cancel_recv = fnc;
921 static void
922 purple_xfer_increase_buffer_size(PurpleXfer *xfer)
924 xfer->current_buffer_size = MIN(xfer->current_buffer_size * 1.5,
925 FT_MAX_BUFFER_SIZE);
928 gssize
929 purple_xfer_read(PurpleXfer *xfer, guchar **buffer)
931 gssize s, r;
933 g_return_val_if_fail(xfer != NULL, 0);
934 g_return_val_if_fail(buffer != NULL, 0);
936 if (purple_xfer_get_size(xfer) == 0)
937 s = xfer->current_buffer_size;
938 else
939 s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
941 if (xfer->ops.read != NULL) {
942 r = (xfer->ops.read)(buffer, xfer);
944 else {
945 *buffer = g_malloc0(s);
947 r = read(xfer->fd, *buffer, s);
948 if (r < 0 && errno == EAGAIN)
949 r = 0;
950 else if (r < 0)
951 r = -1;
952 else if (r == 0)
953 r = -1;
956 if (r == xfer->current_buffer_size)
958 * We managed to read the entire buffer. This means our this
959 * network is fast and our buffer is too small, so make it
960 * bigger.
962 purple_xfer_increase_buffer_size(xfer);
964 return r;
967 gssize
968 purple_xfer_write(PurpleXfer *xfer, const guchar *buffer, gsize size)
970 gssize r, s;
972 g_return_val_if_fail(xfer != NULL, 0);
973 g_return_val_if_fail(buffer != NULL, 0);
974 g_return_val_if_fail(size != 0, 0);
976 s = MIN(purple_xfer_get_bytes_remaining(xfer), size);
978 if (xfer->ops.write != NULL) {
979 r = (xfer->ops.write)(buffer, s, xfer);
980 } else {
981 r = write(xfer->fd, buffer, s);
982 if (r < 0 && errno == EAGAIN)
983 r = 0;
986 return r;
989 static void
990 do_transfer(PurpleXfer *xfer)
992 PurpleXferUiOps *ui_ops;
993 guchar *buffer = NULL;
994 gssize r = 0;
996 ui_ops = purple_xfer_get_ui_ops(xfer);
998 if (xfer->type == PURPLE_XFER_RECEIVE) {
999 r = purple_xfer_read(xfer, &buffer);
1000 if (r > 0) {
1001 size_t wc;
1002 if (ui_ops && ui_ops->ui_write)
1003 wc = ui_ops->ui_write(xfer, buffer, r);
1004 else
1005 wc = fwrite(buffer, 1, r, xfer->dest_fp);
1007 if (wc != r) {
1008 purple_debug_error("filetransfer", "Unable to write whole buffer.\n");
1009 purple_xfer_cancel_local(xfer);
1010 g_free(buffer);
1011 return;
1014 } else if(r < 0) {
1015 purple_xfer_cancel_remote(xfer);
1016 g_free(buffer);
1017 return;
1019 } else if (xfer->type == PURPLE_XFER_SEND) {
1020 size_t result;
1021 size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
1022 PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1024 /* this is so the prpl can keep the connection open
1025 if it needs to for some odd reason. */
1026 if (s == 0) {
1027 if (xfer->watcher) {
1028 purple_input_remove(xfer->watcher);
1029 xfer->watcher = 0;
1031 return;
1034 if (priv->buffer) {
1035 s = s - priv->buffer->len;
1038 if (ui_ops && ui_ops->ui_read) {
1039 gssize tmp = ui_ops->ui_read(xfer, &buffer, s);
1040 if (tmp == 0) {
1042 * UI isn't ready to send data. It will call
1043 * purple_xfer_ui_ready when ready, which sets back up this
1044 * watcher.
1046 if (xfer->watcher != 0) {
1047 purple_input_remove(xfer->watcher);
1048 xfer->watcher = 0;
1051 * if we requested 0 bytes it's only normal that en up here
1052 * we shouldn't return as we still have something to
1053 * write in priv->buffer
1055 if (s != 0)
1056 return;
1057 } else if (tmp < 0) {
1058 purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
1059 purple_xfer_cancel_local(xfer);
1060 return;
1063 result = tmp;
1064 } else {
1065 buffer = g_malloc0(s);
1066 result = fread(buffer, 1, s, xfer->dest_fp);
1067 if (result != s) {
1068 purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
1069 purple_xfer_cancel_local(xfer);
1070 g_free(buffer);
1071 return;
1075 if (priv->buffer) {
1076 priv->buffer = g_byte_array_append(priv->buffer, buffer, result);
1077 g_free(buffer);
1078 buffer = priv->buffer->data;
1079 result = priv->buffer->len;
1082 r = purple_xfer_write(xfer, buffer, result);
1084 if (r == -1) {
1085 purple_xfer_cancel_remote(xfer);
1086 g_free(buffer);
1087 return;
1088 } else if (r == result) {
1090 * We managed to write the entire buffer. This means our
1091 * network is fast and our buffer is too small, so make it
1092 * bigger.
1094 purple_xfer_increase_buffer_size(xfer);
1095 } else {
1096 if (ui_ops && ui_ops->data_not_sent)
1097 ui_ops->data_not_sent(xfer, buffer + r, result -r);
1100 if (priv->buffer) {
1102 * Remove what we wrote
1103 * If we wrote the whole buffer the byte array will be empty
1104 * Otherwise we'll kee what wasn't sent for next time.
1106 buffer = NULL;
1107 priv->buffer = g_byte_array_remove_range(priv->buffer, 0, r);
1111 if (r > 0) {
1112 if (purple_xfer_get_size(xfer) > 0)
1113 xfer->bytes_remaining -= r;
1115 xfer->bytes_sent += r;
1117 if (xfer->ops.ack != NULL)
1118 xfer->ops.ack(xfer, buffer, r);
1120 g_free(buffer);
1122 if (ui_ops != NULL && ui_ops->update_progress != NULL)
1123 ui_ops->update_progress(xfer,
1124 purple_xfer_get_progress(xfer));
1127 if ((purple_xfer_get_size(xfer) > 0) &&
1128 ((purple_xfer_get_bytes_sent(xfer)) >= purple_xfer_get_size(xfer))) {
1129 purple_xfer_set_completed(xfer, TRUE);
1130 purple_xfer_end(xfer);
1134 static void
1135 transfer_cb(gpointer data, gint source, PurpleInputCondition condition)
1137 PurpleXfer *xfer = data;
1139 if (xfer->dest_fp == NULL) {
1140 /* The UI is moderating its side manually */
1141 PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
1142 if (0 == (priv->ready & PURPLE_XFER_READY_UI)) {
1143 priv->ready |= PURPLE_XFER_READY_PRPL;
1145 purple_input_remove(xfer->watcher);
1146 xfer->watcher = 0;
1147 return;
1151 do_transfer(xfer);
1154 static void
1155 begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond)
1157 PurpleXferType type = purple_xfer_get_type(xfer);
1158 PurpleXferUiOps *ui_ops = purple_xfer_get_ui_ops(xfer);
1160 if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
1161 xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer),
1162 type == PURPLE_XFER_RECEIVE ? "wb" : "rb");
1164 if (xfer->dest_fp == NULL) {
1165 purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer));
1166 purple_xfer_cancel_local(xfer);
1167 return;
1170 fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET);
1173 if (xfer->fd != -1)
1174 xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
1176 xfer->start_time = time(NULL);
1178 if (xfer->ops.start != NULL)
1179 xfer->ops.start(xfer);
1182 static void
1183 connect_cb(gpointer data, gint source, const gchar *error_message)
1185 PurpleXfer *xfer = (PurpleXfer *)data;
1187 if (source < 0) {
1188 purple_xfer_cancel_local(xfer);
1189 return;
1192 xfer->fd = source;
1194 begin_transfer(xfer, PURPLE_INPUT_READ);
1197 void
1198 purple_xfer_ui_ready(PurpleXfer *xfer)
1200 PurpleInputCondition cond;
1201 PurpleXferType type;
1202 PurpleXferPrivData *priv;
1204 g_return_if_fail(xfer != NULL);
1206 priv = g_hash_table_lookup(xfers_data, xfer);
1207 priv->ready |= PURPLE_XFER_READY_UI;
1209 if (0 == (priv->ready & PURPLE_XFER_READY_PRPL))
1210 return;
1212 type = purple_xfer_get_type(xfer);
1213 if (type == PURPLE_XFER_SEND)
1214 cond = PURPLE_INPUT_WRITE;
1215 else /* if (type == PURPLE_XFER_RECEIVE) */
1216 cond = PURPLE_INPUT_READ;
1218 if (xfer->watcher == 0 && xfer->fd != -1)
1219 xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
1221 priv->ready = PURPLE_XFER_READY_NONE;
1223 do_transfer(xfer);
1226 void
1227 purple_xfer_prpl_ready(PurpleXfer *xfer)
1229 PurpleXferPrivData *priv;
1231 g_return_if_fail(xfer != NULL);
1233 priv = g_hash_table_lookup(xfers_data, xfer);
1234 priv->ready |= PURPLE_XFER_READY_PRPL;
1236 /* I don't think fwrite/fread are ever *not* ready */
1237 if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI))
1238 return;
1240 priv->ready = PURPLE_XFER_READY_NONE;
1242 do_transfer(xfer);
1245 void
1246 purple_xfer_start(PurpleXfer *xfer, int fd, const char *ip,
1247 unsigned int port)
1249 PurpleInputCondition cond;
1250 PurpleXferType type;
1252 g_return_if_fail(xfer != NULL);
1253 g_return_if_fail(purple_xfer_get_type(xfer) != PURPLE_XFER_UNKNOWN);
1255 type = purple_xfer_get_type(xfer);
1257 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_STARTED);
1260 * FIXME 3.0.0 -- there's too much broken code depending on fd == 0
1261 * meaning "don't use a real fd"
1263 if (fd == 0)
1264 fd = -1;
1266 if (type == PURPLE_XFER_RECEIVE) {
1267 cond = PURPLE_INPUT_READ;
1269 if (ip != NULL) {
1270 xfer->remote_ip = g_strdup(ip);
1271 xfer->remote_port = port;
1273 /* Establish a file descriptor. */
1274 purple_proxy_connect(NULL, xfer->account, xfer->remote_ip,
1275 xfer->remote_port, connect_cb, xfer);
1277 return;
1279 else {
1280 xfer->fd = fd;
1283 else {
1284 cond = PURPLE_INPUT_WRITE;
1286 xfer->fd = fd;
1289 begin_transfer(xfer, cond);
1292 void
1293 purple_xfer_end(PurpleXfer *xfer)
1295 g_return_if_fail(xfer != NULL);
1297 /* See if we are actually trying to cancel this. */
1298 if (!purple_xfer_is_completed(xfer)) {
1299 purple_xfer_cancel_local(xfer);
1300 return;
1303 xfer->end_time = time(NULL);
1304 if (xfer->ops.end != NULL)
1305 xfer->ops.end(xfer);
1307 if (xfer->watcher != 0) {
1308 purple_input_remove(xfer->watcher);
1309 xfer->watcher = 0;
1312 if (xfer->fd != -1)
1313 close(xfer->fd);
1315 if (xfer->dest_fp != NULL) {
1316 fclose(xfer->dest_fp);
1317 xfer->dest_fp = NULL;
1320 purple_xfer_unref(xfer);
1323 void
1324 purple_xfer_add(PurpleXfer *xfer)
1326 PurpleXferUiOps *ui_ops;
1328 g_return_if_fail(xfer != NULL);
1330 ui_ops = purple_xfer_get_ui_ops(xfer);
1332 if (ui_ops != NULL && ui_ops->add_xfer != NULL)
1333 ui_ops->add_xfer(xfer);
1336 void
1337 purple_xfer_cancel_local(PurpleXfer *xfer)
1339 PurpleXferUiOps *ui_ops;
1340 char *msg = NULL;
1342 g_return_if_fail(xfer != NULL);
1344 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
1345 xfer->end_time = time(NULL);
1347 if (purple_xfer_get_filename(xfer) != NULL)
1349 msg = g_strdup_printf(_("You cancelled the transfer of %s"),
1350 purple_xfer_get_filename(xfer));
1352 else
1354 msg = g_strdup(_("File transfer cancelled"));
1356 purple_xfer_conversation_write(xfer, msg, FALSE);
1357 g_free(msg);
1359 if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
1361 if (xfer->ops.cancel_send != NULL)
1362 xfer->ops.cancel_send(xfer);
1364 else
1366 if (xfer->ops.cancel_recv != NULL)
1367 xfer->ops.cancel_recv(xfer);
1370 if (xfer->watcher != 0) {
1371 purple_input_remove(xfer->watcher);
1372 xfer->watcher = 0;
1375 if (xfer->fd != -1)
1376 close(xfer->fd);
1378 if (xfer->dest_fp != NULL) {
1379 fclose(xfer->dest_fp);
1380 xfer->dest_fp = NULL;
1383 ui_ops = purple_xfer_get_ui_ops(xfer);
1385 if (ui_ops != NULL && ui_ops->cancel_local != NULL)
1386 ui_ops->cancel_local(xfer);
1388 xfer->bytes_remaining = 0;
1390 purple_xfer_unref(xfer);
1393 void
1394 purple_xfer_cancel_remote(PurpleXfer *xfer)
1396 PurpleXferUiOps *ui_ops;
1397 gchar *msg;
1398 PurpleAccount *account;
1399 PurpleBuddy *buddy;
1401 g_return_if_fail(xfer != NULL);
1403 purple_request_close_with_handle(xfer);
1404 purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_REMOTE);
1405 xfer->end_time = time(NULL);
1407 account = purple_xfer_get_account(xfer);
1408 buddy = purple_find_buddy(account, xfer->who);
1410 if (purple_xfer_get_filename(xfer) != NULL)
1412 msg = g_strdup_printf(_("%s cancelled the transfer of %s"),
1413 buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer));
1415 else
1417 msg = g_strdup_printf(_("%s cancelled the file transfer"),
1418 buddy ? purple_buddy_get_alias(buddy) : xfer->who);
1420 purple_xfer_conversation_write(xfer, msg, TRUE);
1421 purple_xfer_error(purple_xfer_get_type(xfer), account, xfer->who, msg);
1422 g_free(msg);
1424 if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
1426 if (xfer->ops.cancel_send != NULL)
1427 xfer->ops.cancel_send(xfer);
1429 else
1431 if (xfer->ops.cancel_recv != NULL)
1432 xfer->ops.cancel_recv(xfer);
1435 if (xfer->watcher != 0) {
1436 purple_input_remove(xfer->watcher);
1437 xfer->watcher = 0;
1440 if (xfer->fd != -1)
1441 close(xfer->fd);
1443 if (xfer->dest_fp != NULL) {
1444 fclose(xfer->dest_fp);
1445 xfer->dest_fp = NULL;
1448 ui_ops = purple_xfer_get_ui_ops(xfer);
1450 if (ui_ops != NULL && ui_ops->cancel_remote != NULL)
1451 ui_ops->cancel_remote(xfer);
1453 xfer->bytes_remaining = 0;
1455 purple_xfer_unref(xfer);
1458 void
1459 purple_xfer_error(PurpleXferType type, PurpleAccount *account, const char *who, const char *msg)
1461 char *title;
1463 g_return_if_fail(msg != NULL);
1464 g_return_if_fail(type != PURPLE_XFER_UNKNOWN);
1466 if (account) {
1467 PurpleBuddy *buddy;
1468 buddy = purple_find_buddy(account, who);
1469 if (buddy)
1470 who = purple_buddy_get_alias(buddy);
1473 if (type == PURPLE_XFER_SEND)
1474 title = g_strdup_printf(_("File transfer to %s failed."), who);
1475 else
1476 title = g_strdup_printf(_("File transfer from %s failed."), who);
1478 purple_notify_error(NULL, NULL, title, msg);
1480 g_free(title);
1483 void
1484 purple_xfer_update_progress(PurpleXfer *xfer)
1486 PurpleXferUiOps *ui_ops;
1488 g_return_if_fail(xfer != NULL);
1490 ui_ops = purple_xfer_get_ui_ops(xfer);
1491 if (ui_ops != NULL && ui_ops->update_progress != NULL)
1492 ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer));
1496 /**************************************************************************
1497 * File Transfer Subsystem API
1498 **************************************************************************/
1499 void *
1500 purple_xfers_get_handle(void) {
1501 static int handle = 0;
1503 return &handle;
1506 void
1507 purple_xfers_init(void) {
1508 void *handle = purple_xfers_get_handle();
1510 xfers_data = g_hash_table_new_full(g_direct_hash, g_direct_equal,
1511 NULL, purple_xfer_priv_data_destroy);
1513 /* register signals */
1514 purple_signal_register(handle, "file-recv-accept",
1515 purple_marshal_VOID__POINTER, NULL, 1,
1516 purple_value_new(PURPLE_TYPE_SUBTYPE,
1517 PURPLE_SUBTYPE_XFER));
1518 purple_signal_register(handle, "file-send-accept",
1519 purple_marshal_VOID__POINTER, NULL, 1,
1520 purple_value_new(PURPLE_TYPE_SUBTYPE,
1521 PURPLE_SUBTYPE_XFER));
1522 purple_signal_register(handle, "file-recv-start",
1523 purple_marshal_VOID__POINTER, NULL, 1,
1524 purple_value_new(PURPLE_TYPE_SUBTYPE,
1525 PURPLE_SUBTYPE_XFER));
1526 purple_signal_register(handle, "file-send-start",
1527 purple_marshal_VOID__POINTER, NULL, 1,
1528 purple_value_new(PURPLE_TYPE_SUBTYPE,
1529 PURPLE_SUBTYPE_XFER));
1530 purple_signal_register(handle, "file-send-cancel",
1531 purple_marshal_VOID__POINTER, NULL, 1,
1532 purple_value_new(PURPLE_TYPE_SUBTYPE,
1533 PURPLE_SUBTYPE_XFER));
1534 purple_signal_register(handle, "file-recv-cancel",
1535 purple_marshal_VOID__POINTER, NULL, 1,
1536 purple_value_new(PURPLE_TYPE_SUBTYPE,
1537 PURPLE_SUBTYPE_XFER));
1538 purple_signal_register(handle, "file-send-complete",
1539 purple_marshal_VOID__POINTER, NULL, 1,
1540 purple_value_new(PURPLE_TYPE_SUBTYPE,
1541 PURPLE_SUBTYPE_XFER));
1542 purple_signal_register(handle, "file-recv-complete",
1543 purple_marshal_VOID__POINTER, NULL, 1,
1544 purple_value_new(PURPLE_TYPE_SUBTYPE,
1545 PURPLE_SUBTYPE_XFER));
1546 purple_signal_register(handle, "file-recv-request",
1547 purple_marshal_VOID__POINTER, NULL, 1,
1548 purple_value_new(PURPLE_TYPE_SUBTYPE,
1549 PURPLE_SUBTYPE_XFER));
1552 void
1553 purple_xfers_uninit(void)
1555 void *handle = purple_xfers_get_handle();
1557 purple_signals_disconnect_by_handle(handle);
1558 purple_signals_unregister_by_instance(handle);
1560 g_hash_table_destroy(xfers_data);
1561 xfers_data = NULL;
1564 void
1565 purple_xfers_set_ui_ops(PurpleXferUiOps *ops) {
1566 xfer_ui_ops = ops;
1569 PurpleXferUiOps *
1570 purple_xfers_get_ui_ops(void) {
1571 return xfer_ui_ops;