source: protocols/purple/ft.c @ 9077580

Last change on this file since 9077580 was 98d46d5, checked in by dequis <dx@…>, at 2015-12-26T03:18:53Z

purple: document original names of uiops next to them

  • 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#include "bpurple.h"
31
32#include <stdarg.h>
33
34#include <glib.h>
35#include <purple.h>
36
37struct prpl_xfer_data {
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
58        purple_xfer_request_accepted(px->xfer, NULL);
59        prpl_xfer_write_request(ft);
60}
61
62static void prpl_xfer_canceled(struct file_transfer *ft, char *reason)
63{
64        struct prpl_xfer_data *px = ft->data;
65
66        purple_xfer_request_denied(px->xfer);
67}
68
69static void prplcb_xfer_new(PurpleXfer *xfer)
70{
71        if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) {
72                struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);
73
74                xfer->ui_data = px;
75                px->xfer = xfer;
76                px->fn = mktemp(g_strdup("/tmp/bitlbee-purple-ft.XXXXXX"));
77                px->fd = -1;
78                px->ic = purple_ic_by_pa(xfer->account);
79
80                purple_xfer_set_local_filename(xfer, px->fn);
81
82                /* Sadly the xfer struct is still empty ATM so come back after
83                   the caller is done. */
84                b_timeout_add(0, prplcb_xfer_new_send_cb, xfer);
85        } else {
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
131        tx_bytes = lseek(px->fd, 0, SEEK_CUR);
132        fstat(px->fd, &fs);
133
134        if (fs.st_size > tx_bytes) {
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                        px->ui_wants_data = FALSE;
140                } else {
141                        purple_xfer_cancel_local(px->xfer);
142                        imcb_file_canceled(px->ic, ft, "Read error");
143                }
144        }
145
146        if (lseek(px->fd, 0, SEEK_CUR) == px->xfer->size) {
147                /*purple_xfer_end( px->xfer );*/
148                imcb_file_finished(px->ic, ft);
149        }
150
151        return FALSE;
152}
153
154/* UI calls this when its buffer is empty and wants more data to send to the user. */
155static gboolean prpl_xfer_write_request(struct file_transfer *ft)
156{
157        struct prpl_xfer_data *px = ft->data;
158
159        px->ui_wants_data = TRUE;
160        try_write_to_ui(ft, 0, 0);
161
162        return FALSE;
163}
164
165
166/* Generic (IM<>UI): */
167static void prplcb_xfer_destroy(PurpleXfer *xfer)
168{
169        struct prpl_xfer_data *px = xfer->ui_data;
170
171        g_free(px->fn);
172        g_free(px->handle);
173        if (px->fd >= 0) {
174                close(px->fd);
175        }
176        g_free(px);
177}
178
179static void prplcb_xfer_progress(PurpleXfer *xfer, double percent)
180{
181        struct prpl_xfer_data *px = xfer->ui_data;
182
183        if (px == NULL) {
184                return;
185        }
186
187        if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
188                if (*px->fn) {
189                        char *slash;
190
191                        unlink(px->fn);
192                        if ((slash = strrchr(px->fn, '/'))) {
193                                *slash = '\0';
194                                rmdir(px->fn);
195                        }
196                        *px->fn = '\0';
197                }
198
199                return;
200        }
201
202        if (px->fd == -1 && percent > 0) {
203                /* Weeeeeeeee, we're getting data! That means the file exists
204                   by now so open it and start sending to the UI. */
205                px->fd = open(px->fn, O_RDONLY);
206
207                /* Unlink it now, because we don't need it after this. */
208                unlink(px->fn);
209        }
210
211        if (percent < 1) {
212                try_write_to_ui(px->ft, 0, 0);
213        } else {
214                /* Another nice problem: If we have the whole file, it only
215                   gets closed when we return. Problem: There may still be
216                   stuff buffered and not written, we'll only see it after
217                   the caller close()s the file. So poll the file after that. */
218                b_timeout_add(0, try_write_to_ui, px->ft);
219        }
220}
221
222static void prplcb_xfer_cancel_remote(PurpleXfer *xfer)
223{
224        struct prpl_xfer_data *px = xfer->ui_data;
225
226        if (px->ft) {
227                imcb_file_canceled(px->ic, px->ft, "Canceled by remote end");
228        } else {
229                /* px->ft == NULL for sends, because of the two stages. :-/ */
230                imcb_error(px->ic, "File transfer cancelled by remote end");
231        }
232}
233
234
235/* Sending files (UI->IM): */
236static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len);
237static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond);
238
239void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle)
240{
241        struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);
242        char *dir, *basename;
243
244        ft->data = px;
245        px->ft = ft;
246
247        dir = g_strdup("/tmp/bitlbee-purple-ft.XXXXXX");
248        if (!mkdtemp(dir)) {
249                imcb_error(ic, "Could not create temporary file for file transfer");
250                g_free(px);
251                g_free(dir);
252                return;
253        }
254
255        if ((basename = strrchr(ft->file_name, '/'))) {
256                basename++;
257        } else {
258                basename = ft->file_name;
259        }
260        px->fn = g_strdup_printf("%s/%s", dir, basename);
261        px->fd = open(px->fn, O_WRONLY | O_CREAT, 0600);
262        g_free(dir);
263
264        if (px->fd < 0) {
265                imcb_error(ic, "Could not create temporary file for file transfer");
266                g_free(px);
267                g_free(px->fn);
268                return;
269        }
270
271        px->ic = ic;
272        px->handle = g_strdup(handle);
273
274        imcb_log(ic,
275                 "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait...");
276
277        b_timeout_add(0, purple_transfer_request_cb, ft);
278}
279
280static void purple_transfer_forward(struct file_transfer *ft)
281{
282        struct prpl_xfer_data *px = ft->data;
283        struct purple_data *pd = px->ic->proto_data;
284
285        /* xfer_new() will pick up this variable. It's a hack but we're not
286           multi-threaded anyway. */
287        next_ft = ft;
288        serv_send_file(purple_account_get_connection(pd->account),
289                   px->handle, px->fn);
290}
291
292static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond)
293{
294        file_transfer_t *ft = data;
295        struct prpl_xfer_data *px = ft->data;
296
297        if (ft->write == NULL) {
298                ft->write = prpl_xfer_write;
299                imcb_file_recv_start(px->ic, ft);
300        }
301
302        ft->write_request(ft);
303
304        return FALSE;
305}
306
307static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len)
308{
309        struct prpl_xfer_data *px = ft->data;
310
311        if (write(px->fd, buffer, len) != len) {
312                imcb_file_canceled(px->ic, ft, "Error while writing temporary file");
313                return FALSE;
314        }
315
316        if (lseek(px->fd, 0, SEEK_CUR) >= ft->file_size) {
317                close(px->fd);
318                px->fd = -1;
319
320                purple_transfer_forward(ft);
321                imcb_file_finished(px->ic, ft);
322                px->ft = NULL;
323        } else {
324                b_timeout_add(0, purple_transfer_request_cb, ft);
325        }
326
327        return TRUE;
328}
329
330
331
332PurpleXferUiOps bee_xfer_uiops =
333{
334        prplcb_xfer_new,           /* new_xfer */
335        prplcb_xfer_destroy,       /* destroy */
336        NULL,                      /* add_xfer */
337        prplcb_xfer_progress,      /* update_progress */
338        NULL,                      /* cancel_local */
339        prplcb_xfer_cancel_remote, /* cancel_remote */
340        NULL,                      /* ui_write */
341        NULL,                      /* ui_read */
342        NULL,                      /* data_not_sent */
343};
Note: See TracBrowser for help on using the repository browser.