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