#define AUTO_VERSION "1.00"

/*
 *
 * Written by Colten Edwards. (C) Nov 11/99 
 *
 */

#include "irc.h"
#include "struct.h"
#include "dcc.h"
#include "ircaux.h"
#include "ctcp.h"
#include "cdcc.h"
#include "input.h"
#include "status.h"
#include "lastlog.h"
#include "screen.h"
#include "vars.h" 
#include "misc.h"
#include "output.h"
#include "module.h"
#include "hook.h"
#include "hash2.h"
#include "bsdglob.h"
#define INIT_MODULE
#include "modval.h"

#include <sys/time.h>
#include <sys/stat.h>

#define cparse convert_output_format
#define FS "%PFS%w:%n"

#define DEFAULT_IMPRESS_TIME 30
#define DEFAULT_FSERV 1
#define DEFAULT_IMPRESS 0
#define DEFAULT_MAX_MATCH 4
#define DEFAULT_RECURSE 1
#define DEFAULT_FILEMASK "*.mp3"


char fserv_version[] = "Fserv 1.000";
char *fserv_filename = NULL;
char FSstr[80] = "FS:";

typedef struct _Stats {
	unsigned long total_files;
	unsigned long total_filesize;
	unsigned long files_served;
	unsigned long filesize_served;
	double max_speed;
	time_t starttime;
} Stats;

Stats statistics = { 0, };

/*
 * ideas.
 * make it notice instead of msg the nick.
 * make it allow /msg nick !filename
 */

#if 0
<Lena-:#mp3jukebox> <><>< For My List(1069 files: 4307MB) and DCC Status Type
   @Lena- and @Lena--stats [(1/10) Slots Taken (0/10) Ques
   Taken] [Open Slot Ready] [Bandwidth in Use: 3081cps]
   [Highest Cps Record: 56.3kb/s by Grey13] [Total Files
   Served: 6045] ><><>
<Gaff> [ !Gaff Is It Too Late Now.mp3 ] [2.6MB 128Kbps 44.1Khz Joint Stereo
    (2m44s)]-SpR-@Gaff -> request my list-@Gaff-que -> check
    que-@Gaff-stats -> check DCC stats-@Gaff-remove -> cancel
    song request
#endif                                                                                                                        

typedef struct _AUDIO_HEADER {
	int IDex;
	int ID;
	int layer;
	int protection_bit;
	int bitrate_index;
	int sampling_frequency;
	int padding_bit;
	int private_bit;
	int mode; /* 0 = STEREO 1 = Joint 2 = DUAL 3 = Mono */
	int mode_extension;
	int copyright;
	int original;
	int emphasis;
	int stereo;
	int jsbound;
	int sblimit;
	int true_layer;
	int framesize;
} AUDIO_HEADER;


static unsigned char _buffer[32];
static int _bptr = 0;


typedef struct _files {
	struct _files	*next;
	char		*filename;
	unsigned long	filesize;
	time_t		time;
	int		bitrate;
	int		freq;
	int		stereo;
	int		id3;
} Files;

Files *fserv_files = NULL;


char *mode_str(int mode)
{
	switch(mode)
	{	
		case 0:
			return "Stereo";
		case 1:
			return "Joint-Stereo";
		case 2:
			return "Dual-Channel";
		case 3:
			return "Mono";
	}
	return empty_string;
}

char *print_time(time_t input)
{
	static char	buff[40];
	time_t		seconds,
			minutes;
	seconds = input;
	minutes = seconds / 60;
	seconds = seconds % 60;
	sprintf(buff, "%02u:%02u", (unsigned int)minutes, (unsigned int)seconds);
	return buff;
}                                        

char *make_mp3_string(FILE *fp, Files *f, char *fs, char *dirbuff)
{
	static char	buffer[BIG_BUFFER_SIZE+1];
	char		*s,
			*loc,
			*p,
			*fn;

	if (!fs || !*fs)
		return empty_string;
	memset(buffer, 0, sizeof(buffer));

	loc = LOCAL_COPY(f->filename);
	fn = strrchr(loc, '/');
	*fn++ = 0;
	if ((p = strrchr(loc, '/')))
		*p++ = 0;
	/* fn should point to the filename and p to the dir */
	/* 
	 * init the dir keeper 
	 * or cmp the old dir with the new
	 */
	if (dirbuff && (!*dirbuff || strcmp(dirbuff, p)))
	{
		strcpy(dirbuff, p);
		if (fp)
			fprintf(fp, "\nDirectory [ %s ]\n", dirbuff);
		else
			return NULL;
	}
	/* size bitrate [time] filename */
	s = buffer;
	while (*fs)
	{
		if (*fs == '%')
		{
			int prec = 0, fl = 0;
			fs++;
			if (isdigit(*fs))
			{
				prec = strtol(fs, &fs, 0);
				if (*fs == '.')
					fl = strtoul(fs+1, &fs, 0);
			}
			switch(*fs)
			{
				case '%':
					*s++ = *fs;
					break;
				case 'b':
					sprintf(s, "%*u", prec, f->bitrate);
					break;
				case 's':
					if (!prec) prec = 3;
					sprintf(s, "%*.*f%s", prec, fl, _GMKv(f->filesize), _GMKs(f->filesize));
					break;
				case 't':
					strcpy(s, print_time(f->time));
					break;
				case 'T':
					strcpy(s, ltoa(f->time));
					break;
				case 'f':
					strcpy(s, fn);
					break;
				case 'F':
					strcpy(s, f->filename);
					break;
				case 'S':
					strcpy(s, mode_str(f->stereo));
					break;
				case 'H':
					sprintf(s, "%*.*f", prec, fl, ((double)f->freq) / ((double)1000.0));
					break;
				case 'h':
					sprintf(s, "%*u", prec, f->freq);
					break;
				default:
					*s++ = *fs;
					break;
			}
		}
		else if (*fs == '\\')
		{
			fs++;
			switch(*fs)
			{
				case 'n':
					strcpy(s, "\n");
					break;
				case 't':
					strcpy(s, "\t");
					break;
				default:
					*s++ = *fs++;
			}
		}
		else
			*s++ = *fs;
		while (*s) s++;
		fs++;
	}
	if (fp && *buffer)
		fprintf(fp, buffer);
	return buffer;
}


