source: protocols/purple/ft.c @ b5a5938

Last change on this file since b5a5938 was 4aa0f6b, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-06-07T14:31:07Z

Merging killerbee stuff, bringing all the bleeding-edge stuff together.

  • Property mode set to 100644
File size: 9.5 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - File transfer stuff                                   *
5*                                                                           *
6*  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24/* Do file transfers via disk for now, since libpurple was really designed
25   for straight-to/from disk fts and is only just learning how to pass the
26   file contents the the UI instead (2.6.0 and higher it seems, and with
27   varying levels of success). */
28
29#include "bitlbee.h"
30
31#include <stdarg.h>
32
33#include <glib.h>
34#include <purple.h>
35
36struct prpl_xfer_data
37{
38        PurpleXfer *xfer;
39        file_transfer_t *ft;
40        struct im_connection *ic;
41        int fd;
42        char *fn, *handle;
43        gboolean ui_wants_data;
44};
45
46static file_transfer_t *next_ft;
47
48struct im_connection *purple_ic_by_pa( PurpleAccount *pa );
49static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond );
50static gboolean prpl_xfer_write_request( struct file_transfer *ft );
51
52
53/* Receiving files (IM->UI): */
54static void prpl_xfer_accept( struct file_transfer *ft )
55{
56        struct prpl_xfer_data *px = ft->data;
57        purple_xfer_request_accepted( px->xfer, NULL );
58        prpl_xfer_write_request( ft );
59}
60
61static void prpl_xfer_canceled( struct file_transfer *ft, char *reason )
62{
63        struct prpl_xfer_data *px = ft->data;
64        purple_xfer_request_denied( px->xfer );
65}
66
67static void prplcb_xfer_new( PurpleXfer *xfer )
68{
69        if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE )
70        {
71                struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
72               
73                xfer->ui_data = px;
74                px->xfer = xfer;
75                px->fn = mktemp( g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ) );
76                px->fd = -1;
77                px->ic = purple_ic_by_pa( xfer->account );
78               
79                purple_xfer_set_local_filename( xfer, px->fn );
80               
81                /* Sadly the xfer struct is still empty ATM so come back after
82                   the caller is done. */
83                b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer );
84        }
85        else
86        {
87                struct file_transfer *ft = next_ft;
88                struct prpl_xfer_data *px = ft->data;
89               
90                xfer->ui_data = px;
91                px->xfer = xfer;
92               
93                next_ft = NULL;
94        }
95}
96
97static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond )
98{
99        PurpleXfer *xfer = data;
100        struct im_connection *ic = purple_ic_by_pa( xfer->account );
101        struct prpl_xfer_data *px = xfer->ui_data;
102        PurpleBuddy *buddy;
103        const char *who;
104       
105        buddy = purple_find_buddy( xfer->account, xfer->who );
106        who = buddy ? purple_buddy_get_name( buddy ) : xfer->who;
107       
108        /* TODO(wilmer): After spreading some more const goodness in BitlBee,
109           remove the evil cast below. */
110        px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size );
111        px->ft->data = px;
112       
113        px->ft->accept = prpl_xfer_accept;
114        px->ft->canceled = prpl_xfer_canceled;
115        px->ft->write_request = prpl_xfer_write_request;
116       
117        return FALSE;
118}
119
120gboolean try_write_to_ui( gpointer data, gint fd, b_input_condition cond )
121{
122        struct file_transfer *ft = data;
123        struct prpl_xfer_data *px = ft->data;
124        struct stat fs;
125        off_t tx_bytes;
126       
127        /* If we don't have the file opened yet, there's no data so wait. */
128        if( px->fd < 0 || !px->ui_wants_data )
129                return FALSE;
130       
131        tx_bytes = lseek( px->fd, 0, SEEK_CUR );
132        fstat( px->fd, &fs );
133       
134        if( fs.st_size > tx_bytes )
135        {
136                char buf[1024];
137                size_t n = MIN( fs.st_size - tx_bytes, sizeof( buf ) );
138               
139                if( read( px->fd, buf, n ) == n && ft->write( ft, buf, n ) )
140                {
141                        px->ui_wants_data = FALSE;
142                }
143                else
144                {
145                        purple_xfer_cancel_local( px->xfer );
146                        imcb_file_canceled( px->ic, ft, "Read error" );
147                }
148        }
149       
150        if( lseek( px->fd, 0, SEEK_CUR ) == px->xfer->size )
151        {
152                /*purple_xfer_end( px->xfer );*/
153                imcb_file_finished( px->ic, ft );
154        }
155       
156        return FALSE;
157}
158
159/* UI calls this when its buffer is empty and wants more data to send to the user. */
160static gboolean prpl_xfer_write_request( struct file_transfer *ft )
161{
162        struct prpl_xfer_data *px = ft->data;
163       
164        px->ui_wants_data = TRUE;
165        try_write_to_ui( ft, 0, 0 );
166       
167        return FALSE;
168}
169
170
171/* Generic (IM<>UI): */
172static void prplcb_xfer_destroy( PurpleXfer *xfer )
173{
174        struct prpl_xfer_data *px = xfer->ui_data;
175       
176        g_free( px->fn );
177        g_free( px->handle );
178        if( px->fd >= 0 )
179                close( px->fd );
180        g_free( px );
181}
182
183static void prplcb_xfer_progress( PurpleXfer *xfer, double percent )
184{
185        struct prpl_xfer_data *px = xfer->ui_data;
186       
187        if( px == NULL )
188                return;
189       
190        if( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND )
191        {
192                if( *px->fn )
193                {
194                        char *slash;
195                       
196                        unlink( px->fn );
197                        if( ( slash = strrchr( px->fn, '/' ) ) )
198                        {
199                                *slash = '\0';
200                                rmdir( px->fn );
201                        }
202                        *px->fn = '\0';
203                }
204               
205                return;
206        }
207       
208        if( px->fd == -1 && percent > 0 )
209        {
210                /* Weeeeeeeee, we're getting data! That means the file exists
211                   by now so open it and start sending to the UI. */
212                px->fd = open( px->fn, O_RDONLY );
213               
214                /* Unlink it now, because we don't need it after this. */
215                unlink( px->fn );
216        }
217       
218        if( percent < 1 )
219                try_write_to_ui( px->ft, 0, 0 );
220        else
221                /* Another nice problem: If we have the whole file, it only
222                   gets closed when we return. Problem: There may still be
223                   stuff buffered and not written, we'll only see it after
224                   the caller close()s the file. So poll the file after that. */
225                b_timeout_add( 0, try_write_to_ui, px->ft );
226}
227
228static void prplcb_xfer_cancel_remote( PurpleXfer *xfer )
229{
230        struct prpl_xfer_data *px = xfer->ui_data;
231       
232        if( px->ft )
233                imcb_file_canceled( px->ic, px->ft, "Canceled by remote end" );
234        else
235                /* px->ft == NULL for sends, because of the two stages. :-/ */
236                imcb_error( px->ic, "File transfer cancelled by remote end" );
237}
238
239static void prplcb_xfer_dbg( PurpleXfer *xfer )
240{
241        fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer );
242}
243
244
245/* Sending files (UI->IM): */
246static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len );
247static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond );
248
249void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle )
250{
251        struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
252        char *dir, *basename;
253       
254        ft->data = px;
255        px->ft = ft;
256       
257        dir = g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" );
258        if( !mkdtemp( dir ) )
259        {
260                imcb_error( ic, "Could not create temporary file for file transfer" );
261                g_free( px );
262                g_free( dir );
263                return;
264        }
265       
266        if( ( basename = strrchr( ft->file_name, '/' ) ) )
267                basename++;
268        else
269                basename = ft->file_name;
270        px->fn = g_strdup_printf( "%s/%s", dir, basename );
271        px->fd = open( px->fn, O_WRONLY | O_CREAT, 0600 );
272        g_free( dir );
273       
274        if( px->fd < 0 )
275        {
276                imcb_error( ic, "Could not create temporary file for file transfer" );
277                g_free( px );
278                g_free( px->fn );
279                return;
280        }
281       
282        px->ic = ic;
283        px->handle = g_strdup( handle );
284       
285        imcb_log( ic, "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait..." );
286       
287        b_timeout_add( 0, purple_transfer_request_cb, ft );
288}
289
290static void purple_transfer_forward( struct file_transfer *ft )
291{
292        struct prpl_xfer_data *px = ft->data;
293        PurpleAccount *pa = px->ic->proto_data;
294       
295        /* xfer_new() will pick up this variable. It's a hack but we're not
296           multi-threaded anyway. */
297        next_ft = ft;
298        serv_send_file( purple_account_get_connection( pa ), px->handle, px->fn );
299}
300
301static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond )
302{
303        file_transfer_t *ft = data;
304        struct prpl_xfer_data *px = ft->data;
305       
306        if( ft->write == NULL )
307        {
308                ft->write = prpl_xfer_write;
309                imcb_file_recv_start( px->ic, ft );
310        }
311       
312        ft->write_request( ft );
313       
314        return FALSE;
315}
316
317static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len )
318{
319        struct prpl_xfer_data *px = ft->data;
320       
321        if( write( px->fd, buffer, len ) != len )
322        {
323                imcb_file_canceled( px->ic, ft, "Error while writing temporary file" );
324                return FALSE;
325        }
326       
327        if( lseek( px->fd, 0, SEEK_CUR ) >= ft->file_size )
328        {
329                close( px->fd );
330                px->fd = -1;
331               
332                purple_transfer_forward( ft );
333                imcb_file_finished( px->ic, ft );
334                px->ft = NULL;
335        }
336        else
337                b_timeout_add( 0, purple_transfer_request_cb, ft );
338       
339        return TRUE;
340}
341
342
343
344PurpleXferUiOps bee_xfer_uiops =
345{
346        prplcb_xfer_new,
347        prplcb_xfer_destroy,
348        NULL, /* prplcb_xfer_add, */
349        prplcb_xfer_progress,
350        prplcb_xfer_dbg,
351        prplcb_xfer_cancel_remote,
352        NULL,
353        NULL,
354        prplcb_xfer_dbg,
355};
Note: See TracBrowser for help on using the repository browser.