thft: Rm never-used xfer prpl op stubs.
[thrasher.git] / thft.c
blob1bb04c137726471459adbedcdffdbada989200fb
1 /*
2 * Thrasher Bird - XMPP transport via libpurple
3 * Copyright (C) 2008 Barracuda Networks, Inc.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with Thrasher Bird; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
19 #include <glib.h>
20 #include "thrasher.h"
21 #include "thperl.h"
23 #include <ft.h>
24 #include <server.h>
26 #include "thft.h"
28 /* GENERAL NOTES:
30 * The Thrasher FT ID indexes file transfer structs by an integer,
31 * which can be easily passed back to Perl as a token it can use to
32 * manipulate the file transfer as needed. A value <= 0 indicates an
33 * error. There isn't much value to having Perl manipulate the structs
34 * directly, so the Perl-side FT object is entirely separate from the
35 * libpurple xfer struct.
37 * SENDING:
39 * 1. Perl-side accepts the file and negotiates a SOCKS connection.
40 * Stash parameters by *stream* ID for this step only; everything
41 * else uses the Thrasher-generated ID from step 2.1.
43 * 2. Once Perl-side completes negotiation, it starts libpurple-side
44 * setup with thrasher_send_file().
46 * 2.1. Stash xfer parameters in the account until xfer is constructed.
48 * 2.2. Call server.c:serv_send_file() should end up calling
50 * 2.3. prpl:send_file, which calls
52 * 2.4. purple_xfer_new(), which calls
54 * 2.4. thrasher_new_xfer callback in ui_ops to copy parameters out of
55 * stash and store the xfer in Thrasher's C-side ID lookup table.
57 * 2.5. thrasher_send_file() returns the ID to Perl for building the
58 * lookup table for Perl-side FT data.
60 * 3. Perl-side connects to SOCKS and sets a watch on the FD to
61 * trigger ui_read.
63 * 4. Loop of ft.c:do_transfer() calling ui_read/data_not_sent
64 * callbacks until canceled or complete.
66 * RECEIVING:
67 * 1. thrasher_new_xfer() stores the xfer by ID.
69 * 2. recv_request_cb() hands off to Perl-side to do negotiation.
70 * Eventually, a Perl-side event accepts or denies the FT with
71 * thrasher_xfer_recv_request_responder().
73 * 3. Loop of ft.cdo_transfer() calling ui_write. Perl-side stashes
74 * data if it is unable to keep up forwarding data to the Thrasher
75 * user. ui_write hides such problems (even removing the FD watch
76 * and taking over the loop if necessary) from its caller because
77 * libpurple 2.6 will cancel the FT instead of slowing down.
82 * Set up the mapping and associated functions
85 size_t next_file_transfer_id = 1;
86 /* guint id_ptr => PurpleXfer* xfer */
87 GHashTable *id_to_xfer = NULL;
89 void free_id (gpointer id)
91 free(id);
94 size_t get_next_file_transfer_id () {
95 return next_file_transfer_id++;
98 int store_xfer(PurpleXfer* xfer) {
99 guint id = get_id_by_xfer(xfer);
100 g_hash_table_insert(id_to_xfer, (void*)id, xfer);
101 purple_debug_info("thrasher ft",
102 "%d: stored\n",
103 id);
104 return id;
107 // Both of these functions assume the transfer is present and
108 // will segfault if it is not, so FIXME: Soften this. This
109 // is cool for debugging but production ought to be more graceful.
110 PurpleXfer* get_xfer_by_id(guint id)
112 return g_hash_table_lookup(id_to_xfer, (void*)id);
115 guint get_id_by_xfer(PurpleXfer* xfer)
117 Thrasher_Xfer_UI_Data *ui_data = xfer->ui_data;
118 g_return_val_if_fail(ui_data, 0);
119 g_return_val_if_fail(ui_data->id >= 1, 0);
120 return ui_data->id;
123 // Correctly remove the transfer from our hash tables.
124 void thrasher_remove_xfer(PurpleXfer* xfer)
126 guint id = get_id_by_xfer(xfer);
127 purple_debug_info("thrasher ft",
128 "%d: thrasher_remove_xfer\n",
129 id);
130 g_hash_table_remove(id_to_xfer, (void*)id);
134 * Callbacks for transfers
137 /* thrasher_xfer_recv_request_cb tells Perl side to send an offer IQ
138 * for xfer to the transport user...
140 void
141 thrasher_xfer_recv_request_cb(PurpleXfer *xfer,
142 gpointer data) {
143 guint id = get_id_by_xfer(xfer);
144 const char* remote_filename = purple_xfer_get_filename(xfer);
145 purple_debug_info("thrasher ft",
146 "%d: thrasher_xfer_recv_request_cb with filename %s\n",
148 remote_filename);
150 /* Can accept/deny this xfer if this signal CB either sets
151 * local_filename and status or somehow eventually results in
152 * purple_xfer_request_* being called.
154 * Otherwise, would have to implement a request_action in
155 * thrasher_request_uiops that could tell whether the request was
156 * for an FT, cert, etc. Based on matching the human-readable
157 * message? Ugh.
159 thrasher_wrapper_ft_recv_request(xfer, remote_filename);
162 /* ...and Perl-side calls thrasher_xfer_recv_request_responder when
163 * the response to the offer arrives (first setting up the FT proxy if
164 * accepting).
166 void
167 thrasher_xfer_recv_request_responder(guint id,
168 guint accept) {
169 purple_debug_info("thrasher ft",
170 "%d: thrasher_xfer_recv_request_responder(%d)\n",
172 accept);
173 PurpleXfer *xfer = get_xfer_by_id(id);
174 g_return_if_fail(xfer);
176 const char* remote_filename = purple_xfer_get_filename(xfer);
177 if (accept) {
178 /* local_filename doesn't matter--Thrasher user's XMPP client
179 * maintains its own destination path--but libpurple checks it
180 * before proceeding.
182 purple_xfer_request_accepted(xfer, remote_filename);
184 else {
185 purple_xfer_request_denied(xfer);
189 void thrasher_xfer_recv_accept_cb(PurpleXfer *xfer,
190 gpointer data)
192 guint id = get_id_by_xfer(xfer);
193 purple_debug_info("thrasher ft",
194 "%d: thrasher_xfer_recv_accept_cb\n",
195 id);
198 void thrasher_xfer_recv_start_cb(PurpleXfer *xfer,
199 gpointer data)
201 guint id = get_id_by_xfer(xfer);
202 purple_debug_info("thrasher ft",
203 "%d: thrasher_xfer_recv_start_cb\n",
204 id);
205 purple_xfer_ui_ready(xfer);
208 void thrasher_xfer_recv_cancel_cb(PurpleXfer *xfer,
209 gpointer data)
211 guint id = get_id_by_xfer(xfer);
212 purple_debug_info("thrasher ft",
213 "%d: thrasher_xfer_recv_cancel_cb\n",
214 id);
215 thrasher_wrapper_ft_recv_cancel(id);
218 void
219 thrasher_xfer_recv_complete_cb(PurpleXfer *xfer,
220 gpointer data) {
221 guint id = get_id_by_xfer(xfer);
222 purple_debug_info("thrasher ft",
223 "%d: thrasher_xfer_recv_complete_cb\n",
224 id);
225 thrasher_wrapper_ft_recv_complete(id);
228 void thrasher_xfer_send_accept_cb(PurpleXfer *xfer,
229 gpointer data)
231 guint id = get_id_by_xfer(xfer);
232 purple_debug_info("thrasher ft",
233 "%d: thrasher_xfer_send_accept_cb\n",
234 id);
237 void
238 thrasher_xfer_send_start_cb(PurpleXfer *xfer,
239 gpointer data) {
240 guint id = get_id_by_xfer(xfer);
241 purple_debug_info("thrasher ft",
242 "%d: thrasher_xfer_send_start_cb\n",
243 id);
245 thrasher_wrapper_ft_send_start(id);
248 void thrasher_xfer_send_cancel_cb(PurpleXfer *xfer,
249 gpointer data)
251 guint id = get_id_by_xfer(xfer);
252 purple_debug_info("thrasher ft",
253 "%d: thrasher_xfer_send_cancel_cb\n",
254 id);
255 thrasher_wrapper_ft_send_cancel(id);
258 void
259 thrasher_xfer_send_complete_cb(PurpleXfer *xfer,
260 gpointer data) {
261 guint id = get_id_by_xfer(xfer);
262 purple_debug_info("thrasher ft",
263 "%d: thrasher_xfer_send_complete_cb\n",
264 id);
265 thrasher_wrapper_ft_send_complete(id);
269 * UI Ops for transfers
272 void thrasher_new_xfer(PurpleXfer *xfer) {
273 purple_debug_info("thrasher ft",
274 "new xfer!\n");
276 if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) {
277 Thrasher_Xfer_UI_Data* ui_data = g_new(Thrasher_Xfer_UI_Data, 1);
278 ui_data->id = get_next_file_transfer_id();
279 xfer->ui_data = ui_data;
281 else if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
282 PurpleAccount* account = purple_xfer_get_account(xfer);
283 GHashTable* pending_send_file
284 = thrasher_account_get_pending_send_file(account);
285 const char* who = purple_xfer_get_remote_user(xfer);
286 Thrasher_Xfer_UI_Data* ui_data = g_hash_table_lookup(pending_send_file,
287 who);
288 g_return_if_fail(ui_data);
289 g_hash_table_remove(pending_send_file, who);
290 xfer->ui_data = ui_data;
292 purple_xfer_set_message(xfer, ui_data->desc);
293 g_free(ui_data->desc);
294 ui_data->desc = NULL;
296 purple_xfer_set_local_filename(xfer, ui_data->filename);
297 purple_xfer_set_filename(xfer, ui_data->filename);
298 g_free(ui_data->filename);
299 ui_data->filename = NULL;
300 purple_xfer_set_size(xfer, ui_data->size);
301 purple_debug_info("thrasher ft",
302 "%d: xfer filename now %s and size now %d\n",
303 ui_data->id,
304 xfer->local_filename,
305 xfer->size);
308 store_xfer(xfer);
311 void thrasher_destroy_xfer(PurpleXfer *xfer)
313 guint id = get_id_by_xfer(xfer);
314 purple_debug_info("thrasher ft",
315 "%d: thrasher_destroy_xfer\n", id);
317 thrasher_remove_xfer(xfer);
319 free(xfer->ui_data);
320 xfer->ui_data = NULL;
323 // Called to add a transfer to a UI we don't have
324 void thrasher_add_xfer(PurpleXfer *xfer)
326 int id = get_id_by_xfer(xfer);
327 if (id == -1) {
328 id = store_xfer(xfer);
330 purple_debug_info("thrasher ft",
331 "%d: thrasher_add_xfer\n",
332 id);
333 return;
336 // Called to update a UI we don't have
337 void thrasher_update_xfer_progress(PurpleXfer *xfer, double percent)
339 guint id = get_id_by_xfer(xfer);
340 percent *= 100;
341 purple_debug_info("thrasher ft",
342 "%d: %0.2f%% complete\n",
344 percent);
345 return;
348 void thrasher_xfer_cancel_local(PurpleXfer *xfer)
350 guint id = get_id_by_xfer(xfer);
351 purple_debug_info("thrasher ft",
352 "%d: thrasher_xfer_cancel_local\n", id);
353 return;
356 void thrasher_xfer_cancel_remote(PurpleXfer *xfer)
358 guint id = get_id_by_xfer(xfer);
359 purple_debug_info("thrasher ft",
360 "%d: thrasher_xfer_cancel_remote\n",
361 id);
364 gssize
365 thrasher_ui_write(PurpleXfer *xfer,
366 const guchar *buffer,
367 gssize size) {
368 guint id = get_id_by_xfer(xfer);
369 purple_debug_info("thrasher ft",
370 "%d: asked to write %d bytes.\n",
372 size);
373 gssize written_sz = thrasher_wrapper_ft_write(id, buffer, size);
374 purple_debug_info("thrasher ft",
375 "%d: wrote %d bytes.\n",
377 written_sz);
379 /* HACK: For some reason, libpurple 2.6 doesn't scale the window
380 * if write is incomplete like when reading--it cancels the
381 * transfer entirely! So:
382 * 1. Have Perl-side write stash the leftover data.
383 * 2. Do our own window scaling here.
384 * 3. Lie to libpurple and say write completed.
385 * 4. Have Perl-side slip stashed data into next write.
387 if (written_sz < size) {
388 xfer->current_buffer_size = MAX(xfer->current_buffer_size * 0.5,
389 /* Very low because of above hackery. */
390 128);
391 if (written_sz == 0) {
392 /* Extreme case: wasn't ready to write anything at all.
393 * Perl-side will call ui_ready when caught up.
395 purple_input_remove(xfer->watcher);
396 xfer->watcher = 0;
399 return size;
402 gssize
403 thrasher_ui_read(PurpleXfer *xfer,
404 guchar **buffer,
405 gssize size) {
406 guint id = get_id_by_xfer(xfer);
407 purple_debug_info("thrasher ft",
408 "%d: asked to read %d bytes.\n",
410 size);
411 gssize read_sz = thrasher_wrapper_ft_read(id, buffer, size);
412 purple_debug_info("thrasher ft",
413 "%d: read %d bytes.\n",
415 read_sz);
416 return read_sz;
419 void
420 thrasher_data_not_sent(PurpleXfer *xfer,
421 const guchar *buffer,
422 gsize size) {
423 guint id = get_id_by_xfer(xfer);
424 purple_debug_info("thrasher ft",
425 "%d: asked to unread %d bytes.\n",
426 id, size);
427 thrasher_wrapper_ft_data_not_sent(id, buffer, size);
431 * Initialization for libpurple file transfers
434 static gpointer
435 thrasher_xfer_get_handle()
437 static int handle;
438 return &handle;
441 /* thrasher_request_file() exists only to skip over
442 * purple_xfer_choose_file_ok_cb() to purple_xfer_request_accepted().
443 * ok_cb verifies the file exists on disk, but with Thrasher it never
444 * does. Obviously, Thrasher need not prompt for a filename here.
446 void *thrasher_request_file(const char *title, const char *filename,
447 gboolean savedialog, GCallback ok_cb,
448 GCallback cancel_cb,
449 PurpleAccount *account,
450 const char *who,
451 PurpleConversation *conv,
452 void *user_data) {
453 PurpleXfer* xfer = user_data;
454 purple_debug_info("thrasher ft",
455 "thrasher_request_file (%s -> %s)\n",
456 filename, who);
458 if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) {
459 purple_debug_info("thrasher ft",
460 "Should skip ask_accept/request_file"
461 " if receiving. Refer to"
462 " thrasher_xfer_recv_request_responder().\n");
463 return NULL;
466 guint id = get_id_by_xfer(xfer);
467 purple_debug_info("thrasher ft",
468 "%d: about to purple_xfer_request_accepted\n",
469 id);
470 /* local_filename already set, but do want xfer_add and ops.init called. */
471 purple_xfer_request_accepted(xfer,
472 xfer->filename);
474 /* no meaningful ui handle */
475 return NULL;
478 static PurpleXferUiOps thrasher_xfer_uiops = {
479 thrasher_new_xfer,
480 thrasher_destroy_xfer,
481 thrasher_add_xfer,
482 thrasher_update_xfer_progress,
483 thrasher_xfer_cancel_local,
484 thrasher_xfer_cancel_remote,
485 thrasher_ui_write,
486 thrasher_ui_read,
487 thrasher_data_not_sent,
489 // 1 reserved
490 NULL
493 void thrasher_xfer_init()
495 /* thrasher_xfer_destroy() would release id_to_xfer... but that is
496 * assumed to always coincide with process exit.
498 id_to_xfer = g_hash_table_new(g_direct_hash, g_direct_equal);
500 purple_xfers_init();
501 purple_xfers_set_ui_ops(&thrasher_xfer_uiops);
503 void *ft_handle = purple_xfers_get_handle();
504 gpointer thrasher_xfer_handle = thrasher_xfer_get_handle();
506 purple_signal_connect(ft_handle,
507 "file-recv-request",
508 thrasher_xfer_handle,
509 PURPLE_CALLBACK(thrasher_xfer_recv_request_cb),
510 NULL);
511 purple_signal_connect(ft_handle,
512 "file-recv-accept",
513 thrasher_xfer_handle,
514 PURPLE_CALLBACK(thrasher_xfer_recv_accept_cb),
515 NULL);
516 purple_signal_connect(ft_handle,
517 "file-recv-start",
518 thrasher_xfer_handle,
519 PURPLE_CALLBACK(thrasher_xfer_recv_start_cb),
520 NULL);
521 purple_signal_connect(ft_handle,
522 "file-recv-cancel",
523 thrasher_xfer_handle,
524 PURPLE_CALLBACK(thrasher_xfer_recv_cancel_cb),
525 NULL);
526 purple_signal_connect(ft_handle,
527 "file-recv-complete",
528 thrasher_xfer_handle,
529 PURPLE_CALLBACK(thrasher_xfer_recv_complete_cb),
530 NULL);
531 purple_signal_connect(ft_handle,
532 "file-send-accept",
533 thrasher_xfer_handle,
534 PURPLE_CALLBACK(thrasher_xfer_send_accept_cb),
535 NULL);
536 purple_signal_connect(ft_handle,
537 "file-send-start",
538 thrasher_xfer_handle,
539 PURPLE_CALLBACK(thrasher_xfer_send_start_cb),
540 NULL);
541 purple_signal_connect(ft_handle,
542 "file-send-cancel",
543 thrasher_xfer_handle,
544 PURPLE_CALLBACK(thrasher_xfer_send_cancel_cb),
545 NULL);
546 purple_signal_connect(ft_handle,
547 "file-send-complete",
548 thrasher_xfer_handle,
549 PURPLE_CALLBACK(thrasher_xfer_send_complete_cb),
550 NULL);
555 * Actually initiate a file transfer
557 size_t
558 thrasher_send_file(PurpleAccount *account,
559 const char *who,
560 char* filename,
561 size_t size,
562 char* desc) {
563 g_return_val_if_fail(account, 0);
564 g_return_val_if_fail(who, 0);
565 g_return_val_if_fail(filename, 0);
566 g_return_val_if_fail(size, 0);
567 g_return_val_if_fail(desc, 0);
569 PurpleConnection *connection = thrasher_connection_for_account(account);
570 if (!connection) {
571 purple_debug_info("thrasher ft",
572 "Connection could not be found for account!?\n");
573 return 0;
576 PurplePlugin *prpl = purple_connection_get_prpl(connection);
577 if (! prpl) {
578 purple_debug_info("thrasher ft",
579 "prpl could not be found for connection!?\n");
580 return 0;
583 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
584 if (! prpl_info) {
585 purple_debug_info("thrasher ft",
586 "prpl_info could not be found for prpl!?\n");
587 return 0;
590 if (prpl_info->can_receive_file && ! prpl_info->can_receive_file(connection,
591 who)) {
592 purple_debug_info("thrasher ft",
593 "prpl says %s can't receive a file.\n",
594 who);
595 return 0;
598 /* From now on, assume the FT will either succeed or be canceled.
600 * This is the last chance to decide how to proceed until/if
601 * libpurple calls us back. If we're wrong and the libpurple side
602 * ignores (neither uses nor cancels) the xfer, the XMPP user will
603 * be connected to a proxy that goes nowhere until the proxied
604 * transfer "succeeds"....
606 * Because serv_send_file() returns void and thus can't be
607 * checked, add checks as necessary above.
609 size_t id = get_next_file_transfer_id();
611 /* TODO: separate structures for this backchannel and ui_data.
612 * local_filename and size are redundant once there's an xfer for
613 * this to be a ui_data of.
615 Thrasher_Xfer_UI_Data* ui_data = g_new(Thrasher_Xfer_UI_Data, 1);
616 ui_data->filename = g_strdup(filename);
617 ui_data->size = size;
618 ui_data->id = id;
619 ui_data->desc = g_strdup(desc);
621 /* FIXME: better not have more than one FT to the same who at the
622 * same time.... */
623 GHashTable* pending_send_file
624 = thrasher_account_get_pending_send_file(account);
625 g_hash_table_insert(pending_send_file,
626 (void*)who,
627 ui_data);
629 purple_debug_info("thrasher ft",
630 "%d: calling serv_send for file %s size %d to %s\n",
632 filename,
633 size,
634 who);
636 /* Passing NULL filename causes the prpl to not call
637 * purple_xfer_request_accepted() which is mostly a noop because
638 * Thrasher has a ui_read callback. Would be nice if
640 * 1. request_accepted generated a file-send-accept signal and
642 * 2. it was only called after the remote side had in fact accepted
644 * but neither is currently true in most prpls. Instead, Perl-side
645 * always accepts but then cancel if no remote side appears.
647 serv_send_file(connection, who, NULL);
649 return id;