int read_glob_dir(char *path, int globflags, glob_t *globpat, int recurse)
{
	char	buffer[BIG_BUFFER_SIZE+1];
	
	sprintf(buffer, "%s/*", path);
	bsd_glob(buffer, globflags, NULL, globpat);
	if (recurse)
	{
		int i = 0;
		int old_glpathc = globpat->gl_pathc;
		for (i = 0; i < old_glpathc; i++)
		{
			char *fn;
			fn = globpat->gl_pathv[i];
			if (fn[strlen(fn)-1] != '/')
				continue;
			sprintf(buffer, "%s*", fn);
			bsd_glob(buffer, globflags|GLOB_APPEND, NULL, globpat);
		}
		while (i < globpat->gl_pathc)
		{
			for (i = old_glpathc, old_glpathc = globpat->gl_pathc; i < old_glpathc; i++)
			{
				char *fn;
				fn = globpat->gl_pathv[i];
				if (fn[strlen(fn)-1] != '/')
					continue;
				sprintf(buffer, "%s*", fn);
				bsd_glob(buffer, globflags|GLOB_APPEND, NULL, globpat);
			}
		}
	}
	return 0;
}

unsigned int print_mp3(char *pattern, char *format, int freq, int number, int bitrate)
{
unsigned int count = 0;
Files *new;
char dir[BIG_BUFFER_SIZE];
char *fs = NULL;
	*dir = 0;
	for (new = fserv_files; new; new = new->next)
	{
		if (!pattern || (pattern && wild_match(pattern, new->filename)))
		{
			char *p;
			p = LOCAL_COPY(new->filename);
			p = strrchr(new->filename, '/');
			p++;
			if (do_hook(MODULE_LIST, "FS: File \"%s\" %s %u %lu %lu %u", p, mode_str(new->stereo), new->bitrate, new->time, new->filesize, new->freq))
			{
				if ((bitrate != -1) && (new->bitrate != bitrate))
					continue;
				if ((freq != -1) && (new->freq != freq))
					continue;
				if (!format || !*format)
					put_it("%s \"%s\" %s %dk [%s]", FSstr, p, mode_str(new->stereo), new->bitrate, print_time(new->time));
				else
				{
					if ((fs = make_mp3_string(NULL, new, format, dir)))
						put_it("%s %s", FSstr, fs);
					else
						put_it("%s %s", FSstr, make_mp3_string(NULL, new, format, dir));
				}
			}
			if ((number > 0) && (count == number))
				break;
			count++;
		}
	}
	return count;
}

BUILT_IN_DLL(print_fserv)
{
	int	count = 0;
	int	bitrate = -1;
	int	number = -1;
	int	freq = -1;
	char 	*fs_output = NULL;
	char	*tmp_pat = NULL;
	
	if ((get_dllstring_var("fserv_format")))
		fs_output = m_strdup(get_dllstring_var("fserv_format"));
	if (args && *args)
	{
		char *tmp;
		while ((tmp = next_arg(args, &args)) && *tmp)
		{
			int len;
			len = strlen(tmp);
			if (!my_strnicmp(tmp, "-BITRATE", len))
			{
				if ((tmp = next_arg(args, &args)))
					bitrate = (unsigned int) my_atol(tmp);
			}
			else if (!my_strnicmp(tmp, "-COUNT", len))
			{
				if ((tmp = next_arg(args, &args)))
					number = (unsigned int) my_atol(tmp);
			} 
			else if (!my_strnicmp(tmp, "-FREQ", 3))
			{
				if ((tmp = next_arg(args, &args)))
					freq = (unsigned int)my_atol(tmp);
			} 
			else if (!my_strnicmp(tmp, "-FORMAT", 3))
			{
				if ((tmp = new_next_arg(args, &args)))
					malloc_strcpy(&fs_output, tmp);
			} 
			else
			{
				count += print_mp3(tmp, fs_output, freq, number, bitrate);
				m_s3cat(&tmp_pat, " ", tmp);
			}
		}
	}
	else
		count += print_mp3(NULL, fs_output, freq, number, bitrate);
	
	if (do_hook(MODULE_LIST, "FS: Found %d %s", count, tmp_pat ? tmp_pat : "*"))
		put_it("%s found %d files matching \"%s\"", FSstr, count, tmp_pat ? tmp_pat : "*");
	new_free(&tmp_pat);
	new_free(&fs_output);
}

