/*
 dcc-send-limiter.c : Limit the transmit speed of DCC sends

 For irssi 0.8+

 compile:
   export IRSSI=~/cvs/irssi
   gcc dcc-send-limiter.c -o ~/.irssi/modules/libdcc_send_limiter.so -g -shared -I$IRSSI -I$IRSSI/src -I$IRSSI/src/core -I$IRSSI/src/irc/core -I$IRSSI/src/irc/dcc `glib-config --cflags` -O

 usage:
   /LOAD dcc_send_limiter

    Copyright (C) 2001 Timo Sirainen

    Modified 2002/12/31 by Piotr Krukowiecki (Happy New Year! ;))
    	* fixed unnecesary lag in sending data when send is resume
    	* sends that were started before the module was loaded 
    	  now are being limited as well

    Modified 2001/07/04 by Martin Persson
    	* updated to only keep track of the last 30 sec

    Modified 2001/07/01 by Martin Persson
    	* added speed send checks
    	* fixed crash when destroying dcc sends 
    	  that didn't contain any module data
    	* fixed crash when initiating dcc send


    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#define MODULE_NAME "irc/dcc/limiter"
#define HAVE_CONFIG_H

#include "common.h"
#include "signals.h"
#include "network.h"
#include "settings.h"

#include "irc.h"
#include "dcc-send.h"

typedef struct {
	int timeout_tag;

	unsigned long skip_bytes;
	unsigned long starttime;
	unsigned long max_speed;
} MODULE_SEND_DCC_REC;

static void dcc_send_data(SEND_DCC_REC *dcc);

static void reset_dcc_send(SEND_DCC_REC *dcc)
{
	MODULE_SEND_DCC_REC *mdcc;

	if (g_slist_find(dcc_conns, dcc) == NULL) {
		/* the DCC was closed during the wait */
		return;
	}

	mdcc = MODULE_DATA(dcc);
	g_source_remove(mdcc->timeout_tag);
	mdcc->timeout_tag = -1;

	dcc->tagwrite = g_input_add(dcc->handle, G_INPUT_WRITE,
				    (GInputFunction) dcc_send_data, dcc);
}

static int sent_too_much(SEND_DCC_REC *dcc, MODULE_SEND_DCC_REC *mdcc)
{
	GTimeVal gtv;
	unsigned long timediff, curtime;
	unsigned long transfd, speed;

	/* 0 == unlimited speed */
	if (mdcc->max_speed == 0) return 0;

	/* get time difference in milliseconds */
	g_get_current_time(&gtv);
	curtime = (gtv.tv_sec * 1000) + (gtv.tv_usec / 1000);

	transfd = (dcc->transfd - mdcc->skip_bytes);
	timediff = curtime - mdcc->starttime + 1;
	speed = ((transfd * 1000) / timediff);

	/* reset speed counter every 30 seconds */
	if (timediff >= 30000) {
		mdcc->starttime = curtime;
		mdcc->skip_bytes = dcc->transfd;
	}

	return (speed > (mdcc->max_speed * 1024));
}

/* input function: DCC SEND - we're ready to send more data */
static void dcc_send_data(SEND_DCC_REC *dcc)
{
	MODULE_SEND_DCC_REC *mdcc;
	char buffer[512];
	int ret, max_speed;
	GTimeVal gtv;

	mdcc = MODULE_DATA(dcc);

	max_speed = settings_get_int("dcc_send_top_speed");
	if (max_speed != mdcc->max_speed) {
		/* speed setting has changed, calculate speed from current position 
		   instead of from the start to eliminate speed boosts/slowdowns */

		mdcc->max_speed = max_speed;
		mdcc->skip_bytes = dcc->transfd;

		g_get_current_time(&gtv);
		mdcc->starttime = (gtv.tv_sec * 1000) + (gtv.tv_usec / 1000);
	}

	if (sent_too_much(dcc, mdcc)) {
		/* disable calling this function for 1/10th of a second. */
		g_source_remove(dcc->tagwrite);
		dcc->tagwrite = -1;
		mdcc->timeout_tag =
			g_timeout_add(100, (GSourceFunc) reset_dcc_send, dcc);
		return;
	}

	ret = read(dcc->fhandle, buffer, sizeof(buffer));
	if (ret <= 0) {
		/* no need to call this function anymore..
		   in fact it just eats all the cpu.. */
		dcc->waitforend = TRUE;
		g_source_remove(dcc->tagwrite);
		dcc->tagwrite = -1;
		return;
	}

	ret = net_transmit(dcc->handle, buffer, ret);
	if (ret > 0) dcc->transfd += ret;
	dcc->gotalldata = FALSE;

	lseek(dcc->fhandle, dcc->transfd, SEEK_SET);

	signal_emit("dcc transfer update", 1, dcc);
}

static void sig_dcc_connected(SEND_DCC_REC *dcc)
{
	MODULE_SEND_DCC_REC *mdcc;
	GTimeVal gtv;

	if (!IS_DCC_SEND(dcc))
		return;

	mdcc = g_new0(MODULE_SEND_DCC_REC, 1);
	MODULE_DATA_SET(dcc, mdcc);
	mdcc->timeout_tag = -1;
	mdcc->skip_bytes = dcc->transfd; /* now it works correct with dcc resume - doesn't wait 30s with sending data */
	mdcc->max_speed = settings_get_int("dcc_send_top_speed");

	/* get starttime in milliseconds */
	g_get_current_time(&gtv);
	mdcc->starttime = (gtv.tv_sec * 1000) + (gtv.tv_usec / 1000);

	g_source_remove(dcc->tagwrite);
	dcc->tagwrite = g_input_add(dcc->handle, G_INPUT_WRITE,
					(GInputFunction) dcc_send_data, dcc);
}

static void sig_dcc_destroyed(SEND_DCC_REC *dcc)
{
	MODULE_SEND_DCC_REC *mdcc;

	if (!IS_DCC_SEND(dcc))
		return;

	mdcc = MODULE_DATA(dcc);
	if (mdcc != NULL) {
		if (mdcc->timeout_tag != -1)
			g_source_remove(mdcc->timeout_tag);

		g_free(mdcc);
	}
}

void dcc_send_limiter_init(void)
{
	GSList *tmp;
	settings_add_int("dcc", "dcc_send_top_speed", 30);

	signal_add_last("dcc connected", (SIGNAL_FUNC) sig_dcc_connected);
	signal_add_first("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
	
	/* Limit already existing sends */
	for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) {
		SEND_DCC_REC *dcc = tmp->data;
		if (!IS_DCC_SEND(dcc)) continue;
		if (!dcc_is_connected(dcc)) continue;
		
		sig_dcc_connected(dcc);
	}

        module_register("dcc_send_limiter", "core");
}

void dcc_send_limiter_deinit(void)
{
	signal_remove("dcc connected", (SIGNAL_FUNC) sig_dcc_connected);
	signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
}


syntax highlighted by Code2HTML, v. 0.9.1