comparison libgaim/ft.c @ 14192:60b1bc8dbf37

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