int _get_input(int file, unsigned char *bp, int size)
{
	if (read(file,  bp, size) != size)
		return -1;
	return 0;
}
                                        
static inline int readsync(int file)
{
	_bptr=0;
	_buffer[0]=_buffer[1];
	_buffer[1]=_buffer[2];
	_buffer[2]=_buffer[3];
	return _get_input(file, &_buffer[3], 1);
}


static inline int _fillbfr(int file, unsigned int size)
{
	_bptr=0;
        return _get_input(file, _buffer, size);
}


static inline unsigned int _getbits(int n)
{
	unsigned int	pos,
			ret_value;

        pos = _bptr >> 3;
	ret_value = _buffer[pos] << 24 |
		    _buffer[pos+1] << 16 |
		    _buffer[pos+2] << 8 |
		    _buffer[pos+3];
        ret_value <<= _bptr & 7;
        ret_value >>= 32 - n;
        _bptr += n;
        return ret_value;
}       


/*
 * header and side info parsing stuff ******************************************
 */
static inline void parse_header(AUDIO_HEADER *header) 
{
        header->IDex=_getbits(1);
        header->ID=_getbits(1);
        header->layer=_getbits(2);
        header->protection_bit=_getbits(1);
        header->bitrate_index=_getbits(4);
        header->sampling_frequency=_getbits(2);
        header->padding_bit=_getbits(1);
        header->private_bit=_getbits(1);
        header->mode=_getbits(2);
        header->mode_extension=_getbits(2);
        if (!header->mode) 
        	header->mode_extension=0;
        header->copyright=_getbits(1);
        header->original=_getbits(1);
        header->emphasis=_getbits(2);

	header->stereo = (header->mode == 3) ? 1 : 2;
	header->true_layer = 4 - header->layer;
}

int gethdr(int file, AUDIO_HEADER *header)
{
	int	retval;

	if ((retval=_fillbfr(file, 4))) 
		return retval;

	while (_getbits(11) != 0x7ff) 
	{
		if ((retval=readsync(file))!=0) 
			return retval;
	}
	parse_header(header);
	return 0;
}

long get_bitrate(char *filename, time_t *mp3_time, unsigned int *freq_rate, int *id3, unsigned long *filesize, int *stereo)
{
	short t_bitrate[2][3][15] = {{
	{0,32,48,56,64,80,96,112,128,144,160,176,192,224,256},
	{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160},
	{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160}
	},{
	{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448},
	{0,32,48,56,64,80,96,112,128,160,192,224,256,320,384},
	{0,32,40,48,56,64,80,96,112,128,160,192,224,256,320}
	}};

	int t_sampling_frequency[2][2][3] = {
		{ /* MPEG 2.5 samplerates */ 
  			{ 11025, 12000, 8000},
			{ 0,0,0 }
		},{ /* MPEG 2.0/1.0 samplerates */
			{ 22050 , 24000 , 16000},
			{ 44100 , 48000 , 32000}
		}
	};

	AUDIO_HEADER header;
	unsigned long btr = 0;
	int l = -1;
	struct stat	st;
	unsigned long	framesize = 0,
			totalframes = 0;
	
	if (freq_rate)
		*freq_rate =0;
	if (id3)
		*id3 = 0;
	if ((l = open(filename, O_RDONLY)) == -1) 
		return 0;
	gethdr(l, &header);
	if (header.ID > 1 || header.layer > 2 || header.bitrate_index > 14) 
	{
		close(l);
		return 0;
	}
	btr = t_bitrate[header.ID][3-header.layer][header.bitrate_index];

	fstat(l, &st);
	if (t_sampling_frequency[header.IDex][header.ID][header.sampling_frequency] > 0)
		framesize = (header.ID ? 144000 : 72000) * btr / t_sampling_frequency[header.IDex][header.ID][header.sampling_frequency];
	totalframes = (st.st_size / (framesize + 1)) - 1;
	if (t_sampling_frequency[header.IDex][header.ID][header.sampling_frequency] > 0)
		*mp3_time = (time_t) (totalframes * (header.ID==0?576:1152)/t_sampling_frequency[header.IDex][header.ID][header.sampling_frequency]);
	*filesize = st.st_size;

	if (freq_rate)
		*freq_rate = t_sampling_frequency[header.IDex][header.ID][header.sampling_frequency];
	if (id3)
	{
		char	buffer[200];
		lseek(l, SEEK_END, -128);
		if (read(l, buffer, 128) > 0)
			if (!strncmp(buffer, "TAG", 3))
				*id3 = 1;
	}
	*stereo = header.mode;
	close(l);	        
	return btr;
}



