/*
 * $Id: index.c,v 1.5 1997/09/02 11:39:02 dirk Exp $
 * 
 * This file is part of WorkMan, the civilized CD player program
 * (c) 1991-1997 by Steven Grimm (original author)
 * (c) by Dirk F"orsterling (current 'author' = maintainer)
 * The maintainer can be contacted by his e-mail address:
 * milliByte@DeathsDoor.com 
 *
 * 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.
 *
 * Maintain an external index file for a WorkMan CD database.
 * Uses the Berkeley libdb library, available from ftp.cs.berkeley.edu.
 */

static char index_id[] = "$Id: index.c,v 1.5 1997/09/02 11:39:02 dirk Exp $";

#ifdef LIBDB

#include <stdio.h>
#include <stdlib.h>
#include <db.h>
#include <fcntl.h>
#include <string.h>
#include <netinet/in.h>	/* for htonl() */
#include "config.h"
#include "proto.h"

extern int suppress_locking;

/*
 * idx_find_entry()
 *
 * Find an entry in the index file.
 *
 * Input:
 *	file	Name of database file (text version).
 *	ntracks	Number of tracks on the CD we're looking for.
 *	tracks	Array of track start times.
 *	len	CD length in frames.
 *	fuzz	Fuzz factor (tolerance value).
 *	pos	Pointer to return value.
 *
 * Output:
 *	1	No matching record found.
 *	0	Record found; *pos contains offset into text file.
 *	-1	Index file out of date or inaccessible, or another error.
 */
int
idx_find_entry( char *file, int ntracks, int *tracks,
		int len, int fuzz, unsigned long *pos )
{
	unsigned long	dbpos;
	char		*indexname = NULL, keyval[8];
	int		c;
	FILE		*text;
	DB		*index;
	DBT		key, data;
	BTREEINFO	bti;

	/*
	 * First, see if the text file is accessible.  Lock it if so.
	 */
	text = fopen(file, "r");
	if (text == NULL)
		return (-1);
	if ((c = getc(text)) == EOF)
	{
		fclose(text);
		return (-1);
	}
	if (! suppress_locking)
		if (lockit(fileno(text), F_RDLCK))
		{
			fclose(text);
			return (-1);
		}

	/*
	 * Open the index file.
	 */
	indexname = malloc(strlen(file) + sizeof(".ind"));
	if (indexname == NULL)
	{
		fclose(text);
		return (-1);
	}
	strcpy(indexname, file);
	strcat(indexname, ".ind");
	bti.flags = 0;
	bti.cachesize = 0;
	bti.minkeypage = bti.maxkeypage = 0;
	bti.psize = bti.lorder = 0;
	bti.compare = NULL;
	bti.prefix = NULL;
	index = dbopen(indexname, O_RDONLY, 0666, DB_BTREE, &bti);
	free(indexname);
	if (index == NULL)
	{
		fclose(text);
		return (-1);
	}

	/*
	 * Search for the first matching entry.
	 */
	sprintf(keyval, "%07d", tracks[ntracks - 1] - fuzz);
	key.data = keyval;
	key.size = 7;
	if (c = (index->seq)(index, &key, &data, R_CURSOR))
	{
		(index->close)(index);
		fclose(text);
		return (c);
	}

	/*
	 * Now loop through all the possible matches, collecting them into
	 * memory.
	 */
	do {
		char	tracksline[750], *s;
		int	i, val;

		/* Hit the end of the valid entries? */
		sscanf(key.data, "%d", &val);
		if (val > tracks[ntracks - 1] + fuzz)
			break;

		dbpos = ntohl(*((unsigned long *) data.data));
		if (fseek(text, dbpos, 0))
			break;
		
		fgets(tracksline, sizeof(tracksline), text);
		if (strncmp(tracksline, "tracks ", 7))
			break;
		(void) strtok(tracksline, " \t");

		/* Got a valid tracks line.  See if it matches the CD. */
		s = strtok(NULL, " \t");
		if (s == NULL)
			break;
		if (atoi(s) != ntracks)
			continue;
		
		for (i = 0; i < ntracks; i++)
		{
			s = strtok(NULL, " \t");
			if (s == NULL)
				break;
			val = atoi(s);
			if (val + fuzz < tracks[i] || val - fuzz > tracks[i])
				break;
		}
		if (i != ntracks)
			continue;

		s = strtok(NULL, " \t");
		if (s == NULL)
			continue;
		val = atoi(s);
		if (val + fuzz / 75 < len / 75 || val + fuzz / 75 > len / 75)
			continue;
		
		/* XXX - add to sorted list! */
		*pos = dbpos;
		(index->close)(index);
		fclose(text);
		return (0);
	} while ((c = (index->seq)(index, &key, &data, R_NEXT)) == 0);

	if (c == 0)
	{
		/* An error. */
		(index->close)(index);
		fclose(text);
		return (-1);
	}

	(index->close)(index);
	fclose(text);
	return (1);
}

