aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/purple/ft.c
blob: d1484054b4f832aa53e0dcd424230286fccfff3e (plain)
1
2
3
4
5
6
7
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
-include ../Makefile.settings

all:
	# Only build the docs if this is a bzr checkout
	test ! -d ../.bzr || $(MAKE) -C user-guide

install:
	mkdir -p $(DESTDIR)$(MANDIR)/man8/ $(DESTDIR)$(MANDIR)/man5/
	install -m 0644 bitlbee.8 $(DESTDIR)$(MANDIR)/man8/
	install -m 0644 bitlbee.conf.5 $(DESTDIR)$(MANDIR)/man5/
	$(MAKE) -C user-guide $@

uninstall:
	rm -f $(DESTDIR)$(MANDIR)/man8/bitlbee.8*
	rm -f $(DESTDIR)$(MANDIR)/man5/bitlbee.conf.5*
	$(MAKE) -C user-guide $@

.PHONY: install uninstall
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 344 345 346 347 348
/***************************************************************************\
*                                                                           *
*  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");
	}
}

static void prplcb_xfer_dbg(PurpleXfer *xfer)
{
	fprintf(stderr, "prplcb_xfer_dbg 0x%p\n", xfer);
}


/* 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,
	prplcb_xfer_dbg,
	prplcb_xfer_cancel_remote,
	NULL,
	NULL,
	prplcb_xfer_dbg,
};