BUILT_IN_DLL(unload_fserv)
{
	Files	*new, *tmp;
	int	count = 0;
	if (!args || !*args)
	{
		while ((new = fserv_files))
		{
			tmp = fserv_files->next;
			new_free(&new->filename);
			statistics.total_filesize -= new->filesize;
			new_free(&new);
			fserv_files = tmp;
			count++;
		}
	} 
	else 
	{
		char	*pat;

		while ((pat = new_next_arg(args, &args)))	
		{
			if (!pat || !*pat)
				break;
			if ((new = (Files *)remove_from_list((List **)&fserv_files, pat)))
			{
				new_free(&new->filename);
				statistics.total_filesize -= new->filesize;
				new_free(&new);
				count++;
			}
		}
	}
	if (do_hook(MODULE_LIST, "FS: Clear %d", count))
		put_it("%s cleared %d entries", FSstr, count);
	statistics.total_files -= count;
}

off_t file_size (char *filename)
{
	struct stat statbuf;

	if (!stat(filename, &statbuf))
		return (off_t)(statbuf.st_size);
	else
		return -1;
}

unsigned int scan_mp3_dir(char *path, int recurse, int reload)
{
	int	mt = 0;
	glob_t	globpat;
	int	i = 0;
	Files	*new;
	int	count = 0;
	memset(&globpat, 0, sizeof(glob_t));
	read_glob_dir(path, GLOB_MARK|GLOB_NOSORT, &globpat, recurse);
	for (i = 0; i < globpat.gl_pathc; i++)
	{
		char	*fn;
		fn = globpat.gl_pathv[i];
		if (fn[strlen(fn)-1] == '/')
			continue;
		if (!(mt = wild_match(DEFAULT_FILEMASK, fn)))
			continue;
		if (reload && find_in_list((List **)&fserv_files, globpat.gl_pathv[i], 0))
			continue;
		new = (Files *) new_malloc(sizeof(Files));
		new->filename = m_strdup(fn);
		new->bitrate = get_bitrate(fn, &new->time, &new->freq, &new->id3, &new->filesize, &new->stereo);
		if (new->filesize)
		{
			add_to_list((List **)&fserv_files, (List *)new);
			statistics.total_files++;
			statistics.total_filesize += new->filesize;
			count++;
		}
		else
		{
			new_free(&new->filename);
			new_free(&new);
		}
	}
	bsd_globfree(&globpat);
	return count;
}

BUILT_IN_DLL(load_fserv)
{
	char	*path = NULL;
	int	recurse = DEFAULT_RECURSE;
	char	*pch;
	int	count = 0;
	int	reload = 0;
	
	if (command && !my_stricmp(command, "FSRELOAD"))
		reload = 1;
	if (args && *args)
	{
		while ((path = next_arg(args, &args)) && *path)
		{
			int len = strlen(path);
			if (!my_strnicmp(path, "-recurse", len))
			{
				recurse ^= 1;
				continue;
			}
			count += scan_mp3_dir(path, recurse, reload);
		}
		goto fs_print_out;
	}

	path = get_dllstring_var("fserv_dir");

	if (!path || !*path)
	{
		if (do_hook(MODULE_LIST, "FS: Error no fserv_dir path"))
			put_it("%s No path. /set fserv_dir first.", FSstr);
		return;
	}

	pch = LOCAL_COPY(path);
	while ((path = next_arg(pch, &pch)))
		count += scan_mp3_dir(path, recurse, reload);
fs_print_out:
	if (do_hook(MODULE_LIST, "FS: Load %d", count))
	{
		if (!fserv_files || !count)
			put_it("%s Could not read dir", FSstr);
		else
			put_it("%s found %d files", FSstr, count);
	}
	return;
}

Files *search_list(char *nick, char *pat, int wild)
{
	Files	*new;
	int	dcc_send_num = 0;
	int	dcc_queue_num = 0;
	char	*p;
	char	buffer[BIG_BUFFER_SIZE+1];
	int	num_to_match;

	num_to_match = get_dllint_var("fserv_max_match");
	if (wild)
	{
		int	count = 0;

		sprintf(buffer, "*%s*", pat);
		while ((p = strchr(buffer, ' ')))
			*p = '*';
		dcc_send_num = get_active_count();
		dcc_queue_num = get_num_queue();
		for (new = fserv_files; new; new = new->next)
		{
			p = strrchr(new->filename, '/');			
			p++;
			if (!wild_match(buffer, p))
				continue;

			if (!count)
				if (do_hook(MODULE_LIST, "FS: SearchHeader %s %s %d %d %d %d", nick, buffer, dcc_send_num, get_int_var(DCC_SEND_LIMIT_VAR), dcc_queue_num, get_int_var(DCC_QUEUE_LIMIT_VAR)))
					queue_send_to_server(from_server, "PRIVMSG %s :Matches for %s. Copy and Paste in channel to request. (Slots:%d/%d), (Queue:%d/%d)", nick, buffer, dcc_send_num, get_int_var(DCC_SEND_LIMIT_VAR), dcc_queue_num, get_int_var(DCC_QUEUE_LIMIT_VAR));
			count++;
			if (!num_to_match || (count < num_to_match))
			{
				if (do_hook(MODULE_LIST, "FS: SearchList %s \"%s\" %u %u %lu %lu", nick, p, new->bitrate, new->freq, new->filesize, new->time)) 
					queue_send_to_server(from_server, "PRIVMSG %s :!%s %s %dk [%s]", nick, get_server_nickname(from_server), p, new->bitrate, print_time(new->time));
			}
		}
		if (num_to_match && (count > num_to_match))
		{
			if (do_hook(MODULE_LIST, "FS: SearchTooMany %s %d", nick, count))
				queue_send_to_server(from_server, "PRIVMSG %s :Too Many Matches[%d]", nick, count);
		}
		else if (count)
		{
			if (do_hook(MODULE_LIST, "FS: SearchResults %s %d", nick, count)) 
				queue_send_to_server(from_server, "PRIVMSG %s :..... Total %d files found", nick, count);
		}
		return NULL;
	}
	for (new = fserv_files; new; new = new->next)
	{
		p = strrchr(new->filename, '/');			
		p++;
		if (my_stricmp(pat, p))
			continue;
		return new;
	}
	return NULL;
}