/*
 * idx_delete_entry()
 *
 * Delete an entry from the index file.
 *
 * Input:
 *	file	Name of database file (text version).
 *	track	Last track's start time (database key).
 *	fuzz	Fuzz factor (tolerance value).
 *	pos	Position of CD in database file.
 *
 * Output:
 *	1	No matching record found.
 *	0	Record deleted.
 *	-1	Index file out of date or inaccessible, or another error.
 *
 * Note: it is the caller's responsibility to do locking, as it's assumed
 * that this operation will accompany a modification of the main database
 * file and will need to be atomic with that modification.
 */
int
idx_delete_entry(char *file, int track, int fuzz, unsigned long pos )
{
	unsigned long	dbpos;
	char		*indexname = NULL, keyval[8];
	int		c, status;
	DB		*index;
	DBT		key, data;
	BTREEINFO	bti;

	/*
	 * Open the index file.
	 */
	indexname = malloc(strlen(file) + sizeof(".ind"));
	if (indexname == NULL)
		return (-1);

	strcpy(indexname, file);
	strcat(indexname, ".ind");

	bti.flags = 0;
	bti.cachesize = 0;
	bti.minkeypage = bti.maxkeypage = 0;
	bti.psize = bti.lorder = 0;
	bti.compare = NULL;
	bti.prefix = NULL;
	index = dbopen(indexname, O_RDWR, 0666, DB_BTREE, &bti);
	free(indexname);
	if (index == NULL)
		return (-1);

	/*
	 * Search for the first matching entry.
	 */
	sprintf(keyval, "%07d", track - fuzz);
	key.data = keyval;
	key.size = 7;
	if (c = (index->seq)(index, &key, &data, R_CURSOR))
	{
		/*
		 * Nothing matched!
		 */
		(index->close)(index);
		return (c);
	}

	/*
	 * Look for the entry the user wants to delete.
	 */
	do {
		int	val;

		/* Hit the end of the valid entries? */
		sscanf(key.data, "%d", &val);
		if (val > track + fuzz)
			break;

		/* Is this the entry we want? */
		if (pos == ntohl(*((unsigned long *) data.data)))
		{
			/*
			 * Yep!  Delete it.
			 */
			status = (index->del)(index, &key, R_CURSOR);
			(index->close)(index);
			return (status);
		}
	} while ((c = (index->seq)(index, &key, &data, R_NEXT)) == 0);

	if (c == 0)
	{
		/* An error. */
		(index->close)(index);
		return (-1);
	}

	(index->close)(index);
	return (1);
}

/*
 * idx_write_entry()
 *
 * Write out an index file entry.
 *
 * Input:
 *	file	Name of database file (text version).
 *	track	Start time of last track (database key).
 *	pos	Position of entry in text file.
 *
 * Output:
 *	0	Record written.
 *	-1	Index file inaccessible, or another error.
 *
 * Note: it is the caller's responsibility to do locking, as it's assumed
 * that this operation will accompany a modification of the main database
 * file and will need to be atomic with that modification.
 */
int
idx_write_entry( char *file, int track, unsigned long pos )
{
	char		*indexname, keyval[8];
	int		status;
	DB		*index;
	DBT		key, data;
	BTREEINFO	bti;

	/*
	 * Open the index file.
	 */
	indexname = malloc(strlen(file) + sizeof(".ind"));
	if (indexname == NULL)
		return (-1);

	strcpy(indexname, file);
	strcat(indexname, ".ind");

	bti.flags = R_DUP;
	bti.cachesize = 0;
	bti.minkeypage = 0;
	bti.maxkeypage = 0;
	bti.psize = 0;
	bti.lorder = 4321;	/* network byte order */
	bti.compare = NULL;
	bti.prefix = NULL;
	index = dbopen(indexname, O_RDWR, 0666, DB_BTREE, &bti);
	free(indexname);
	if (index == NULL)
		return (-1);

	/*
	 * Create a new key and value.
	 */
	pos = htonl(pos);
	data.data = &pos;
	data.size = sizeof(pos);
	key.data = keyval;
	key.size = 7;

	sprintf(keyval, "%07d", track);

	status = (index->put)(index, &key, &data, 0);

	(index->close)(index);
	return (status);
}

#else /* LIBDB */

int
idx_find_entry()
{
	return (1);	/* no record found; text file will be searched. */
}

int
idx_delete_entry()
{
	return (0);
}

int
idx_write_entry()
{
	return (0);
}

#endif /* LIBDB */
