/* blacklist.c
 *
 * part of ed2k-gtk-gui
 *
 * (c) 2002 Tim-Philipp Muller <t.i.m@orange.net>
 *
 * the blacklist contains hashes of files we don't want to see when we search
 * it is not saved anywhere, but the gui reads the gui_blacklist file at startup
 * and then creates its blacklist
 *
 */

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

/* last general code review: 2002-06-20 by Tim */

#include "global.h"
#include "misc.h"
#include "options.h"
#include "misc_strings.h"
#include "status_page.h"
#include "blacklist.h"
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>


/* local defines */

#define     BUCKETS   16


/* local variables */

static GSList	*blacklist_buckets[BUCKETS];

static time_t	 last_blacklistfile_modtime;   /* 0 */


/* local functions */

static void   append_to_blacklist_file (const guint8 *evilhash, guint32 size, const gchar *name);

static void   reread_blacklist (void);

static guint  blacklist_get_number_of_items(void);

static void   clear_blacklist (void);


/******************************************************************************
 *
 * is_item_in_blacklist
 *
 * Checks if a filelist item is blacklisted.
 * Returns TRUE or FALSE
 *
 ***/

gboolean
is_item_in_blacklist (const guint8 *hash, guint size)
{
	GSList *node;
	guint   bucket;

	g_return_val_if_fail ( hash != NULL, FALSE );

	bucket = ( (*hash) % BUCKETS );

	for ( node = blacklist_buckets[bucket];  node != NULL;  node = node->next )
	{
		if (node->data)
		{
			GuiBlacklistItem	*checkitem = (GuiBlacklistItem *)node->data;

			if ( memcmp(checkitem->hash, hash, 16) == 0 && checkitem->filesize == size )
				return TRUE;	/* yup, is in list */
		}
	}

	return FALSE; /* nope, is not in list */
}


/******************************************************************************
 *
 *   append_to_blacklist_file
 *
 *   Appends an item to the gui_blacklist file (name is printed for 'readability' reasons,
 *     but doesn't matter. We only check for hash and file size when checking if
 *     items are blacklisted. This function does not check if file is already in list.
 *
 ****/

static void
append_to_blacklist_file (const guint8 *evilhash, guint32 size, const gchar *name)
{
	FILE        *lf;
	const gchar *fn;

	g_return_if_fail ( evilhash != NULL );
	g_return_if_fail ( name     != NULL );

	fn = opt_get_opt_filename_without_instance("gui_blacklist");

	lf = fopen (fn, "a");

	if ( lf != NULL )
	{
		fprintf (lf,"ed2k://|file|%s|%u|%s|\n", name, size, hash_to_hash_str (evilhash));
		fclose(lf);
	}
	else
	{
		status_system_error_msg (_("couldn't write to blacklist file!"));
	}

	last_blacklistfile_modtime = time(NULL);
}


/******************************************************************************
 *
 *   reread_blacklist
 *
 *   Rereads the list of items in the blacklist from the gui_blacklist file
 *
 ****/

static void
reread_blacklist (void)
{
	const gchar   *fn;
	gchar        **line, **linearray, *utf8;
	guint          commented_out=0, invalid=0;

	clear_blacklist();

	status_msg ("%s", _("GUI: blacklist file has changed - rereading it.\n"));

	fn = opt_get_opt_filename_without_instance("gui_blacklist");

	linearray = misc_get_array_of_lines_from_textfile (fn, FALSE, FALSE);

	if (!linearray)
	{
		last_blacklistfile_modtime = time(NULL);
		return;
	}

	if ((*linearray) && (**linearray!=0x00))	/* file not empty */
	{
		for (line = linearray;  *line != NULL;  line++ )
		{
			gchar  *thisline, *hash, *filenamebuf;
			guint8 *hexhash;
			guint   size = 0;

			if ( **line == '#' )
			{
				commented_out++;
				continue;
			}

			thisline = g_strchug(*line);	/* remove leading whitespaces */

			if ( g_ascii_strncasecmp(thisline,"ed2k://|file|",13) !=0 )
			{
				if ( *thisline != 0x00 )
				{
					status_parse_error_msg ("blacklist", (linearray-line)/sizeof(gchar*), thisline);
					invalid++;
				}
				continue;
			}

			hash = filenamebuf = NULL;

			utf8 = TO_UTF8(thisline);
			if (!misc_check_if_ed2klink_is_valid(utf8,&filenamebuf,&size,&hash,NULL,NULL))
			{
				invalid++;
				FREE_UTF8(utf8);
				continue;
			}

			hexhash = (guint8*) hash_str_to_hash(hash);
			if (hexhash)
			{
				/* "" is a marker, so the add_to_.. doesn't write to file */
				add_to_blacklist (hexhash, size, "");
			}
			else
			{
				invalid++;
			}

			FREE_UTF8(utf8);
			G_FREE(filenamebuf);
			G_FREE(hash);
		}
	}

	g_strfreev(linearray);	/* free line array */

	last_blacklistfile_modtime = time(NULL);

	status_msg (_("GUI: read in %u valid blacklist items (another %u were commented out - %u were invalid)\n"),
	            blacklist_get_number_of_items(), commented_out, invalid);
}