char *make_temp_list(char *nick)
{
	char	*nam;
	char	*real_nam;
	FILE	*fp;
	
	if (!(nam = get_dllstring_var("fserv_filename")) || !*nam)
		nam = tmpnam(NULL);
	real_nam = expand_twiddle(nam);
	if (!fserv_files || !real_nam || !*real_nam)
	{
		new_free(&real_nam);
		return NULL;
	}
	if ((fp = fopen(real_nam, "w")))
	{
		char	buffer2[BIG_BUFFER_SIZE+1];
		char	*fs;
		int	count = 0;
		
		time_t t = now;
		Files *new;
		strftime(buffer2, 200, "%X %d/%m/%Y", localtime(&t));
		for (new = fserv_files; new; new = new->next)
			count++;
		fprintf(fp, "Temporary mp3 list created for %s by %s on %s with %d mp3's\n\n", nick, get_server_nickname(from_server), buffer2, count);
		*buffer2 = 0;
		if (!(fs = get_dllstring_var("fserv_format")) || !*fs)
			fs = " %6.3s %3b [%t]\t %f\n";
		for (new = fserv_files; new; new = new->next)
			make_mp3_string(fp, new, fs, buffer2);
		fclose(fp);
		new_free(&real_nam);
		return nam;
	}
	new_free(&real_nam);
	return NULL;
}

BUILT_IN_DLL(list_fserv)
{
	char *nam;
	if (!get_dllstring_var("fserv_filename"))
	{
		put_it("%s /set fserv_filename first", FSstr);
		return;
	}
	if ((nam = make_temp_list(get_server_nickname(from_server))))
		malloc_strcpy(&fserv_filename, nam);
	return;
}

int search_proc(char *which, char *str, char **unused)
{
	char	*loc,
		*chan = NULL,
		*nick,
		*command,
		*chan_list;
	char	buffer[BIG_BUFFER_SIZE+1];

	loc = LOCAL_COPY(str);
	chan_list = get_dllstring_var("fserv_chan");
	nick = next_arg(loc, &loc);
	
	if (my_stricmp(which, "MSG"))
	{
		chan = next_arg(loc, &loc);
		command = next_arg(loc, &loc);
	}
	else
		command = next_arg(loc, &loc);

	if (!get_dllint_var("fserv"))
		return 1;
	if (chan_list && *chan_list && chan)
	{
		char	*t, 
			*ch;
		int got_it = 0;
		ch = LOCAL_COPY(chan_list);
		if (*ch == '*')
			got_it = 1;
		else
		{
			while ((t = next_in_comma_list(ch, &ch)) && *t)
				if (!my_stricmp(t, chan))
					got_it = 1;
		}
		if (!got_it)
			return 1;
	}
	if (command && *command == '@')
	{
		char *p;
		command++;
		if (!*command)
			return 1;
		if (loc && *loc && (!my_stricmp(command, "locate") || !my_stricmp(command, "find")))
		{
			search_list(nick, loc, 1);
			if (do_hook(MODULE_LIST, "FS: Search %s %s \"%s\"", nick, chan ? chan : "*", loc))
				put_it("%s got nick %s in %s searching for \"%s\"" , FSstr, nick, chan ? chan : "*", loc);
			return 1;
		} 
		if ((p = strchr(command, '-')))
		{
			*p++ = 0;
			if (!*p || my_stricmp(command, get_server_nickname(from_server)))
				return 1;
			if (!my_stricmp("que", command))
			{
				/* check queue */
				return 1;			
			}
			if (!my_stricmp("stats", command))
			{
				/* print stats */
				return 1;			
			}
			if (!my_stricmp("remove", command))
			{
				/* remove from queue */
				return 1;			
			}
		}
	}
	if (command && *command == '!')
	{
		command++;
		if (!*command) return 1;
		if (!my_stricmp(get_server_nickname(from_server), command) && loc && *loc)
		{
			Files	*fn = NULL;
			if ((fn = search_list(nick, loc, 0)))
			{
				int	send_num,
					queue_num;
				send_num = get_active_count();
				queue_num = get_num_queue();
				if (do_hook(MODULE_LIST, "FS: Sending %s \"%s\" $lu", nick, fn->filename, fn->filesize))
					put_it("%s sending %s \"%s\" %lu", FSstr, nick, fn->filename, fn->filesize);
				sprintf(buffer, "%s \"%s\"", nick, fn->filename);
				if (send_num > get_int_var(DCC_SEND_LIMIT_VAR))
				{
					pack *ptr = NULL;
					if (queue_num < get_int_var(DCC_QUEUE_LIMIT_VAR))
					{
						sprintf(buffer, "\"%s\"", fn->filename);
						ptr = (pack *)alloca(sizeof(pack));
						memset(ptr, 0, sizeof(pack));
						ptr->file = LOCAL_COPY(buffer);
						ptr->desc = LOCAL_COPY(buffer);
						ptr->numfiles = 1;
						ptr->size = fn->filesize;
						ptr->server = from_server;
						do_hook(MODULE_LIST, "FS: Queue Add %s %s", nick, buffer);

						if (add_to_queue(nick, "SEND", ptr))
						{
							statistics.files_served++;
							statistics.filesize_served += fn->filesize;
						}
						else if (do_hook(MODULE_LIST, "FS: QueueFile %s %s", nick, buffer))
							queue_send_to_server(from_server, "PRIVMSG %s :Queued File %s", nick, buffer);
					} else if (do_hook(MODULE_LIST, "FS: Queue Full %s", nick))
						queue_send_to_server(from_server, "PRIVMSG %s :Queue is full, try again later.", nick);
				}
				else
				{
					dcc_filesend("SEND", buffer);				
					statistics.files_served++;
					statistics.filesize_served += fn->filesize;
				}
			}
		}
		else if (!my_stricmp(get_server_nickname(from_server), command))
		{
			char	*name = NULL;
			if (fserv_filename || (name = make_temp_list(nick)))
			{
				sprintf(buffer, "%s %s", nick, fserv_filename ? fserv_filename : name);
				dcc_filesend("SEND", buffer);
			}
		}
	}
	return 1;
}

