source: protocols/purple/ft.c @ 73f0a01

Last change on this file since 73f0a01 was d93c8beb, checked in by dequis <dx@…>, at 2015-03-02T03:27:16Z

purple: move PurpleAccount from proto_data in a struct purple_data

  • 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#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
234static void prplcb_xfer_dbg(PurpleXfer *xfer)
235{
236        fprintf(stderr, "prplcb_xfer_dbg 0x%p\n", xfer);
237}
238
239
240/* Sending files (UI->IM): */
241static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len);
242static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond);
243
244void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle)
245{
246        struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);
247        char *dir, *basename;
248
249        ft->data = px;
250        px->ft = ft;
251
252        dir = g_strdup("/tmp/bitlbee-purple-ft.XXXXXX");
253        if (!mkdtemp(dir)) {
254                imcb_error(ic, "Could not create temporary file for file transfer");
255                g_free(px);
256                g_free(dir);
257                return;
258        }
259
260        if ((basename = strrchr(ft->file_name, '/'))) {
261                basename++;
262        } else {
263                basename = ft->file_name;
264        }
265        px->fn = g_strdup_printf("%s/%s", dir, basename);
266        px->fd = open(px->fn, O_WRONLY | O_CREAT, 0600);
267        g_free(dir);
268
269        if (px->fd < 0) {
270                imcb_error(ic, "Could not create temporary file for file transfer");
271                g_free(px);
272                g_free(px->fn);
273                return;
274        }
275
276        px->ic = ic;
277        px->handle = g_strdup(handle);
278
279        imcb_log(ic,
280                 "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait...");
281
282        b_timeout_add(0, purple_transfer_request_cb, ft);
283}
284
285static void purple_transfer_forward(struct file_transfer *ft)
286{
287        struct prpl_xfer_data *px = ft->data;
288        struct purple_data *pd = px->ic->proto_data;
289
290        /* xfer_new() will pick up this variable. It's a hack but we're not
291           multi-threaded anyway. */
292        next_ft = ft;
293        serv_send_file(purple_account_get_connection(pd->account),
294                   px->handle, px->fn);
295}
296
297static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond)
298{
299        file_transfer_t *ft = data;
300        struct prpl_xfer_data *px = ft->data;
301
302        if (ft->write == NULL) {
303                ft->write = prpl_xfer_write;
304                imcb_file_recv_start(px->ic, ft);
305        }
306
307        ft->write_request(ft);
308
309        return FALSE;
310}
311
312static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len)
313{
314        struct prpl_xfer_data *px = ft->data;
315
316        if (write(px->fd, buffer, len) != len) {
317                imcb_file_canceled(px->ic, ft, "Error while writing temporary file");
318                return FALSE;
319        }
320
321        if (lseek(px->fd, 0, SEEK_CUR) >= ft->file_size) {
322                close(px->fd);
323                px->fd = -1;
324
325                purple_transfer_forward(ft);
326                imcb_file_finished(px->ic, ft);
327                px->ft = NULL;
328        } else {
329                b_timeout_add(0, purple_transfer_request_cb, ft);
330        }
331
332        return TRUE;
333}
334
335
336
337PurpleXferUiOps bee_xfer_uiops =
338{
339        prplcb_xfer_new,
340        prplcb_xfer_destroy,
341        NULL, /* prplcb_xfer_add, */
342        prplcb_xfer_progress,
343        prplcb_xfer_dbg,
344        prplcb_xfer_cancel_remote,
345        NULL,
346        NULL,
347        prplcb_xfer_dbg,
348};
Note: See TracBrowser for help on using the repository browser.