comparison libpurple/ft.c @ 15373:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 35fa1d1c4ef2
comparison
equal deleted inserted replaced
15372:f79e0f4df793 15373:5fe8042783c1
1 /**
2 * @file ft.c File Transfer API
3 *
4 * gaim
5 *
6 * Gaim is the legal property of its developers, whose names are too numerous
7 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * source distribution.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 */
25 #include "internal.h"
26 #include "ft.h"
27 #include "network.h"
28 #include "notify.h"
29 #include "prefs.h"
30 #include "proxy.h"
31 #include "request.h"
32 #include "util.h"
33
34 #define FT_INITIAL_BUFFER_SIZE 4096
35 #define FT_MAX_BUFFER_SIZE 65535
36
37 static GaimXferUiOps *xfer_ui_ops = NULL;
38
39 static int gaim_xfer_choose_file(GaimXfer *xfer);
40
41 GaimXfer *
42 gaim_xfer_new(GaimAccount *account, GaimXferType type, const char *who)
43 {
44 GaimXfer *xfer;
45 GaimXferUiOps *ui_ops;
46
47 g_return_val_if_fail(type != GAIM_XFER_UNKNOWN, NULL);
48 g_return_val_if_fail(account != NULL, NULL);
49 g_return_val_if_fail(who != NULL, NULL);
50
51 xfer = g_new0(GaimXfer, 1);
52
53 xfer->ref = 1;
54 xfer->type = type;
55 xfer->account = account;
56 xfer->who = g_strdup(who);
57 xfer->ui_ops = gaim_xfers_get_ui_ops();
58 xfer->message = NULL;
59 xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE;
60
61 ui_ops = gaim_xfer_get_ui_ops(xfer);
62
63 if (ui_ops != NULL && ui_ops->new_xfer != NULL)
64 ui_ops->new_xfer(xfer);
65
66 return xfer;
67 }
68
69 static void
70 gaim_xfer_destroy(GaimXfer *xfer)
71 {
72 GaimXferUiOps *ui_ops;
73
74 g_return_if_fail(xfer != NULL);
75
76 /* Close the file browser, if it's open */
77 gaim_request_close_with_handle(xfer);
78
79 if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_STARTED)
80 gaim_xfer_cancel_local(xfer);
81
82 ui_ops = gaim_xfer_get_ui_ops(xfer);
83
84 if (ui_ops != NULL && ui_ops->destroy != NULL)
85 ui_ops->destroy(xfer);
86
87 g_free(xfer->who);
88 g_free(xfer->filename);
89 g_free(xfer->remote_ip);
90 g_free(xfer->local_filename);
91
92 g_free(xfer);
93 }
94
95 void
96 gaim_xfer_ref(GaimXfer *xfer)
97 {
98 g_return_if_fail(xfer != NULL);
99
100 xfer->ref++;
101 }
102
103 void
104 gaim_xfer_unref(GaimXfer *xfer)
105 {
106 g_return_if_fail(xfer != NULL);
107 g_return_if_fail(xfer->ref > 0);
108
109 xfer->ref--;
110
111 if (xfer->ref == 0)
112 gaim_xfer_destroy(xfer);
113 }
114
115 static void
116 gaim_xfer_set_status(GaimXfer *xfer, GaimXferStatusType status)
117 {
118 g_return_if_fail(xfer != NULL);
119
120 if(xfer->type == GAIM_XFER_SEND) {
121 switch(status) {
122 case GAIM_XFER_STATUS_ACCEPTED:
123 gaim_signal_emit(gaim_xfers_get_handle(), "file-send-accept", xfer);
124 break;
125 case GAIM_XFER_STATUS_STARTED:
126 gaim_signal_emit(gaim_xfers_get_handle(), "file-send-start", xfer);
127 break;
128 case GAIM_XFER_STATUS_DONE:
129 gaim_signal_emit(gaim_xfers_get_handle(), "file-send-complete", xfer);
130 break;
131 case GAIM_XFER_STATUS_CANCEL_LOCAL:
132 case GAIM_XFER_STATUS_CANCEL_REMOTE:
133 gaim_signal_emit(gaim_xfers_get_handle(), "file-send-cancel", xfer);
134 break;
135 default:
136 break;
137 }
138 } else if(xfer->type == GAIM_XFER_RECEIVE) {
139 switch(status) {
140 case GAIM_XFER_STATUS_ACCEPTED:
141 gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-accept", xfer);
142 break;
143 case GAIM_XFER_STATUS_STARTED:
144 gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-start", xfer);
145 break;
146 case GAIM_XFER_STATUS_DONE:
147 gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-complete", xfer);
148 break;
149 case GAIM_XFER_STATUS_CANCEL_LOCAL:
150 case GAIM_XFER_STATUS_CANCEL_REMOTE:
151 gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-cancel", xfer);
152 break;
153 default:
154 break;
155 }
156 }
157
158 xfer->status = status;
159 }
160
161 void gaim_xfer_conversation_write(GaimXfer *xfer, char *message, gboolean is_error)
162 {
163 GaimConversation *conv = NULL;
164 GaimMessageFlags flags = GAIM_MESSAGE_SYSTEM;
165 char *escaped;
166
167 g_return_if_fail(xfer != NULL);
168 g_return_if_fail(message != NULL);
169
170 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, xfer->who,
171 gaim_xfer_get_account(xfer));
172
173 if (conv == NULL)
174 return;
175
176 escaped = g_markup_escape_text(message, -1);
177
178 if (is_error)
179 flags = GAIM_MESSAGE_ERROR;
180
181 gaim_conversation_write(conv, NULL, escaped, flags, time(NULL));
182 g_free(escaped);
183 }
184
185 static void gaim_xfer_show_file_error(GaimXfer *xfer, const char *filename)
186 {
187 int err = errno;
188 gchar *msg = NULL, *utf8;
189 GaimXferType xfer_type = gaim_xfer_get_type(xfer);
190 GaimAccount *account = gaim_xfer_get_account(xfer);
191
192 utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
193 switch(xfer_type) {
194 case GAIM_XFER_SEND:
195 msg = g_strdup_printf(_("Error reading %s: \n%s.\n"),
196 utf8, strerror(err));
197 break;
198 case GAIM_XFER_RECEIVE:
199 msg = g_strdup_printf(_("Error writing %s: \n%s.\n"),
200 utf8, strerror(err));
201 break;
202 default:
203 msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"),
204 utf8, strerror(err));
205 break;
206 }
207 g_free(utf8);
208
209 gaim_xfer_conversation_write(xfer, msg, TRUE);
210 gaim_xfer_error(xfer_type, account, xfer->who, msg);
211 g_free(msg);
212 }
213
214 static void
215 gaim_xfer_choose_file_ok_cb(void *user_data, const char *filename)
216 {
217 GaimXfer *xfer;
218 struct stat st;
219 gchar *dir;
220
221 xfer = (GaimXfer *)user_data;
222
223 if (g_stat(filename, &st) != 0) {
224 /* File not found. */
225 if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) {
226 #ifndef _WIN32
227 int mode = W_OK;
228 #else
229 int mode = F_OK;
230 #endif
231 dir = g_path_get_dirname(filename);
232
233 if (g_access(dir, mode) == 0) {
234 gaim_xfer_request_accepted(xfer, filename);
235 } else {
236 gaim_xfer_ref(xfer);
237 gaim_notify_message(
238 NULL, GAIM_NOTIFY_MSG_ERROR, NULL,
239 _("Directory is not writable."), NULL,
240 (GaimNotifyCloseCallback)gaim_xfer_choose_file, xfer);
241 }
242
243 g_free(dir);
244 }
245 else {
246 gaim_xfer_show_file_error(xfer, filename);
247 gaim_xfer_request_denied(xfer);
248 }
249 }
250 else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) &&
251 (st.st_size == 0)) {
252
253 gaim_notify_error(NULL, NULL,
254 _("Cannot send a file of 0 bytes."), NULL);
255
256 gaim_xfer_request_denied(xfer);
257 }
258 else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) &&
259 S_ISDIR(st.st_mode)) {
260 /*
261 * XXX - Sending a directory should be valid for some protocols.
262 */
263 gaim_notify_error(NULL, NULL,
264 _("Cannot send a directory."), NULL);
265
266 gaim_xfer_request_denied(xfer);
267 }
268 else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) &&
269 S_ISDIR(st.st_mode)) {
270 char *msg, *utf8;
271 utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
272 msg = g_strdup_printf(
273 _("%s is not a regular file. Cowardly refusing to overwrite it.\n"), utf8);
274 g_free(utf8);
275 gaim_notify_error(NULL, NULL, msg, NULL);
276 g_free(msg);
277 gaim_xfer_request_denied(xfer);
278 }
279 else {
280 gaim_xfer_request_accepted(xfer, filename);
281 }
282
283 gaim_xfer_unref(xfer);
284 }
285
286 static void
287 gaim_xfer_choose_file_cancel_cb(void *user_data, const char *filename)
288 {
289 GaimXfer *xfer = (GaimXfer *)user_data;
290
291 gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL);
292 gaim_xfer_request_denied(xfer);
293 }
294
295 static int
296 gaim_xfer_choose_file(GaimXfer *xfer)
297 {
298 gaim_request_file(xfer, NULL, gaim_xfer_get_filename(xfer),
299 (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE),
300 G_CALLBACK(gaim_xfer_choose_file_ok_cb),
301 G_CALLBACK(gaim_xfer_choose_file_cancel_cb), xfer);
302
303 return 0;
304 }
305
306 static int
307 cancel_recv_cb(GaimXfer *xfer)
308 {
309 gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL);
310 gaim_xfer_request_denied(xfer);
311 gaim_xfer_unref(xfer);
312
313 return 0;
314 }
315
316 static void
317 gaim_xfer_ask_recv(GaimXfer *xfer)
318 {
319 char *buf, *size_buf;
320 size_t size;
321
322 /* If we have already accepted the request, ask the destination file
323 name directly */
324 if (gaim_xfer_get_status(xfer) != GAIM_XFER_STATUS_ACCEPTED) {
325 GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who);
326
327 if (gaim_xfer_get_filename(xfer) != NULL)
328 {
329 size = gaim_xfer_get_size(xfer);
330 size_buf = gaim_str_size_to_units(size);
331 buf = g_strdup_printf(_("%s wants to send you %s (%s)"),
332 buddy ? gaim_buddy_get_alias(buddy) : xfer->who,
333 gaim_xfer_get_filename(xfer), size_buf);
334 g_free(size_buf);
335 }
336 else
337 {
338 buf = g_strdup_printf(_("%s wants to send you a file"),
339 buddy ? gaim_buddy_get_alias(buddy) : xfer->who);
340 }
341
342 if (xfer->message != NULL)
343 serv_got_im(gaim_account_get_connection(xfer->account),
344 xfer->who, xfer->message, 0, time(NULL));
345
346 gaim_request_accept_cancel(xfer, NULL, buf, NULL,
347 GAIM_DEFAULT_ACTION_NONE, xfer,
348 G_CALLBACK(gaim_xfer_choose_file),
349 G_CALLBACK(cancel_recv_cb));
350
351 g_free(buf);
352 } else
353 gaim_xfer_choose_file(xfer);
354 }
355
356 static int
357 ask_accept_ok(GaimXfer *xfer)
358 {
359 gaim_xfer_request_accepted(xfer, NULL);
360
361 return 0;
362 }
363
364 static int
365 ask_accept_cancel(GaimXfer *xfer)
366 {
367 gaim_xfer_request_denied(xfer);
368 gaim_xfer_unref(xfer);
369
370 return 0;
371 }
372
373 static void
374 gaim_xfer_ask_accept(GaimXfer *xfer)
375 {
376 char *buf, *buf2 = NULL;
377 GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who);
378
379 buf = g_strdup_printf(_("Accept file transfer request from %s?"),
380 buddy ? gaim_buddy_get_alias(buddy) : xfer->who);
381 if (gaim_xfer_get_remote_ip(xfer) &&
382 gaim_xfer_get_remote_port(xfer))
383 buf2 = g_strdup_printf(_("A file is available for download from:\n"
384 "Remote host: %s\nRemote port: %d"),
385 gaim_xfer_get_remote_ip(xfer),
386 gaim_xfer_get_remote_port(xfer));
387 gaim_request_accept_cancel(xfer, NULL, buf, buf2,
388 GAIM_DEFAULT_ACTION_NONE, xfer,
389 G_CALLBACK(ask_accept_ok),
390 G_CALLBACK(ask_accept_cancel));
391 g_free(buf);
392 g_free(buf2);
393 }
394
395 void
396 gaim_xfer_request(GaimXfer *xfer)
397 {
398 g_return_if_fail(xfer != NULL);
399 g_return_if_fail(xfer->ops.init != NULL);
400
401 gaim_xfer_ref(xfer);
402
403 if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE)
404 {
405 gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-request", xfer);
406 if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL)
407 {
408 /* The file-transfer was cancelled by a plugin */
409 gaim_xfer_cancel_local(xfer);
410 }
411 else if (gaim_xfer_get_filename(xfer) ||
412 gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_ACCEPTED)
413 {
414 gchar* message = NULL;
415 GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who);
416 message = g_strdup_printf(_("%s is offering to send file %s"),
417 buddy ? gaim_buddy_get_alias(buddy) : xfer->who, gaim_xfer_get_filename(xfer));
418 gaim_xfer_conversation_write(xfer, message, FALSE);
419 g_free(message);
420 /* Ask for a filename to save to if it's not already given by a plugin */
421 if (xfer->local_filename == NULL)
422 gaim_xfer_ask_recv(xfer);
423 }
424 else
425 {
426 gaim_xfer_ask_accept(xfer);
427 }
428 }
429 else
430 {
431 gaim_xfer_choose_file(xfer);
432 }
433 }
434
435 void
436 gaim_xfer_request_accepted(GaimXfer *xfer, const char *filename)
437 {
438 GaimXferType type;
439 struct stat st;
440 char *msg, *utf8;
441 GaimAccount *account;
442 GaimBuddy *buddy;
443
444 if (xfer == NULL)
445 return;
446
447 type = gaim_xfer_get_type(xfer);
448 account = gaim_xfer_get_account(xfer);
449
450 if (!filename && type == GAIM_XFER_RECEIVE) {
451 xfer->status = GAIM_XFER_STATUS_ACCEPTED;
452 xfer->ops.init(xfer);
453 return;
454 }
455
456 buddy = gaim_find_buddy(account, xfer->who);
457
458 if (type == GAIM_XFER_SEND) {
459 /* Sending a file */
460 /* Check the filename. */
461 #ifdef _WIN32
462 if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\")) {
463 #else
464 if (g_strrstr(filename, "../")) {
465 #endif
466 char *utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
467
468 msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8);
469 gaim_xfer_error(type, account, xfer->who, msg);
470 g_free(utf8);
471 g_free(msg);
472
473 gaim_xfer_unref(xfer);
474 return;
475 }
476
477 if (g_stat(filename, &st) == -1) {
478 gaim_xfer_show_file_error(xfer, filename);
479 gaim_xfer_unref(xfer);
480 return;
481 }
482
483 gaim_xfer_set_local_filename(xfer, filename);
484 gaim_xfer_set_size(xfer, st.st_size);
485
486 utf8 = g_filename_to_utf8(g_basename(filename), -1, NULL, NULL, NULL);
487 gaim_xfer_set_filename(xfer, utf8);
488
489 msg = g_strdup_printf(_("Offering to send %s to %s"),
490 utf8, buddy ? gaim_buddy_get_alias(buddy) : xfer->who);
491 g_free(utf8);
492
493 gaim_xfer_conversation_write(xfer, msg, FALSE);
494 g_free(msg);
495 }
496 else {
497 /* Receiving a file */
498 xfer->status = GAIM_XFER_STATUS_ACCEPTED;
499 gaim_xfer_set_local_filename(xfer, filename);
500
501 msg = g_strdup_printf(_("Starting transfer of %s from %s"),
502 xfer->filename, buddy ? gaim_buddy_get_alias(buddy) : xfer->who);
503 gaim_xfer_conversation_write(xfer, msg, FALSE);
504 g_free(msg);
505 }
506
507 gaim_xfer_add(xfer);
508 xfer->ops.init(xfer);
509
510 }
511
512 void
513 gaim_xfer_request_denied(GaimXfer *xfer)
514 {
515 g_return_if_fail(xfer != NULL);
516
517 if (xfer->ops.request_denied != NULL)
518 xfer->ops.request_denied(xfer);
519
520 gaim_xfer_unref(xfer);
521 }
522
523 GaimXferType
524 gaim_xfer_get_type(const GaimXfer *xfer)
525 {
526 g_return_val_if_fail(xfer != NULL, GAIM_XFER_UNKNOWN);
527
528 return xfer->type;
529 }
530
531 GaimAccount *
532 gaim_xfer_get_account(const GaimXfer *xfer)
533 {
534 g_return_val_if_fail(xfer != NULL, NULL);
535
536 return xfer->account;
537 }
538
539 GaimXferStatusType
540 gaim_xfer_get_status(const GaimXfer *xfer)
541 {
542 g_return_val_if_fail(xfer != NULL, GAIM_XFER_STATUS_UNKNOWN);
543
544 return xfer->status;
545 }
546
547 gboolean
548 gaim_xfer_is_canceled(const GaimXfer *xfer)
549 {
550 g_return_val_if_fail(xfer != NULL, TRUE);
551
552 if ((gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) ||
553 (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_REMOTE))
554 return TRUE;
555 else
556 return FALSE;
557 }
558
559 gboolean
560 gaim_xfer_is_completed(const GaimXfer *xfer)
561 {
562 g_return_val_if_fail(xfer != NULL, TRUE);
563
564 return (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_DONE);
565 }
566
567 const char *
568 gaim_xfer_get_filename(const GaimXfer *xfer)
569 {
570 g_return_val_if_fail(xfer != NULL, NULL);
571
572 return xfer->filename;
573 }
574
575 const char *
576 gaim_xfer_get_local_filename(const GaimXfer *xfer)
577 {
578 g_return_val_if_fail(xfer != NULL, NULL);
579
580 return xfer->local_filename;
581 }
582
583 size_t
584 gaim_xfer_get_bytes_sent(const GaimXfer *xfer)
585 {
586 g_return_val_if_fail(xfer != NULL, 0);
587
588 return xfer->bytes_sent;
589 }
590
591 size_t
592 gaim_xfer_get_bytes_remaining(const GaimXfer *xfer)
593 {
594 g_return_val_if_fail(xfer != NULL, 0);
595
596 return xfer->bytes_remaining;
597 }
598
599 size_t
600 gaim_xfer_get_size(const GaimXfer *xfer)
601 {
602 g_return_val_if_fail(xfer != NULL, 0);
603
604 return xfer->size;
605 }
606
607 double
608 gaim_xfer_get_progress(const GaimXfer *xfer)
609 {
610 g_return_val_if_fail(xfer != NULL, 0.0);
611
612 if (gaim_xfer_get_size(xfer) == 0)
613 return 0.0;
614
615 return ((double)gaim_xfer_get_bytes_sent(xfer) /
616 (double)gaim_xfer_get_size(xfer));
617 }
618
619 unsigned int
620 gaim_xfer_get_local_port(const GaimXfer *xfer)
621 {
622 g_return_val_if_fail(xfer != NULL, -1);
623
624 return xfer->local_port;
625 }
626
627 const char *
628 gaim_xfer_get_remote_ip(const GaimXfer *xfer)
629 {
630 g_return_val_if_fail(xfer != NULL, NULL);
631
632 return xfer->remote_ip;
633 }
634
635 unsigned int
636 gaim_xfer_get_remote_port(const GaimXfer *xfer)
637 {
638 g_return_val_if_fail(xfer != NULL, -1);
639
640 return xfer->remote_port;
641 }
642
643 void
644 gaim_xfer_set_completed(GaimXfer *xfer, gboolean completed)
645 {
646 GaimXferUiOps *ui_ops;
647
648 g_return_if_fail(xfer != NULL);
649
650 if (completed == TRUE) {
651 char *msg = NULL;
652 gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_DONE);
653
654 if (gaim_xfer_get_filename(xfer) != NULL)
655 msg = g_strdup_printf(_("Transfer of file %s complete"),
656 gaim_xfer_get_filename(xfer));
657 else
658 msg = g_strdup_printf(_("File transfer complete"));
659 gaim_xfer_conversation_write(xfer, msg, FALSE);
660 g_free(msg);
661 }
662
663 ui_ops = gaim_xfer_get_ui_ops(xfer);
664
665 if (ui_ops != NULL && ui_ops->update_progress != NULL)
666 ui_ops->update_progress(xfer, gaim_xfer_get_progress(xfer));
667 }
668
669 void
670 gaim_xfer_set_message(GaimXfer *xfer, const char *message)
671 {
672 g_return_if_fail(xfer != NULL);
673
674 g_free(xfer->message);
675 xfer->message = g_strdup(message);
676 }
677
678 void
679 gaim_xfer_set_filename(GaimXfer *xfer, const char *filename)
680 {
681 g_return_if_fail(xfer != NULL);
682
683 g_free(xfer->filename);
684 xfer->filename = g_strdup(filename);
685 }
686
687 void
688 gaim_xfer_set_local_filename(GaimXfer *xfer, const char *filename)
689 {
690 g_return_if_fail(xfer != NULL);
691
692 g_free(xfer->local_filename);
693 xfer->local_filename = g_strdup(filename);
694 }
695
696 void
697 gaim_xfer_set_size(GaimXfer *xfer, size_t size)
698 {
699 g_return_if_fail(xfer != NULL);
700
701 xfer->size = size;
702 xfer->bytes_remaining = xfer->size - gaim_xfer_get_bytes_sent(xfer);
703 }
704
705 void
706 gaim_xfer_set_bytes_sent(GaimXfer *xfer, size_t bytes_sent)
707 {
708 g_return_if_fail(xfer != NULL);
709
710 xfer->bytes_sent = bytes_sent;
711 xfer->bytes_remaining = gaim_xfer_get_size(xfer) - bytes_sent;
712 }
713
714 GaimXferUiOps *
715 gaim_xfer_get_ui_ops(const GaimXfer *xfer)
716 {
717 g_return_val_if_fail(xfer != NULL, NULL);
718
719 return xfer->ui_ops;
720 }
721
722 void
723 gaim_xfer_set_init_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *))
724 {
725 g_return_if_fail(xfer != NULL);
726
727 xfer->ops.init = fnc;
728 }
729
730 void gaim_xfer_set_request_denied_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *))
731 {
732 g_return_if_fail(xfer != NULL);
733
734 xfer->ops.request_denied = fnc;
735 }
736
737 void
738 gaim_xfer_set_read_fnc(GaimXfer *xfer, gssize (*fnc)(guchar **, GaimXfer *))
739 {
740 g_return_if_fail(xfer != NULL);
741
742 xfer->ops.read = fnc;
743 }
744
745 void
746 gaim_xfer_set_write_fnc(GaimXfer *xfer,
747 gssize (*fnc)(const guchar *, size_t, GaimXfer *))
748 {
749 g_return_if_fail(xfer != NULL);
750
751 xfer->ops.write = fnc;
752 }
753
754 void
755 gaim_xfer_set_ack_fnc(GaimXfer *xfer,
756 void (*fnc)(GaimXfer *, const guchar *, size_t))
757 {
758 g_return_if_fail(xfer != NULL);
759
760 xfer->ops.ack = fnc;
761 }
762
763 void
764 gaim_xfer_set_start_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *))
765 {
766 g_return_if_fail(xfer != NULL);
767
768 xfer->ops.start = fnc;
769 }
770
771 void
772 gaim_xfer_set_end_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *))
773 {
774 g_return_if_fail(xfer != NULL);
775
776 xfer->ops.end = fnc;
777 }
778
779 void
780 gaim_xfer_set_cancel_send_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *))
781 {
782 g_return_if_fail(xfer != NULL);
783
784 xfer->ops.cancel_send = fnc;
785 }
786
787 void
788 gaim_xfer_set_cancel_recv_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *))
789 {
790 g_return_if_fail(xfer != NULL);
791
792 xfer->ops.cancel_recv = fnc;
793 }
794
795 static void
796 gaim_xfer_increase_buffer_size(GaimXfer *xfer)
797 {
798 xfer->current_buffer_size = MIN(xfer->current_buffer_size * 1.5,
799 FT_MAX_BUFFER_SIZE);
800 }
801
802 gssize
803 gaim_xfer_read(GaimXfer *xfer, guchar **buffer)
804 {
805 gssize s, r;
806
807 g_return_val_if_fail(xfer != NULL, 0);
808 g_return_val_if_fail(buffer != NULL, 0);
809
810 if (gaim_xfer_get_size(xfer) == 0)
811 s = xfer->current_buffer_size;
812 else
813 s = MIN(gaim_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
814
815 if (xfer->ops.read != NULL)
816 r = (xfer->ops.read)(buffer, xfer);
817 else {
818 *buffer = g_malloc0(s);
819
820 r = read(xfer->fd, *buffer, s);
821 if (r < 0 && errno == EAGAIN)
822 r = 0;
823 else if (r < 0)
824 r = -1;
825 else if ((gaim_xfer_get_size(xfer) > 0) &&
826 ((gaim_xfer_get_bytes_sent(xfer)+r) >= gaim_xfer_get_size(xfer)))
827 gaim_xfer_set_completed(xfer, TRUE);
828 else if (r == 0)
829 r = -1;
830 }
831
832 if (r == xfer->current_buffer_size)
833 /*
834 * We managed to read the entire buffer. This means our this
835 * network is fast and our buffer is too small, so make it
836 * bigger.
837 */
838 gaim_xfer_increase_buffer_size(xfer);
839
840 return r;
841 }
842
843 gssize
844 gaim_xfer_write(GaimXfer *xfer, const guchar *buffer, gsize size)
845 {
846 gssize r, s;
847
848 g_return_val_if_fail(xfer != NULL, 0);
849 g_return_val_if_fail(buffer != NULL, 0);
850 g_return_val_if_fail(size != 0, 0);
851
852 s = MIN(gaim_xfer_get_bytes_remaining(xfer), size);
853
854 if (xfer->ops.write != NULL) {
855 r = (xfer->ops.write)(buffer, s, xfer);
856 } else {
857 r = write(xfer->fd, buffer, s);
858 if (r < 0 && errno == EAGAIN)
859 r = 0;
860 if ((gaim_xfer_get_bytes_sent(xfer)+r) >= gaim_xfer_get_size(xfer))
861 gaim_xfer_set_completed(xfer, TRUE);
862 }
863
864 return r;
865 }
866
867 static void
868 transfer_cb(gpointer data, gint source, GaimInputCondition condition)
869 {
870 GaimXferUiOps *ui_ops;
871 GaimXfer *xfer = (GaimXfer *)data;
872 guchar *buffer = NULL;
873 gssize r = 0;
874
875 if (condition & GAIM_INPUT_READ) {
876 r = gaim_xfer_read(xfer, &buffer);
877 if (r > 0) {
878 fwrite(buffer, 1, r, xfer->dest_fp);
879 } else if(r <= 0) {
880 gaim_xfer_cancel_remote(xfer);
881 return;
882 }
883 }
884
885 if (condition & GAIM_INPUT_WRITE) {
886 size_t s = MIN(gaim_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
887
888 /* this is so the prpl can keep the connection open
889 if it needs to for some odd reason. */
890 if (s == 0) {
891 if (xfer->watcher) {
892 gaim_input_remove(xfer->watcher);
893 xfer->watcher = 0;
894 }
895 return;
896 }
897
898 buffer = g_malloc0(s);
899
900 fread(buffer, 1, s, xfer->dest_fp);
901
902 /* Write as much as we're allowed to. */
903 r = gaim_xfer_write(xfer, buffer, s);
904
905 if (r == -1) {
906 gaim_xfer_cancel_remote(xfer);
907 g_free(buffer);
908 return;
909 } else if (r < s) {
910 /* We have to seek back in the file now. */
911 fseek(xfer->dest_fp, r - s, SEEK_CUR);
912 } else {
913 /*
914 * We managed to write the entire buffer. This means our
915 * network is fast and our buffer is too small, so make it
916 * bigger.
917 */
918 gaim_xfer_increase_buffer_size(xfer);
919 }
920 }
921
922 if (r > 0) {
923 if (gaim_xfer_get_size(xfer) > 0)
924 xfer->bytes_remaining -= r;
925
926 xfer->bytes_sent += r;
927
928 if (xfer->ops.ack != NULL)
929 xfer->ops.ack(xfer, buffer, r);
930
931 g_free(buffer);
932
933 ui_ops = gaim_xfer_get_ui_ops(xfer);
934
935 if (ui_ops != NULL && ui_ops->update_progress != NULL)
936 ui_ops->update_progress(xfer,
937 gaim_xfer_get_progress(xfer));
938 }
939
940 if (gaim_xfer_is_completed(xfer))
941 gaim_xfer_end(xfer);
942 }
943
944 static void
945 begin_transfer(GaimXfer *xfer, GaimInputCondition cond)
946 {
947 GaimXferType type = gaim_xfer_get_type(xfer);
948
949 xfer->dest_fp = g_fopen(gaim_xfer_get_local_filename(xfer),
950 type == GAIM_XFER_RECEIVE ? "wb" : "rb");
951
952 if (xfer->dest_fp == NULL) {
953 gaim_xfer_show_file_error(xfer, gaim_xfer_get_local_filename(xfer));
954 gaim_xfer_cancel_local(xfer);
955 return;
956 }
957
958 fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET);
959
960 xfer->watcher = gaim_input_add(xfer->fd, cond, transfer_cb, xfer);
961
962 xfer->start_time = time(NULL);
963
964 if (xfer->ops.start != NULL)
965 xfer->ops.start(xfer);
966 }
967
968 static void
969 connect_cb(gpointer data, gint source, const gchar *error_message)
970 {
971 GaimXfer *xfer = (GaimXfer *)data;
972
973 xfer->fd = source;
974
975 begin_transfer(xfer, GAIM_INPUT_READ);
976 }
977
978 void
979 gaim_xfer_start(GaimXfer *xfer, int fd, const char *ip,
980 unsigned int port)
981 {
982 GaimInputCondition cond;
983 GaimXferType type;
984
985 g_return_if_fail(xfer != NULL);
986 g_return_if_fail(gaim_xfer_get_type(xfer) != GAIM_XFER_UNKNOWN);
987
988 type = gaim_xfer_get_type(xfer);
989
990 gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_STARTED);
991
992 if (type == GAIM_XFER_RECEIVE) {
993 cond = GAIM_INPUT_READ;
994
995 if (ip != NULL) {
996 xfer->remote_ip = g_strdup(ip);
997 xfer->remote_port = port;
998
999 /* Establish a file descriptor. */
1000 gaim_proxy_connect(NULL, xfer->account, xfer->remote_ip,
1001 xfer->remote_port, connect_cb, xfer);
1002
1003 return;
1004 }
1005 else {
1006 xfer->fd = fd;
1007 }
1008 }
1009 else {
1010 cond = GAIM_INPUT_WRITE;
1011
1012 xfer->fd = fd;
1013 }
1014
1015 begin_transfer(xfer, cond);
1016 }
1017
1018 void
1019 gaim_xfer_end(GaimXfer *xfer)
1020 {
1021 g_return_if_fail(xfer != NULL);
1022
1023 /* See if we are actually trying to cancel this. */
1024 if (!gaim_xfer_is_completed(xfer)) {
1025 gaim_xfer_cancel_local(xfer);
1026 return;
1027 }
1028
1029 xfer->end_time = time(NULL);
1030 if (xfer->ops.end != NULL)
1031 xfer->ops.end(xfer);
1032
1033 if (xfer->watcher != 0) {
1034 gaim_input_remove(xfer->watcher);
1035 xfer->watcher = 0;
1036 }
1037
1038 if (xfer->fd != 0)
1039 close(xfer->fd);
1040
1041 if (xfer->dest_fp != NULL) {
1042 fclose(xfer->dest_fp);
1043 xfer->dest_fp = NULL;
1044 }
1045
1046 gaim_xfer_unref(xfer);
1047 }
1048
1049 void
1050 gaim_xfer_add(GaimXfer *xfer)
1051 {
1052 GaimXferUiOps *ui_ops;
1053
1054 g_return_if_fail(xfer != NULL);
1055
1056 ui_ops = gaim_xfer_get_ui_ops(xfer);
1057
1058 if (ui_ops != NULL && ui_ops->add_xfer != NULL)
1059 ui_ops->add_xfer(xfer);
1060 }
1061
1062 void
1063 gaim_xfer_cancel_local(GaimXfer *xfer)
1064 {
1065 GaimXferUiOps *ui_ops;
1066 char *msg = NULL;
1067
1068 g_return_if_fail(xfer != NULL);
1069
1070 gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL);
1071 xfer->end_time = time(NULL);
1072
1073 if (gaim_xfer_get_filename(xfer) != NULL)
1074 {
1075 msg = g_strdup_printf(_("You canceled the transfer of %s"),
1076 gaim_xfer_get_filename(xfer));
1077 }
1078 else
1079 {
1080 msg = g_strdup_printf(_("File transfer cancelled"));
1081 }
1082 gaim_xfer_conversation_write(xfer, msg, FALSE);
1083 g_free(msg);
1084
1085 if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND)
1086 {
1087 if (xfer->ops.cancel_send != NULL)
1088 xfer->ops.cancel_send(xfer);
1089 }
1090 else
1091 {
1092 if (xfer->ops.cancel_recv != NULL)
1093 xfer->ops.cancel_recv(xfer);
1094 }
1095
1096 if (xfer->watcher != 0) {
1097 gaim_input_remove(xfer->watcher);
1098 xfer->watcher = 0;
1099 }
1100
1101 if (xfer->fd != 0)
1102 close(xfer->fd);
1103
1104 if (xfer->dest_fp != NULL) {
1105 fclose(xfer->dest_fp);
1106 xfer->dest_fp = NULL;
1107 }
1108
1109 ui_ops = gaim_xfer_get_ui_ops(xfer);
1110
1111 if (ui_ops != NULL && ui_ops->cancel_local != NULL)
1112 ui_ops->cancel_local(xfer);
1113
1114 xfer->bytes_remaining = 0;
1115
1116 gaim_xfer_unref(xfer);
1117 }
1118
1119 void
1120 gaim_xfer_cancel_remote(GaimXfer *xfer)
1121 {
1122 GaimXferUiOps *ui_ops;
1123 gchar *msg;
1124 GaimAccount *account;
1125 GaimBuddy *buddy;
1126
1127 g_return_if_fail(xfer != NULL);
1128
1129 gaim_request_close_with_handle(xfer);
1130 gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_REMOTE);
1131 xfer->end_time = time(NULL);
1132
1133 account = gaim_xfer_get_account(xfer);
1134 buddy = gaim_find_buddy(account, xfer->who);
1135
1136 if (gaim_xfer_get_filename(xfer) != NULL)
1137 {
1138 msg = g_strdup_printf(_("%s canceled the transfer of %s"),
1139 buddy ? gaim_buddy_get_alias(buddy) : xfer->who, gaim_xfer_get_filename(xfer));
1140 }
1141 else
1142 {
1143 msg = g_strdup_printf(_("%s canceled the file transfer"),
1144 buddy ? gaim_buddy_get_alias(buddy) : xfer->who);
1145 }
1146 gaim_xfer_conversation_write(xfer, msg, TRUE);
1147 gaim_xfer_error(gaim_xfer_get_type(xfer), account, xfer->who, msg);
1148 g_free(msg);
1149
1150 if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND)
1151 {
1152 if (xfer->ops.cancel_send != NULL)
1153 xfer->ops.cancel_send(xfer);
1154 }
1155 else
1156 {
1157 if (xfer->ops.cancel_recv != NULL)
1158 xfer->ops.cancel_recv(xfer);
1159 }
1160
1161 if (xfer->watcher != 0) {
1162 gaim_input_remove(xfer->watcher);
1163 xfer->watcher = 0;
1164 }
1165
1166 if (xfer->fd != 0)
1167 close(xfer->fd);
1168
1169 if (xfer->dest_fp != NULL) {
1170 fclose(xfer->dest_fp);
1171 xfer->dest_fp = NULL;
1172 }
1173
1174 ui_ops = gaim_xfer_get_ui_ops(xfer);
1175
1176 if (ui_ops != NULL && ui_ops->cancel_remote != NULL)
1177 ui_ops->cancel_remote(xfer);
1178
1179 xfer->bytes_remaining = 0;
1180
1181 gaim_xfer_unref(xfer);
1182 }
1183
1184 void
1185 gaim_xfer_error(GaimXferType type, GaimAccount *account, const char *who, const char *msg)
1186 {
1187 char *title;
1188
1189 g_return_if_fail(msg != NULL);
1190 g_return_if_fail(type != GAIM_XFER_UNKNOWN);
1191
1192 if (account) {
1193 GaimBuddy *buddy;
1194 buddy = gaim_find_buddy(account, who);
1195 if (buddy)
1196 who = gaim_buddy_get_alias(buddy);
1197 }
1198
1199 if (type == GAIM_XFER_SEND)
1200 title = g_strdup_printf(_("File transfer to %s failed."), who);
1201 else
1202 title = g_strdup_printf(_("File transfer from %s failed."), who);
1203
1204 gaim_notify_error(NULL, NULL, title, msg);
1205
1206 g_free(title);
1207 }
1208
1209 void
1210 gaim_xfer_update_progress(GaimXfer *xfer)
1211 {
1212 GaimXferUiOps *ui_ops;
1213
1214 g_return_if_fail(xfer != NULL);
1215
1216 ui_ops = gaim_xfer_get_ui_ops(xfer);
1217 if (ui_ops != NULL && ui_ops->update_progress != NULL)
1218 ui_ops->update_progress(xfer, gaim_xfer_get_progress(xfer));
1219 }
1220
1221
1222 /**************************************************************************
1223 * File Transfer Subsystem API
1224 **************************************************************************/
1225 void *
1226 gaim_xfers_get_handle(void) {
1227 static int handle = 0;
1228
1229 return &handle;
1230 }
1231
1232 void
1233 gaim_xfers_init(void) {
1234 void *handle = gaim_xfers_get_handle();
1235
1236 /* register signals */
1237 gaim_signal_register(handle, "file-recv-accept",
1238 gaim_marshal_VOID__POINTER, NULL, 1,
1239 gaim_value_new(GAIM_TYPE_SUBTYPE,
1240 GAIM_SUBTYPE_XFER));
1241 gaim_signal_register(handle, "file-send-accept",
1242 gaim_marshal_VOID__POINTER, NULL, 1,
1243 gaim_value_new(GAIM_TYPE_SUBTYPE,
1244 GAIM_SUBTYPE_XFER));
1245 gaim_signal_register(handle, "file-recv-start",
1246 gaim_marshal_VOID__POINTER, NULL, 1,
1247 gaim_value_new(GAIM_TYPE_SUBTYPE,
1248 GAIM_SUBTYPE_XFER));
1249 gaim_signal_register(handle, "file-send-start",
1250 gaim_marshal_VOID__POINTER, NULL, 1,
1251 gaim_value_new(GAIM_TYPE_SUBTYPE,
1252 GAIM_SUBTYPE_XFER));
1253 gaim_signal_register(handle, "file-send-cancel",
1254 gaim_marshal_VOID__POINTER, NULL, 1,
1255 gaim_value_new(GAIM_TYPE_SUBTYPE,
1256 GAIM_SUBTYPE_XFER));
1257 gaim_signal_register(handle, "file-recv-cancel",
1258 gaim_marshal_VOID__POINTER, NULL, 1,
1259 gaim_value_new(GAIM_TYPE_SUBTYPE,
1260 GAIM_SUBTYPE_XFER));
1261 gaim_signal_register(handle, "file-send-complete",
1262 gaim_marshal_VOID__POINTER, NULL, 1,
1263 gaim_value_new(GAIM_TYPE_SUBTYPE,
1264 GAIM_SUBTYPE_XFER));
1265 gaim_signal_register(handle, "file-recv-complete",
1266 gaim_marshal_VOID__POINTER, NULL, 1,
1267 gaim_value_new(GAIM_TYPE_SUBTYPE,
1268 GAIM_SUBTYPE_XFER));
1269 gaim_signal_register(handle, "file-recv-request",
1270 gaim_marshal_VOID__POINTER, NULL, 1,
1271 gaim_value_new(GAIM_TYPE_SUBTYPE,
1272 GAIM_SUBTYPE_XFER));
1273 }
1274
1275 void
1276 gaim_xfers_uninit(void) {
1277 gaim_signals_disconnect_by_handle(gaim_xfers_get_handle());
1278 }
1279
1280 void
1281 gaim_xfers_set_ui_ops(GaimXferUiOps *ops) {
1282 xfer_ui_ops = ops;
1283 }
1284
1285 GaimXferUiOps *
1286 gaim_xfers_get_ui_ops(void) {
1287 return xfer_ui_ops;
1288 }