aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/purple/ft.c
blob: 3cdbb8dddafa2cb51d55d01b79da56f92fba95ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/***************************************************************************\
*                                                                           *
*  BitlBee - An IRC to IM gateway                                           *
*  libpurple module - File transfer stuff                                   *
*                                                                           *
*  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              *
*                                                                           *
*  This program is free software; you can redistribute it and/or modify     *
*  it under the terms of the GNU General Public License as published by     *
*  the Free Software Foundation; either version 2 of the License, or        *
*  (at your option) any later version.                                      *
*                                                                           *
*  This program is distributed in the hope that it will be useful,          *
*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
*  GNU General Public License for more details.                             *
*                                                                           *
*  You should have received a copy of the GNU General Public License along  *
*  with this program; if not, write to the Free Software Foundation, Inc.,  *
*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
*                                                                           *
\***************************************************************************/

/* Do file transfers via disk for now, since libpurple was really designed
   for straight-to/from disk fts and is only just learning how to pass the
   file contents the the UI instead (2.6.0 and higher it seems, and with
   varying levels of success). */

#include "bitlbee.h"
#include "bpurple.h"

#include <stdarg.h>

#include <glib.h>
#include <purple.h>

struct prpl_xfer_data {
	PurpleXfer *xfer;
	file_transfer_t *ft;
	struct im_connection *ic;
	int fd;
	char *fn, *handle;
	gboolean ui_wants_data;
};

static file_transfer_t *next_ft;

struct im_connection *purple_ic_by_pa(PurpleAccount *pa);
static gboolean prplcb_xfer_new_send_cb(gpointer data, gint fd, b_input_condition cond);
static gboolean prpl_xfer_write_request(struct file_transfer *ft);


/* Receiving files (IM->UI): */
static void prpl_xfer_accept(struct file_transfer *ft)
{
	struct prpl_xfer_data *px = ft->data;

	purple_xfer_request_accepted(px->xfer, NULL);
	prpl_xfer_write_request(ft);
}

static void prpl_xfer_canceled(struct file_transfer *ft, char *reason)
{
	struct prpl_xfer_data *px = ft->data;

	purple_xfer_request_denied(px->xfer);
}

static void prplcb_xfer_new(PurpleXfer *xfer)
{
	if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) {
		struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);

		xfer->ui_data = px;
		px->xfer = xfer;
		px->fn = mktemp(g_strdup("/tmp/bitlbee-purple-ft.XXXXXX"));
		px->fd = -1;
		px->ic = purple_ic_by_pa(xfer->account);

		purple_xfer_set_local_filename(xfer, px->fn);

		/* Sadly the xfer struct is still empty ATM so come back after
		   the caller is done. */
		b_timeout_add(0, prplcb_xfer_new_send_cb, xfer);
	} else {
		struct file_transfer *ft = next_ft;
		struct prpl_xfer_data *px = ft->data;

		xfer->ui_data = px;
		px->xfer = xfer;

		next_ft = NULL;
	}
}

static gboolean prplcb_xfer_new_send_cb(gpointer data, gint fd, b_input_condition cond)
{
	PurpleXfer *xfer = data;
	struct im_connection *ic = purple_ic_by_pa(xfer->account);
	struct prpl_xfer_data *px = xfer->ui_data;
	PurpleBuddy *buddy;
	const char *who;

	buddy = purple_find_buddy(xfer->account, xfer->who);
	who = buddy ? purple_buddy_get_name(buddy) : xfer->who;

	/* TODO(wilmer): After spreading some more const goodness in BitlBee,
	   remove the evil cast below. */
	px->ft = imcb_file_send_start(ic, (char *) who, xfer->filename, xfer->size);
	px->ft->data = px;

	px->ft->accept = prpl_xfer_accept;
	px->ft->canceled = prpl_xfer_canceled;
	px->ft->write_request = prpl_xfer_write_request;

	return FALSE;
}

gboolean try_write_to_ui(gpointer data, gint fd, b_input_condition cond)
{
	struct file_transfer *ft = data;
	struct prpl_xfer_data *px = ft->data;
	struct stat fs;
	off_t tx_bytes;

	/* If we don't have the file opened yet, there's no data so wait. */
	if (px->fd < 0 || !px->ui_wants_data) {
		return FALSE;
	}

	tx_bytes = lseek(px->fd, 0, SEEK_CUR);
	fstat(px->fd, &fs);

	if (fs.st_size > tx_bytes) {
		char buf[1024];
		size_t n = MIN(fs.st_size - tx_bytes, sizeof(buf));

		if (read(px->fd, buf, n) == n && ft->write(ft, buf, n)) {
			px->ui_wants_data = FALSE;
		} else {
			purple_xfer_cancel_local(px->xfer);
			imcb_file_canceled(px->ic, ft, "Read error");
		}
	}

	if (lseek(px->fd, 0, SEEK_CUR) == px->xfer->size) {
		/*purple_xfer_end( px->xfer );*/
		imcb_file_finished(px->ic, ft);
	}

	return FALSE;
}