void impress_me(void *args)
{
	int		timer;
	char		*ch = NULL;
	ChannelList	*chan= NULL;

	timer = get_dllint_var("fserv_time");
	if (timer < DEFAULT_IMPRESS_TIME)
		timer = DEFAULT_IMPRESS_TIME;
	if (!(ch = get_dllstring_var("fserv_chan")) || !*ch)
		ch = NULL;
	else
		ch = m_strdup(ch);
	chan = get_server_channels(from_server);
	if (!ch)
		ch = m_strdup(get_current_channel_by_refnum(0));
	else
	{
		char	*c,
			*p;

		c = LOCAL_COPY(ch);
		ch = NULL;
		if (*c == '*')
		{
			ChannelList *chan;
			for (chan = get_server_channels(from_server); chan; chan=chan->next)
				m_s3cat(&ch, ",", chan->channel);
		}
		else
		{
			ChannelList *tmpchan;
			while ((p = next_in_comma_list(c, &c)) && *p)
			{
				if ((tmpchan = (ChannelList *)find_in_list((List **)&chan, p, 0)))
					m_s3cat(&ch, ",", p);
			}
		}
	}
	if (fserv_files && get_dllint_var("fserv_impress"))
	{
		unsigned long	l;
		Files		*new;

		l = random_number(0L) % statistics.total_files;
		for (new = fserv_files; new && l; new = new->next, l--)
			;
		if (new && new->bitrate)
		{
			char	frq[30],
				size[40],
				*p;
			p = strrchr(new->filename, '/');
			p++; 
			if (do_hook(MODULE_LIST, "FS: Impress %s \"%s\" %lu %u %u %s %lu",ch, p, new->filesize, new->bitrate, new->freq, mode_str(new->stereo), new->time))
			{
				sprintf(frq, "%3.1f", ((double)new->freq)/1000.0);
				sprintf(size, "%4.3f%s", _GMKv(new->filesize), _GMKs(new->filesize));
				queue_send_to_server(from_server, "PRIVMSG %s :[  !%s %s  ] [%s %uKbps %sKhz %s]-[%s]",
					ch, get_server_nickname(from_server), p, 
					size, new->bitrate, frq, 
					mode_str(new->stereo), 
					print_time(new->time));
			}
		}
	}
	add_timer(0, empty_string, timer * 1000, 1, impress_me, NULL, NULL, -1, "fserv");
	new_free(&ch);
}

BUILT_IN_FUNCTION(func_convert_mp3time)
{
	int	hours, 
		minutes, 
		seconds;
	if (!input)
		return m_strdup(empty_string);
	seconds = my_atol(input);
	hours = seconds / ( 60 * 60 );
	minutes = seconds / 60;
	seconds = seconds % 60;
	return m_sprintf("[%02d:%02d:%02d]", hours, minutes, seconds);
}