/******************************************************************************
 *
 *   onTimeoutCheckFile
 *
 ******************************************************************************/

static gboolean
onTimeoutCheckFile (gpointer foo)
{
	check_if_blacklistfile_changed();

	return TRUE; /* call again */
}


/******************************************************************************
 *
 *   check_if_blacklistfile_changed
 *
 *   Checks if the blacklist file has changed (ie. has a newer date returned by fstat() than
 *    than that that we have saved) and re-reads the blacklist file if it has changed
 *
 ***/

void
check_if_blacklistfile_changed (void)
{
	const gchar  *fn;
	struct stat   blstat;

	fn = opt_get_opt_filename_without_instance("gui_blacklist");

	if (last_blacklistfile_modtime == 0)
	{
		g_timeout_add(60*1000, (GSourceFunc) onTimeoutCheckFile, NULL);
		last_blacklistfile_modtime = 1; /* make sure we only hook up the timeout once */
	}

	/* if not, error or file doesn't exist */
	if (stat(fn, &blstat) != 0)
		return;

	if ( blstat.st_mtime > last_blacklistfile_modtime )
		reread_blacklist();
}


/******************************************************************************
 *
 *   add_to_blacklist
 *
 *   Adds an item to the blacklist if it is not already in there.
 *
 ***/

void
add_to_blacklist (const guint8 *evilhash, guint32 size, const gchar *name)
{
	GuiBlacklistItem  *bli;
	guint            bucket;

	g_return_if_fail ( evilhash != NULL );

	/* we don't check for name!=NULL, because that's an indicator that
	 *	we've just read the item in from disk */

	if ( is_item_in_blacklist(evilhash,size) == TRUE )
		return;

	bli = g_new (GuiBlacklistItem, 1);

	g_return_if_fail (bli!=NULL);

	g_memmove (bli->hash, evilhash, 16);

	bli->filesize = size;

	bucket = ( (*evilhash) % BUCKETS );

	blacklist_buckets[bucket] = g_slist_append (blacklist_buckets[bucket], (gpointer) bli );

	/* don't append items to the file that we've just read in from the file */
	if ( name != NULL && *name != 0x00 )
		append_to_blacklist_file (evilhash, size, name);
}


/******************************************************************************
 *
 *   clear_blacklist
 *
 *   Frees the blacklist and all data it contains
 *
 ***/

static void
clear_blacklist (void)
{
	GSList  *node;
	guint    i;

	for ( i = 0;  i < BUCKETS; i++ )
	{
		for ( node = blacklist_buckets[i]; node != NULL; node = node->next )
			G_FREE(node->data);	/* free the GuiBlacklistItem */

		g_slist_free (blacklist_buckets[i]);

		blacklist_buckets[i] = NULL;
	}
}


/******************************************************************************
 *
 *  blacklist_get_number_of_items
 *
 ***/

static guint
blacklist_get_number_of_items (void)
{
	guint  count, i;

	count = 0;

	for ( i = 0;  i < BUCKETS; i++ )
		count += g_slist_length (blacklist_buckets[i]);

	return count;
}