/* UI calls this when its buffer is empty and wants more data to send to the user. */
static gboolean prpl_xfer_write_request(struct file_transfer *ft)
{
	struct prpl_xfer_data *px = ft->data;

	px->ui_wants_data = TRUE;
	try_write_to_ui(ft, 0, 0);

	return FALSE;
}


/* Generic (IM<>UI): */
static void prplcb_xfer_destroy(PurpleXfer *xfer)
{
	struct prpl_xfer_data *px = xfer->ui_data;

	g_free(px->fn);
	g_free(px->handle);
	if (px->fd >= 0) {
		close(px->fd);
	}
	g_free(px);
}

static void prplcb_xfer_progress(PurpleXfer *xfer, double percent)
{
	struct prpl_xfer_data *px = xfer->ui_data;

	if (px == NULL) {
		return;
	}

	if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
		if (*px->fn) {
			char *slash;

			unlink(px->fn);
			if ((slash = strrchr(px->fn, '/'))) {
				*slash = '\0';
				rmdir(px->fn);
			}
			*px->fn = '\0';
		}

		return;
	}

	if (px->fd == -1 && percent > 0) {
		/* Weeeeeeeee, we're getting data! That means the file exists
		   by now so open it and start sending to the UI. */
		px->fd = open(px->fn, O_RDONLY);

		/* Unlink it now, because we don't need it after this. */
		unlink(px->fn);
	}

	if (percent < 1) {
		try_write_to_ui(px->ft, 0, 0);
	} else {
		/* Another nice problem: If we have the whole file, it only
		   gets closed when we return. Problem: There may still be
		   stuff buffered and not written, we'll only see it after
		   the caller close()s the file. So poll the file after that. */
		b_timeout_add(0, try_write_to_ui, px->ft);
	}
}

static void prplcb_xfer_cancel_remote(PurpleXfer *xfer)
{
	struct prpl_xfer_data *px = xfer->ui_data;

	if (px->ft) {
		imcb_file_canceled(px->ic, px->ft, "Canceled by remote end");
	} else {
		/* px->ft == NULL for sends, because of the two stages. :-/ */
		imcb_error(px->ic, "File transfer cancelled by remote end");
	}
}


/* Sending files (UI->IM): */
static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len);
static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond);

void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle)
{
	struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1);
	char *dir, *basename;

	ft->data = px;
	px->ft = ft;

	dir = g_strdup("/tmp/bitlbee-purple-ft.XXXXXX");
	if (!mkdtemp(dir)) {
		imcb_error(ic, "Could not create temporary file for file transfer");
		g_free(px);
		g_free(dir);
		return;
	}

	if ((basename = strrchr(ft->file_name, '/'))) {
		basename++;
	} else {
		basename = ft->file_name;
	}
	px->fn = g_strdup_printf("%s/%s", dir, basename);
	px->fd = open(px->fn, O_WRONLY | O_CREAT, 0600);
	g_free(dir);

	if (px->fd < 0) {
		imcb_error(ic, "Could not create temporary file for file transfer");
		g_free(px);
		g_free(px->fn);
		return;
	}

	px->ic = ic;
	px->handle = g_strdup(handle);

	imcb_log(ic,
	         "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait...");

	b_timeout_add(0, purple_transfer_request_cb, ft);
}

static void purple_transfer_forward(struct file_transfer *ft)
{
	struct prpl_xfer_data *px = ft->data;
	struct purple_data *pd = px->ic->proto_data;

	/* xfer_new() will pick up this variable. It's a hack but we're not
	   multi-threaded anyway. */
	next_ft = ft;
	serv_send_file(purple_account_get_connection(pd->account),
                   px->handle, px->fn);
}

static gboolean purple_transfer_request_cb(gpointer data, gint fd, b_input_condition cond)
{
	file_transfer_t *ft = data;
	struct prpl_xfer_data *px = ft->data;

	if (ft->write == NULL) {
		ft->write = prpl_xfer_write;
		imcb_file_recv_start(px->ic, ft);
	}

	ft->write_request(ft);

	return FALSE;
}

static gboolean prpl_xfer_write(struct file_transfer *ft, char *buffer, unsigned int len)
{
	struct prpl_xfer_data *px = ft->data;

	if (write(px->fd, buffer, len) != len) {
		imcb_file_canceled(px->ic, ft, "Error while writing temporary file");
		return FALSE;
	}

	if (lseek(px->fd, 0, SEEK_CUR) >= ft->file_size) {
		close(px->fd);
		px->fd = -1;

		purple_transfer_forward(ft);
		imcb_file_finished(px->ic, ft);
		px->ft = NULL;
	} else {
		b_timeout_add(0, purple_transfer_request_cb, ft);
	}

	return TRUE;
}



PurpleXferUiOps bee_xfer_uiops =
{
	prplcb_xfer_new,
	prplcb_xfer_destroy,
	NULL, /* prplcb_xfer_add, */
	prplcb_xfer_progress,
	NULL,
	prplcb_xfer_cancel_remote,
	NULL,
	NULL,
	NULL,
};