BUILT_IN_DLL(stats_fserv)
{
	put_it("%s\t File Server Statistics From %s", FSstr, my_ctime(statistics.starttime));
	put_it("%s\t Fserv is [%s] Impress is [%s] %d seconds with %d matches allowed", FSstr, on_off(get_dllint_var("fserv")), on_off(get_dllint_var("fserv_impress")), get_dllint_var("fserv_time"), get_dllint_var("fserv_max_match"));
	put_it("%s\t Files available %lu for %4.3f%s",FSstr, statistics.total_files, _GMKv(statistics.total_filesize), _GMKs(statistics.total_filesize));
	put_it("%s\t Files served %lu for %4.3f%s", FSstr, statistics.files_served, _GMKv(statistics.filesize_served), _GMKs(statistics.filesize_served));
}



BUILT_IN_DLL(save_fserv)
{
char bogus[] = "fserv";
FILE *fp;
char buffer[BIG_BUFFER_SIZE];
char *p;
char *fserv_savname = NULL;
	sprintf(buffer, "%s/fserv.sav", get_string_var(CTOOLZ_DIR_VAR));
	fserv_savname  = expand_twiddle(buffer);
	if (!(fp = fopen(fserv_savname, "w")))
	{
		new_free(&fserv_savname);
		return;
	}
	fprintf(fp, "%s %s\n", bogus, on_off(get_dllint_var("fserv")));
	if ((p = get_dllstring_var("fserv_dir")))
		fprintf(fp, "%s%s %s\n", bogus, "_dir", p);
	if ((p = get_dllstring_var("fserv_chan")))
		fprintf(fp, "%s%s %s\n", bogus, "_chan", p);
	if ((p = get_dllstring_var("fserv_filename")))
		fprintf(fp, "%s%s %s\n", bogus, "_filename", p);
	if ((p = get_dllstring_var("fserv_format")))
		fprintf(fp, "%s%s %s\n", bogus, "_format", p);
	fprintf(fp, "%s%s %u\n", bogus, "_time", get_dllint_var("fserv_time"));
	fprintf(fp, "%s%s %u\n", bogus, "_max_match", get_dllint_var("fserv_max_match"));
	fprintf(fp, "%s%s %s\n", bogus, "_impress", on_off(get_dllint_var("fserv_impress")));
	if (statistics.files_served)
	{
		fprintf(fp, "%s%s %lu\n", bogus, "_totalserved", statistics.files_served);
		fprintf(fp, "%s%s %lu\n", bogus, "_totalstart", statistics.starttime);
		fprintf(fp, "%s%s %lu\n", bogus, "_totalsizeserved", statistics.filesize_served);
	}
	fclose(fp);
	if ((do_hook(MODULE_LIST, "FS: Save")))
		put_it("%s Done Saving.", FSstr);
	new_free(&fserv_savname);
}

void fserv_read(char *filename)
{
FILE *fp;
char buff[IRCD_BUFFER_SIZE+1];
char *fserv_savname = NULL;
	fserv_savname  = expand_twiddle(filename);
	if (!(fp = fopen(fserv_savname, "r")))
	{
		new_free(&fserv_savname);
		return;
	}
	fgets(buff, IRCD_BUFFER_SIZE, fp);
	while (!feof(fp))
	{
		char *p;
		chop(buff, 1);
		if ((p = strchr(buff, ' ')))
		{
			*p++ = 0;

			if (!my_strnicmp(buff, "fserv_totalserved", 17))
				statistics.files_served = strtoul(p, NULL, 0);
			else if (!my_strnicmp(buff, "fserv_totalsizeserved", 17))
				statistics.filesize_served = strtoul(p, NULL, 0);
			else if (!my_strnicmp(buff, "fserv_totalserved", 17)) 
				statistics.starttime = strtoul(p, NULL, 0);
			else
			{
				if (*p > '0' && *p < '9')
				{
					int val;
					val = my_atol(p);
					set_dllint_var(buff, val);
				}
				else if (!my_stricmp(p, "ON"))
					set_dllint_var(buff, 1);
				else if (!my_stricmp(p, "OFF"))
					set_dllint_var(buff, 0);
				else
					set_dllstring_var(buff, p);
			}
		}
		fgets(buff, IRCD_BUFFER_SIZE, fp);
	}
	fclose(fp);
}

BUILT_IN_DLL(help_fserv)
{
	put_it("%s FServ %s by Colten Edwards aka panasync", FSstr, AUTO_VERSION); 
	put_it("%s [Sets]", FSstr);
	put_it("%s fserv on/off  fserv functions. Default is %s", FSstr, on_off(DEFAULT_FSERV));
	put_it("%s fserv_dir path [path]", FSstr);
	put_it("%s fserv_chan #chan[,#chan2]", FSstr);
	put_it("%s fserv_time seconds between displays of random mp3. Default is %d", FSstr, DEFAULT_IMPRESS_TIME);
	put_it("%s fserv_max_match defines how many matches allowed. Default is %d", FSstr, DEFAULT_MAX_MATCH);
	put_it("%s fserv_impress on/off public display of random mp3. Default is %s", FSstr, on_off(DEFAULT_IMPRESS));
	put_it("%s", FSstr);
	put_it("%s channel commands are @find pattern or @locate pattern", FSstr);
	put_it("%s !nick filename to send a file to nick requesting", FSstr);
	put_it("%s a /msg to the nick can be used instead of a public", FSstr);
	put_it("%s a $mp3time() function as well as a hook are provided. /on module \"FS:*\"", FSstr);
	put_it("%s    more help available with /help", FSstr);
}

