source: protocols/purple/ft.c @ f85e9d6

Last change on this file since f85e9d6 was 75c3ff7, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-21T00:09:29Z

Fixed sending with proper filenames by creating a temporary directory with
the file in it; protocol modules are mostly hardcoded to use the filename
from the filesystem with no way to override this.

Also improved robustness a little bit.

  • Property mode set to 100644
File size: 9.4 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               
78                purple_xfer_set_local_filename( xfer, px->fn );
79               
80                /* Sadly the xfer struct is still empty ATM so come back after
81                   the caller is done. */
82                b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer );
83        }
84        else
85        {
86                struct file_transfer *ft = next_ft;
87                struct prpl_xfer_data *px = ft->data;
88               
89                xfer->ui_data = px;
90                px->xfer = xfer;
91               
92                next_ft = NULL;
93        }
94}
95
96static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond )
97{
98        PurpleXfer *xfer = data;
99        struct im_connection *ic = purple_ic_by_pa( xfer->account );
100        struct prpl_xfer_data *px = xfer->ui_data;
101        PurpleBuddy *buddy;
102        const char *who;
103       
104        buddy = purple_find_buddy( xfer->account, xfer->who );
105        who = buddy ? purple_buddy_get_name( buddy ) : xfer->who;
106       
107        /* TODO(wilmer): After spreading some more const goodness in BitlBee,
108           remove the evil cast below. */
109        px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size );
110        px->ft->data = px;
111       
112        px->ft->accept = prpl_xfer_accept;
113        px->ft->canceled = prpl_xfer_canceled;
114        px->ft->write_request = prpl_xfer_write_request;
115       
116        return FALSE;
117}
118
119gboolean try_write_to_ui( gpointer data, gint fd, b_input_condition cond )
120{
121        struct file_transfer *ft = data;
122        struct prpl_xfer_data *px = ft->data;
123        struct stat fs;
124        off_t tx_bytes;
125       
126        /* If we don't have the file opened yet, there's no data so wait. */
127        if( px->fd < 0 || !px->ui_wants_data )
128                return FALSE;
129       
130        tx_bytes = lseek( px->fd, 0, SEEK_CUR );
131        fstat( px->fd, &fs );
132       
133        if( fs.st_size > tx_bytes )
134        {
135                char buf[1024];
136                size_t n = MIN( fs.st_size - tx_bytes, sizeof( buf ) );
137               
138                if( read( px->fd, buf, n ) == n && ft->write( ft, buf, n ) )
139                {
140                        px->ui_wants_data = FALSE;
141                }
142                else
143                {
144                        purple_xfer_cancel_local( px->xfer );
145                        imcb_file_canceled( ft, "Read error" );
146                }
147        }
148       
149        if( lseek( px->fd, 0, SEEK_CUR ) == px->xfer->size )
150        {
151                /*purple_xfer_end( px->xfer );*/
152                imcb_file_finished( ft );
153        }
154       
155        return FALSE;
156}
157
158/* UI calls this when its buffer is empty and wants more data to send to the user. */
159static gboolean prpl_xfer_write_request( struct file_transfer *ft )
160{
161        struct prpl_xfer_data *px = ft->data;
162       
163        px->ui_wants_data = TRUE;
164        try_write_to_ui( ft, 0, 0 );
165       
166        return FALSE;
167}
168
169
170/* Generic (IM<>UI): */
171static void prplcb_xfer_destroy( PurpleXfer *xfer )
172{
173        struct prpl_xfer_data *px = xfer->ui_data;
174       
175        g_free( px->fn );
176        g_free( px->handle );
177        if( px->fd >= 0 )
178                close( px->fd );
179        g_free( px );
180}
181
182static void prplcb_xfer_progress( PurpleXfer *xfer, double percent )
183{
184        struct prpl_xfer_data *px = xfer->ui_data;
185       
186        if( px == NULL )
187                return;
188       
189        if( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND )
190        {
191                if( *px->fn )
192                {
193                        char *slash;
194                       
195                        unlink( px->fn );
196                        if( ( slash = strrchr( px->fn, '/' ) ) )
197                        {
198                                *slash = '\0';
199                                rmdir( px->fn );
200                        }
201                        *px->fn = '\0';
202                }
203               
204                return;
205        }
206       
207        if( px->fd == -1 && percent > 0 )
208        {
209                /* Weeeeeeeee, we're getting data! That means the file exists
210                   by now so open it and start sending to the UI. */
211                px->fd = open( px->fn, O_RDONLY );
212               
213                /* Unlink it now, because we don't need it after this. */
214                unlink( px->fn );
215        }
216       
217        if( percent < 1 )
218                try_write_to_ui( px->ft, 0, 0 );
219        else
220                /* Another nice problem: If we have the whole file, it only
221                   gets closed when we return. Problem: There may still be
222                   stuff buffered and not written, we'll only see it after
223                   the caller close()s the file. So poll the file after that. */
224                b_timeout_add( 0, try_write_to_ui, px->ft );
225}
226
227static void prplcb_xfer_cancel_remote( PurpleXfer *xfer )
228{
229        struct prpl_xfer_data *px = xfer->ui_data;
230       
231        if( px->ft )
232                imcb_file_canceled( px->ft, "Canceled by remote end" );
233        else
234                /* px->ft == NULL for sends, because of the two stages. :-/ */
235                imcb_error( px->ic, "File transfer cancelled by remote end" );
236}
237
238static void prplcb_xfer_dbg( PurpleXfer *xfer )
239{
240        fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer );
241}
242
243
244/* Sending files (UI->IM): */
245static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len );
246static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond );
247
248void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle )
249{
250        struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
251        char *dir, *basename;
252       
253        ft->data = px;
254        px->ft = ft;
255       
256        dir = g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" );
257        if( !mkdtemp( dir ) )
258        {
259                imcb_error( ic, "Could not create temporary file for file transfer" );
260                g_free( px );
261                g_free( dir );
262                return;
263        }
264       
265        if( ( basename = strrchr( ft->file_name, '/' ) ) )
266                basename++;
267        else
268                basename = ft->file_name;
269        px->fn = g_strdup_printf( "%s/%s", dir, basename );
270        px->fd = open( px->fn, O_WRONLY | O_CREAT, 0600 );
271        g_free( dir );
272       
273        if( px->fd < 0 )
274        {
275                imcb_error( ic, "Could not create temporary file for file transfer" );
276                g_free( px );
277                g_free( px->fn );
278                return;
279        }
280       
281        px->ic = ic;
282        px->handle = g_strdup( handle );
283       
284        imcb_log( ic, "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait..." );
285       
286        b_timeout_add( 0, purple_transfer_request_cb, ft );
287}
288
289static void purple_transfer_forward( struct file_transfer *ft )
290{
291        struct prpl_xfer_data *px = ft->data;
292        PurpleAccount *pa = px->ic->proto_data;
293       
294        /* xfer_new() will pick up this variable. It's a hack but we're not
295           multi-threaded anyway. */
296        next_ft = ft;
297        serv_send_file( purple_account_get_connection( pa ), px->handle, px->fn );
298}
299
300static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond )
301{
302        file_transfer_t *ft = data;
303       
304        if( ft->write == NULL )
305        {
306                ft->write = prpl_xfer_write;
307                imcb_file_recv_start( ft );
308        }
309       
310        ft->write_request( ft );
311       
312        return FALSE;
313}
314
315static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len )
316{
317        struct prpl_xfer_data *px = ft->data;
318       
319        if( write( px->fd, buffer, len ) != len )
320        {
321                imcb_file_canceled( ft, "Error while writing temporary file" );
322                return FALSE;
323        }
324       
325        if( lseek( px->fd, 0, SEEK_CUR ) >= ft->file_size )
326        {
327                close( px->fd );
328                px->fd = -1;
329               
330                purple_transfer_forward( ft );
331                imcb_file_finished( ft );
332                px->ft = NULL;
333        }
334        else
335                b_timeout_add( 0, purple_transfer_request_cb, ft );
336       
337        return TRUE;
338}
339
340
341
342PurpleXferUiOps bee_xfer_uiops =
343{
344        prplcb_xfer_new,
345        prplcb_xfer_destroy,
346        NULL, /* prplcb_xfer_add, */
347        prplcb_xfer_progress,
348        prplcb_xfer_dbg,
349        prplcb_xfer_cancel_remote,
350        NULL,
351        NULL,
352        prplcb_xfer_dbg,
353};
Note: See TracBrowser for help on using the repository browser.