int Fserv_Lock(IrcCommandDll **intp, Function_ptr *global_table)
{
	return 1;
}

char *Fserv_Version(IrcCommandDll **intp)
{
	return AUTO_VERSION;
}


int Fserv_Init(IrcCommandDll **intp, Function_ptr *global_table)
{
char buffer[BIG_BUFFER_SIZE+1];
	initialize_module("Fserv");
	add_module_proc(VAR_PROC, "Fserv", "fserv", NULL, BOOL_TYPE_VAR, DEFAULT_FSERV, NULL, NULL);
	add_module_proc(VAR_PROC, "Fserv", "fserv_dir", NULL, STR_TYPE_VAR, 0, NULL, NULL);
	add_module_proc(VAR_PROC, "Fserv", "fserv_chan", NULL, STR_TYPE_VAR, 0, NULL, NULL);
	add_module_proc(VAR_PROC, "Fserv", "fserv_filename", NULL, STR_TYPE_VAR, 0, NULL, NULL);
	add_module_proc(VAR_PROC, "Fserv", "fserv_format", NULL, STR_TYPE_VAR, 0, NULL, NULL);
	add_module_proc(VAR_PROC, "Fserv", "fserv_time", NULL, INT_TYPE_VAR, DEFAULT_IMPRESS_TIME, NULL, NULL);
	add_module_proc(VAR_PROC, "Fserv", "fserv_max_match", NULL, INT_TYPE_VAR, DEFAULT_MAX_MATCH, NULL, NULL);
	add_module_proc(VAR_PROC, "Fserv", "fserv_impress", NULL, BOOL_TYPE_VAR, DEFAULT_IMPRESS, NULL, NULL);

	sprintf(buffer, " [-recurse] [path [path]] to load all files -recurse is a \ntoggle and can appear anywhere. Default is [%s]", on_off(DEFAULT_RECURSE));
	add_module_proc(COMMAND_PROC, "Fserv", "fsload", NULL, 0, 0, load_fserv, buffer);

	sprintf(buffer, " [-count #] [-freq #] [-bitrate #] [pattern] to search database locally");
	add_module_proc(COMMAND_PROC, "Fserv", "fsprint", NULL, 0, 0, print_fserv, buffer);

	sprintf(buffer, " to remove all files or [pat [pat]] to remove specific");
	add_module_proc(COMMAND_PROC, "Fserv", "fsunload", NULL, 0, 0, unload_fserv, buffer);

	add_module_proc(COMMAND_PROC, "Fserv", "fshelp", NULL, 0, 0, help_fserv, " to provide help for fserv plugin");

	sprintf(buffer, " [-recurse] [path [path]] to reload all files");
	add_module_proc(COMMAND_PROC, "Fserv", "fsreload", NULL, 0, 0, load_fserv, buffer);

	add_module_proc(COMMAND_PROC, "Fserv", "fsstats", NULL, 0, 0, stats_fserv, " provides fserv statistics");

	sprintf(buffer, " Creates list of mp3");
	add_module_proc(COMMAND_PROC, "Fserv", "fslist", NULL, 0, 0, list_fserv, buffer);

	sprintf(buffer, " to save your stats and settings to %s/fserv.sav", get_string_var(CTOOLZ_DIR_VAR));
	add_module_proc(COMMAND_PROC, "Fserv", "fssave", NULL, 0, 0, save_fserv, buffer);


	add_module_proc(ALIAS_PROC, "Fserv", "mp3time", NULL, 0, 0, func_convert_mp3time, NULL);
        add_module_proc(HOOK_PROC, "Fserv", NULL, "*", PUBLIC_LIST, 1, NULL, search_proc);
        add_module_proc(HOOK_PROC, "Fserv", NULL, "*", MSG_LIST, 1, NULL, search_proc);
        add_module_proc(HOOK_PROC, "Fserv", NULL, "*", PUBLIC_OTHER_LIST, 1, NULL, search_proc);

	add_completion_type("fsload", 3, FILE_COMPLETION);

	add_timer(0, empty_string, get_dllint_var("fserv_time"), 1, impress_me, NULL, NULL, -1, "fserv");
	strcpy(FSstr, cparse(FS, NULL, NULL));
	put_it("%s %s", FSstr, convert_output_format("$0 v$1 by panasync.", "%s %s", fserv_version, AUTO_VERSION));
	sprintf(buffer, "$0+%s by panasync - $2 $3", fserv_version);
	fset_string_var(FORMAT_VERSION_FSET, buffer);
	statistics.starttime = time(NULL);

	sprintf(buffer, "%s/fserv.sav", get_string_var(CTOOLZ_DIR_VAR));
	fserv_read(buffer);

	put_it("%s for help with this fserv, /fshelp", FSstr);
	return 0;
}

