/*
 * @(#)database.c	1.25	12/10/92
 *
 * Manage the CD database.  All these routines assume that the "cd" global
 * structure contains current information (as far as the outside world knows;
 * obviously it won't contain track titles just after a CD is inserted.)
 */

#define RCFILE "/.workmanrc"
#define DBFILE "/.workmandb"
/*static char            *ident = "@(#)database.c	1.25 12/10/92";*/

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/param.h>
#include <unistd.h>
#include <sys/time.h>
#include "struct.h"
#include "cdp.h"
#include "globalvar.h"


#define SWALLOW_LINE(fp) { int c; while ((c = getc(fp)) != '\n' && c != EOF); }

void                   *malloc (),
                       *realloc ();
char                   *strchr (),
                       *getenv ();

int                     suppress_locking = 0;	/* Turn off locking of datafile (dangerous) */

char                   *rcfile = NULL;	/* Personal rcfile */
char                  **databases = NULL;	/* NULL-terminated list of databases */

char                   *otherrc = NULL;	/* Unrecognized cruft from start of rcfile */

long                    rcpos,
                        rclen;	/* XXX */

int                     found_in_db,
                        found_in_rc;
long                    holepos,
                        firstpos;    
int                     cur_stopmode, cur_playnew;
extern int              cur_ntracks,
                        cur_nsections;
static int              mark_a,
                        mark_b;

/* Copy into a malloced string. */
extern void  strmcpy( char                  **t, char       *s );


/* Add to a malloced string. */
void
strmcat (t, s)
     char                  **t,
                            *s;
{
    int                     len = strlen (s) + 1;

    if (*s == '\0')
	return;

    if (*t != NULL) {
	len += strlen (*t);
	*t = realloc (*t, len);
	if (*t == NULL) {
	    perror ("strmcat");
	    exit (1);
	}
	strcat (*t, s);
    } else
	strmcpy (t, s);
}


/*
 * insert_trackinfo()
 *
 * Add a new track to the CD info structure.  Pass the position of the new
 * entry in the track list -- 0 will make this the first track, 1 the second,
 * etc.  The new entry will be zeroed out.
 */
void  insert_trackinfo( int	num )
{
	struct trackinfo *newtrk;

	/* Easy case: the list is empty */
	if (cd->trk == NULL)
		if ((cd->trk = (struct trackinfo *) calloc(1,
						sizeof(*newtrk))) == NULL)
		{
nomem:
			perror("insert_trackinfo");
			exit(1);
		}
		else
			return;

	/* Stick the new entry in cd->trk[]. */
	if ((newtrk = (struct trackinfo *) malloc(sizeof(*newtrk) *
						(cur_ntracks + 1))) == NULL)
		goto nomem;

	if (num)
		memcpy(newtrk, cd->trk, sizeof(*newtrk) * num);
	memset(&newtrk[num], 0, sizeof(*newtrk));
	if (num < cur_ntracks)
		memcpy(&newtrk[num + 1], &cd->trk[num], sizeof(*newtrk) *
			(cur_ntracks - num));

	free(cd->trk);
	cd->trk = newtrk;
}



/*
 * split_workmandb()
 *
 * Split the WORKMANDB environment variable, if any, into a list of database
 * files in the global "databases".  If WORKMANDB is not available, make
 * a single entry with $HOME/DBFILE.
 *
 * Also, fill the "rcfile" global with the personal preferences filename.
 */
void
split_workmandb ()
{
    int                     ndbs,
                            i;
    char                   *home,
                           *wmdb;

    if (rcfile == NULL && (rcfile = getenv ("WORKMANRC")) == NULL) {
	if ((home = getenv ("HOME")) == NULL) {
	  nohome:
	    fprintf (stderr, "$HOME is not set!  Despair!\n");
	    exit (1);
	}
	rcfile = malloc (strlen (home) + sizeof (RCFILE));
	if (rcfile == NULL) {
	  nomem:
	    perror ("split_workmandb()");
	    exit (1);
	}
	strcpy (rcfile, home);
	strcat (rcfile, RCFILE);
    }
    if (databases == NULL) {
	if ((wmdb = getenv ("WORKMANDB")) == NULL) {
	    if ((home = getenv ("HOME")) == NULL)
		goto nohome;

	    wmdb = malloc (strlen (home) + sizeof (DBFILE));
	    if (wmdb == NULL)
		goto nomem;

	    databases = malloc (2 * sizeof (databases[0]));
	    if (databases == NULL)
		goto nomem;

	    strcpy (wmdb, home);
	    strcat (wmdb, DBFILE);
	    databases[0] = wmdb;
	    databases[1] = NULL;
	} else {
	    ndbs = 1;
	    for (home = wmdb; *home; home++)
		if (*home == ':') {
		    *home = '\0';
		    ndbs++;
		}
	    databases = malloc ((ndbs + 1) * sizeof (databases[0]));
	    if (databases == NULL)
		goto nomem;

	    for (i = 0; i < ndbs; i++) {
		databases[i] = wmdb;
		wmdb += strlen (wmdb) + 1;
	    }

	    databases[i] = NULL;
	}
    }
}

/*
 * print_cdinfo(cd, prefs)
 *
 * cd		A pointer to a cdinfo struct.
 * prefs	Flag: write personal preferences?
 *
 * Print a CD's information (in more or less readable form) to a buffer.
 * Returns a pointer to the buffer.
 *
 * XXX - could be more efficient about calling strmcat() and strlen().
 */
char                   *
print_cdinfo (cd, prefs)
     struct cdinfo          *cd;
     int                     prefs;
{
    int                     i;
    char                    tempbuf[2000];	/* XXX - is this always big enough? */
    static char            *cdibuf = NULL;
    struct playlist        *l;

    sprintf (tempbuf, "tracks %d", cd->ntracks);
    for (i = 0; i < cur_ntracks; i++)
	if (cd->trk[i].section < 2)
	    sprintf (tempbuf + strlen (tempbuf), " %d",
		     cd->trk[i].start);
    sprintf (tempbuf + strlen (tempbuf), " %d\n", cd->length);

    strmcpy (&cdibuf, tempbuf);

    if (cur_nsections) {
	sprintf (tempbuf, "sections %d", cur_nsections);
	for (i = 0; i < cur_ntracks; i++)
	    if (cd->trk[i].section > 1)
		sprintf (tempbuf + strlen (tempbuf), " %d",
			 cd->trk[i].start);
	sprintf (tempbuf + strlen (tempbuf), "\n");

	strmcat (&cdibuf, tempbuf);
    }
    if (prefs) {
	if (cd->autoplay)
	    strmcat (&cdibuf, "autoplay\n");
	for (l = cd->lists; l != NULL && l->name != NULL; l++) {
	    strmcat (&cdibuf, "playlist ");

	    i = strlen (cdibuf) - 1;
	    strmcat (&cdibuf, l->name);
	    while (cdibuf[++i])
		if (cdibuf[i] == ' ' || cdibuf[i] == '\t')
		    cdibuf[i] = '_';

	    if (l->list != NULL) {
		for (i = 0; l->list[i]; i++);
		sprintf (tempbuf, " %d", i);
		strmcat (&cdibuf, tempbuf);
		for (i = 0; l->list[i]; i++) {
		    sprintf (tempbuf, " %d", l->list[i]);
		    strmcat (&cdibuf, tempbuf);
		}
		strmcat (&cdibuf, "\n");
	    } else
		strmcat (&cdibuf, " 0\n");
	}

	if (cd->volume) {
	    sprintf (tempbuf, "cdvolume %d\n", cd->volume);
	    strmcat (&cdibuf, tempbuf);
	}
	if (cd->playmode) {
	    sprintf (tempbuf, "playmode %d\n", cd->playmode);
	    strmcat (&cdibuf, tempbuf);
	}
	if (mark_a) {
	    sprintf (tempbuf, "mark %d START\n", mark_a);
	    strmcat (&cdibuf, tempbuf);
	}
	if (mark_b) {
	    sprintf (tempbuf, "mark %d END\n", mark_b);
	    strmcat (&cdibuf, tempbuf);
	}
	if (cd->otherrc)
	    strmcat (&cdibuf, cd->otherrc);

	for (i = 0; i < cur_ntracks; i++) {
	    if (cd->trk[i].avoid) {
		sprintf (tempbuf, "dontplay %d\n", i + 1);
		strmcat (&cdibuf, tempbuf);
	    }
	    if (cd->trk[i].volume) {
		sprintf (tempbuf, "volume %d %d\n", i + 1,
			 cd->trk[i].volume);
		strmcat (&cdibuf, tempbuf);
	    }
	    if (cd->trk[i].otherrc)
		strmcat (&cdibuf, cd->trk[i].otherrc);
	}
    } else {
	if (cd->cdname[0]) {
	    strmcat (&cdibuf, "cdname ");
	    strmcat (&cdibuf, cd->cdname);
	    strmcat (&cdibuf, "\n");
	}
	if (cd->artist[0]) {
	    strmcat (&cdibuf, "artist ");
	    strmcat (&cdibuf, cd->artist);
	    strmcat (&cdibuf, "\n");
	}
	if (cd->otherdb)
	    strmcat (&cdibuf, cd->otherdb);

	for (i = 0; i < cur_ntracks; i++) {
	    if (cd->trk[i].section > 1)
		strmcat (&cdibuf, "s-");
	    strmcat (&cdibuf, "track ");
	    if (cd->trk[i].songname != NULL)
		strmcat (&cdibuf, cd->trk[i].songname);
	    strmcat (&cdibuf, "\n");
	    if (cd->trk[i].contd) {
		if (cd->trk[i].section > 1)
		    strmcat (&cdibuf, "s-");
		strmcat (&cdibuf, "continue\n");
	    }
	    if (cd->trk[i].otherdb)
		strmcat (&cdibuf, cd->trk[i].otherdb);
	}
    }

    return (cdibuf);
}

/*
 * Open the rcfile for reading or writing.
 *
 *	name		Filename
 *	mode		"r" or "w"
 */
FILE                   *
open_rcfile (name, mode)
     char                   *name,
                            *mode;
{
    FILE                   *fp;

    fp = fopen (name, mode);
    if (fp == NULL) {
	if (errno != ENOENT || mode[0] == 'w')
	    perror (name);
    } else if (mode[0] == 'w') {/* create -- put data in so locks work */
	fputs ("# WorkMan database file\n", fp);
	fclose (fp);
	fp = fopen (name, "r+");
	if (fp == NULL)
	    if (errno != ENOENT)
		perror (name);
    }
    return (fp);
}

/* Free some memory and set a pointer to null. */
void   freeup( void     ** x )
{
    if (*x != NULL) {
	free(*x);
	*x = NULL;
    }
}


/*
 * split_trackinfo()
 *
 * Split a track in two at a particular position (absolute, in frames).  All
 * internal data structures and variables will be adjusted to the new
 * numbering scheme.  Pass in the track number (>=1) to split, which is also
 * the index into cd->trk[] of the new entry.
 *
 * If pos is within 1 second of the start of another track, the split fails.
 *
 * Returns 1 on success, 0 if the track couldn't be inserted.
 *
 * Note: updating user interface elements is up to the caller.
 */
int   split_trackinfo( int	pos )
{
	int	i, l, num;

	if (pos < cd->trk[0].start)
		return (0);

	/* First find the appropriate track. */
	for (num = 0; num < cur_ntracks; num++)
		if (cd->trk[num].start - 75 < pos &&
						cd->trk[num].start + 75 > pos)
			return (0);
		else if (cd->trk[num].start > pos)
			break;
	if (num == 0)
		return (0);

	/* Insert the new entry into the track array. */
	insert_trackinfo(num);

	/* Update the easy variables. */
	if (cur_track > num)
		cur_track++;
	if (cur_firsttrack > num)
		cur_firsttrack++;
	if (cur_lasttrack > num)
		cur_lasttrack++;

	/* Update the user-defined playlists. */
	if (cd->lists != NULL)
		for (l = 0; cd->lists[l].name != NULL; l++)
			if (cd->lists[l].list != NULL)
				for (i = 0; cd->lists[l].list[i]; i++)
					if (cd->lists[l].list[i] > num)
						cd->lists[l].list[i]++;

	/* Update the internal playlist. */
/*	if (playlist != NULL)
		for (i = 0; playlist[i].start; i++)
		{
			if (playlist[i].start > num)
				playlist[i].start++;
			if (playlist[i].end > num)
				playlist[i].end++;
		}
		*/
	/* Now adjust the information in cd->trk[]. */
	cd->trk[num].start = pos;
	if (num == cur_ntracks)
		cd->trk[num].length = cur_cdlen - pos / 75;
	else
		cd->trk[num].length = (cd->trk[num + 1].start - pos) / 75;
	cd->trk[num - 1].length -= cd->trk[num].length;
	if (cur_track == num)
		cur_tracklen -= cd->trk[num].length;
	cd->trk[num].track = cd->trk[num - 1].track;
	cd->trk[num].data = cd->trk[num - 1].data;
	cd->trk[num].contd = 1;
	cd->trk[num].volume = cd->trk[num - 1].volume;

	if (cd->trk[num - 1].section == 0)
		cd->trk[num - 1].section = 1;
	cd->trk[num].section = cd->trk[num - 1].section + 1;

	cur_ntracks++;
	cur_nsections++;

	for (i = num + 1; i < cur_ntracks; i++)
		if (cd->trk[i].track == cd->trk[num].track)
			cd->trk[i].section++;
	
	return (1);
}


/*
 * new_list()
 *
 * Add a playlist to a CD.
 */
struct playlist *
new_list(cd, listname)
	struct cdinfo	*cd;
	char		*listname;
{
	int	nlists = 0;
	struct playlist *l;

	if (cd->lists != NULL)
	{
		for (nlists = 0; cd->lists[nlists].name != NULL; nlists++)
			;
		l = (struct playlist *)realloc(cd->lists, (nlists + 2) *
			sizeof (struct playlist));
	}
	else
		l = (struct playlist *)malloc(2 * sizeof (struct playlist));

	if (l == NULL)
		return (NULL);

	l[nlists + 1].name = NULL;
	l[nlists].name = NULL;		/* so strmcpy doesn't free() it */
	strmcpy(&l[nlists].name, listname);
	l[nlists].list = NULL;
	cd->lists = l;

	return (&l[nlists]);
}


/*
 * Load a new-format database file, searching for a match with the currently
 * inserted CD.  Modify the in-core copy of the CD info based on what's found
 * in the database.
 *
 * Returns 1 if there was a match or 0 if not.
 *
 *	fp		FILE* of database or rcfile.
 *	prefs		1 if we're searching .workmanrc, 0 for .workmandb
 *	scan		Scan for "tracks" location and entry size only
 *	holesize_wanted	How big a hole we're looking for, if any.
 *
 * If a hole was found along the way, update the global "holepos" with its
 * starting offset in the file.  A hole is defined as a bunch of blank lines
 * preceding a "tracks" line.  Holepos will contain the best match.
 *
 * In addition, "firstpos" will be filled with the position of the first
 * "tracks" keyword, so we know how much room is available for global
 * settings at the rcfile's start.
 */
int   search_db (fp, prefs, scan, holesize_wanted)
     FILE                   *fp;
     int                     prefs,
                             scan,
                             holesize_wanted;
{
    char                    keyword[64],
                            listname[64],
                           *c;
    int                     i,
                            j,
                            track = 0,
                            listsize,
                            ntracks,
                            scratch,
                            searching = 1;
    int                    *trackmap,
                            gotsections = 0;
    long                    pos = 0,
                            thisholepos = -1,
                            holesize = 99991239;
    struct playlist        *l;

    rclen = 0;

    /* We may not find any holes at all! */
    if (holesize_wanted)
	holepos = -1;

    /*
	 * Since we access track numbers indirectly (to handle sections
	 * with at least a little elegance), the track mapping needs to be
	 * set up before we read anything.  Initially it must assume that
	 * no sections will appear in this datafile.
	 */
    trackmap = malloc (sizeof (int) * cur_ntracks);

    if (trackmap == NULL) {
	perror ("trackmap");
	exit (1);
    }
    j = 0;
    for (i = 0; i < cd->ntracks; i++) {
	trackmap[i] = j;
	while (cd->trk[++j].section > 1);
    }

    if (prefs)
	freeup ( (void **)&otherrc);
    firstpos = -1;

    while (!feof (fp)) {
	pos = ftell (fp);

	keyword[0] = '\0';
	fscanf (fp, "%s", keyword);
	if (keyword[0] == '\0') {	/* Blank line. */
	    if (thisholepos < 0)
		thisholepos = pos;
	    continue;
	}
	/* Strip off "s-" if we've seen a "sections" keyword */
	if (gotsections && keyword[0] == 's' && keyword[1] == '-')
	    for (c = &keyword[2]; (c[-2] = *c) != '\0'; c++);

	/* If this is the start of a CD entry, see if it matches. */
	if (!strcmp (keyword, "tracks")) {
	    /* Is this the end of a hole? */
	    if (holesize_wanted && (thisholepos >= 0)) {
		/* Yep.  Is it better than the last one? */
		if (pos - thisholepos < holesize && pos -
		    thisholepos >= holesize_wanted) {
		    holepos = thisholepos;
		    holesize = pos - thisholepos;
		}
		thisholepos = -1;
	    }
	    /* Is it the start of the CD entries? */
	    if (firstpos == -1)
		firstpos = pos;

	    /* Is this the end of the entry we really wanted? */
	    if (!searching) {
		rclen = pos - rcpos;
		break;
	    }
	    fscanf (fp, "%d", &ntracks);

	    if (ntracks != cd->ntracks) {
	      chomp:
		SWALLOW_LINE (fp);
		continue;
	    }
	    track = 0;
	    for (i = 0; i < ntracks; i++) {
		fscanf (fp, "%d", &scratch);
		if (scratch != cd->trk[track].start)
		    break;
		while (cd->trk[++track].section > 1);
	    }
	    if (i != ntracks)
		goto chomp;

	    fscanf (fp, "%d", &scratch);

	    if (scratch != -1 && scratch != cd->length)
		goto chomp;

	    /* Found it! */
	    rcpos = pos;
	    track = 0;
	    searching = 0;

	    SWALLOW_LINE (fp);	/* Get rid of newline */
	}
	/* Global mode stuff goes here */
	else if (!strcmp (keyword, "whendone")) {
	    getc (fp);
	    i = getc (fp);	/* only first letter is used */
	    if (cur_stopmode == -1)	/* user's setting preferred */
		cur_stopmode = i == 's' ? 0 : i == 'r' ? 1 : 2;
	    do
		i = getc (fp);
	    while (i != '\n' && i != EOF);
	} else if (!strcmp (keyword, "playnew")) {
	    if (cur_playnew == -1)
		cur_playnew = 1;
	}
	/* If we're searching, skip to the next "tracks" line. */
	else if ((searching || scan) && !(prefs && firstpos == -1))
	    SWALLOW_LINE (fp)
		else
	    if (!strcmp (keyword, "sections")) {
		gotsections = 1;
		fscanf (fp, "%d", &ntracks);

		free (trackmap);
		trackmap = (int *) malloc (sizeof (int) *
			                        (cur_ntracks + ntracks));

		if (trackmap == NULL) {
		    perror ("section mapping");
		    exit (1);
		}
		/*
			 * If sections are already defined, use these as a
			 * reference, mapping this CD entry's section numbers
			 * to the ones in core.
			 *
			 * Otherwise, split the CD up according to the sections
			 * listed here.
			 */
		if (cur_nsections) {
		    track = 0;
		    i = 0;
		    while (ntracks) {
			ntracks--;
			fscanf (fp, "%d", &scratch);
			while (scratch > cd->trk[track].start) {
			    if (cd->trk[track].section < 2)
				trackmap[i++] = track;
			    ++track;

			    if (track == cur_ntracks)
				break;
			}

			/* rc has later sections than db... */
			if (track == cur_ntracks)
			    break;

			/* Matches can be approximate */
			if (scratch + 75 > cd->trk[track].start &&
			    scratch - 75 < cd->trk[track].start)
			    trackmap[i++] = track++;
			else
			    trackmap[i++] = -1;

			if (track == cur_ntracks)
			    break;
		    }

		    /* This only happens if track == cur_ntracks */
		    while (ntracks--)
			trackmap[i++] = -1;

		    while (track < cur_ntracks) {
			if (cd->trk[track].section < 2)
			    trackmap[i++] = track;
			track++;
		    }

		    track = 0;
		    SWALLOW_LINE (fp);
		} else {
		    while (ntracks--) {
			fscanf (fp, "%d", &scratch);
			split_trackinfo (scratch);
		    }

		    for (i = 0; i < cur_ntracks; i++) {
			trackmap[i] = i;
			/* split_trackinfo() sets this */
			cd->trk[i].contd = 0;
		    }

		    SWALLOW_LINE (fp);
		}
	    } else if (!strcmp (keyword, "track")) {
		char                    buf[502];

		getc (fp);	/* lose the space */
		/* don't overwrite existing track names. */
		if (trackmap[track] == -1)
		    SWALLOW_LINE (fp)
			else
		    if (cd->trk[trackmap[track]].songname &&
			cd->trk[trackmap[track]].songname[0])
			do
			    i = getc (fp);
			while (i != '\n' && i != EOF);
		    else {
			fgets (buf, sizeof (buf), fp);
			if ((i = strlen (buf)) != 0)
			    buf[i - 1] = '\0';
			strmcpy (&cd->trk[trackmap[track]].songname,
				 buf);
		    }
		track++;
	    } else if (!strcmp (keyword, "playmode"))
		fscanf (fp, "%d", &cd->playmode);

	    else if (!strcmp (keyword, "autoplay"))
		cd->autoplay = 1;

	    else if (!strcmp (keyword, "cdname")) {
		getc (fp);	/* lose the space */
		/* don't overwrite existing cd name. */
		if (cd->cdname[0])
		    do
			i = getc (fp);
		    while (i != '\n' && i != EOF);
		else {
		    fgets (cd->cdname, sizeof (cd->cdname), fp);
		    if ((i = strlen (cd->cdname)) != 0 )
			cd->cdname[i - 1] = '\0';
		}
	    } else if (!strcmp (keyword, "artist")) {
		getc (fp);	/* lose the space */
		/* don't overwrite existing artist names. */
		if (cd->artist[0])
		    do
			i = getc (fp);
		    while (i != '\n' && i != EOF);
		else {
		    fgets (cd->artist, sizeof (cd->artist), fp);
		    if ((i = strlen (cd->artist)) != 0 )
			cd->artist[i - 1] = '\0';
		}
	    } else if (!strcmp (keyword, "cdvolume"))
		fscanf (fp, "%d", &cd->volume);

	    else if (!strcmp (keyword, "dontplay")) {
		fscanf (fp, "%d", &i);
		if (trackmap[i - 1] != -1)
		    cd->trk[trackmap[i - 1]].avoid = 1;
	    } else if (!strcmp (keyword, "continue")) {
		if (trackmap[track - 1] != -1)
		    cd->trk[trackmap[track - 1]].contd = 1;
	    } else if (!strcmp (keyword, "volume")) {
		fscanf (fp, "%d", &i);
		if (trackmap[i - 1] == -1)
		    SWALLOW_LINE (fp)
			else
		{
		    i = trackmap[i - 1];
		    fscanf (fp, "%d", &cd->trk[i].volume);
		    if (cd->trk[i].volume > 32)
			cd->trk[i].volume = 0;
		}
	    } else if (!strcmp (keyword, "playlist")) {
		getc (fp);
		fscanf (fp, "%s", listname);

		/* XXX take this out at some point */
		if (!strcmp (listname, "Default"))
		    strcpy (listname, "List A");

		for (i = 0; listname[i]; i++)
		    if (listname[i] == '_')
			listname[i] = ' ';

		l = new_list (cd, listname);
		if (l == NULL) {
		  plnomem:
		    perror ("playlist read");
		    exit (1);
		}
		fscanf (fp, "%d", &listsize);

		l->list = malloc (sizeof (int) * (listsize + 1));

		if (l->list == NULL)
		    goto plnomem;

		/* Leave out tracks that weren't in .workmandb. */
		j = 0;
		for (i = 0; i < listsize; i++) {
		    fscanf (fp, "%d", &scratch);
		    scratch = trackmap[scratch - 1];
		    if (scratch != -1)
			l->list[j++] = scratch + 1;
		}

		l->list[j] = 0;
	    } else if (!strcmp (keyword, "mark")) {
		int                     mark_val = -1,
		                        mark_namelen;
		char                    mark_name[32];

		fscanf (fp, "%d", &mark_val);
		if (mark_val == -1)
		    goto chomp;

		if (getc (fp) != ' ')
		    continue;

		fgets (mark_name, sizeof (mark_name), fp);
		if (( mark_namelen = strlen (mark_name)) != 0)
		    mark_name[mark_namelen - 1] = '\0';

/*		if (!strcmp (mark_name, "START"))
		    set_abtimer (0, mark_val);
		else if (!strcmp (mark_name, "END"))
		    set_abtimer (1, mark_val);
		    */    }
	/* Unrecognized keyword.  Put it in the right place. */
	    else {
		char                  **buf,
		                        input[BUFSIZ];

		if (track && trackmap[track - 1] == -1) {
		    SWALLOW_LINE (fp);
		    continue;
		}
		i = track ? trackmap[track - 1] : 0;
		buf = prefs ? i ? &cd->trk[i].otherrc : &cd->otherrc :
		    i ? &cd->trk[i].otherdb : &cd->otherdb;
		if (firstpos == -1)
		    if (prefs)
			buf = &otherrc;
		    else
			goto chomp;

		strmcat (buf, keyword);
		do {
		    input[sizeof (input) - 1] = 'x';
		    fgets (input, sizeof (input), fp);
		    strmcat (buf, input);
		} while (input[sizeof (input) - 1] != 'x');
	    }
    }

    if (rclen == 0 && !searching)
	rclen = pos - rcpos;

    return (!searching);
}

/*
 * Delay some amount of time without using interval timers.
 */
void
spinwheels (secs)
     int                     secs;
{
    struct timeval          tv;

    tv.tv_usec = 0;
    tv.tv_sec = secs;
    select (0, NULL, NULL, NULL, &tv);
}

/*
 * Lock a file.  Time out after a little while if we can't get a lock;
 * this usually means the locking system is broken.
 *
 * Unfortunately, if there are lots of people contending for a lock,
 * this can result in the file not getting locked when it probably should.
 */
int
lockit (fd, type)
     int                     fd;
     int                     type;
{
    struct flock            fl;
    int                     result,
                            timer = 0;

    if (suppress_locking)
	return (0);

    fl.l_type = type;
    fl.l_whence = 0;
    fl.l_start = 0;
    fl.l_len = 0;

    while ((result = fcntl (fd, F_SETLK, &fl)) < 0) {
	if (errno != EACCES || errno != EAGAIN)
	    break;
	if (timer++ == 30) {
	    errno = ETIMEDOUT;
	    break;
	}
	spinwheels (1);
    }

    return (result);
}

/*
 * Search all the database files and our personal preference file for
 * more information about the current CD.
 */
void         load( void )
{
    FILE                   *fp;
    char                  **dbfile;
    int                     locked = 0;

    if (databases == NULL)
	split_workmandb ();

    dbfile = databases;

    found_in_db = 0;

    do {
	fp = open_rcfile (*dbfile, "r");
	if (fp != NULL) {
	    if (lockit (fileno (fp), F_RDLCK))
		perror ("Couldn't get read lock");
	    else
		locked = 1;

	    if (search_db (fp, 0, 0, 0)) {
		found_in_db = 1;
		cd->whichdb = *dbfile;
	    }
	    if (locked && lockit (fileno (fp), F_UNLCK))
		perror ("Couldn't relinquish lock");

	    fclose (fp);
	}
    } while (*++dbfile != NULL && cd->whichdb == NULL);

    fp = open_rcfile (rcfile, "r");
    if (fp != NULL) {
	locked = 0;
	if (lockit (fileno (fp), F_RDLCK))
	    perror ("Couldn't get read lock");
	else
	    locked = 1;

	rcpos = 0;
	found_in_rc = search_db (fp, 1, 0, 0);
	if (!found_in_rc)
	    cd->autoplay = 0;

	if (locked && lockit (fileno (fp), F_UNLCK))
	    perror ("Couldn't relinquish lock");

	fclose (fp);
    }
    if (cur_playnew == -1)
	cur_playnew = 0;
}

/*
 * save_globals()
 *
 * Save the global preferences, scooting CD entries to the end if needed.
 * The assumption here is that the rcfile is locked, and that firstpos has
 * been set by a previous scan.
 */
void
save_globals (fp)
     FILE                   *fp;
{
    char                   *globes = NULL,
                           *cdentry = NULL,
                            temp[100];
    long                    curpos;
    int                     globesize,
                            hit_cdent = 0,
                            c = 0;

    if (otherrc)
	strmcpy (&globes, otherrc);

    if (cur_stopmode == 1 || cur_stopmode == 2) {
	sprintf (temp, "whendone %s\n", cur_stopmode == 1 ? "repeat" :
		 "eject");
	strmcat (&globes, temp);
    }
    if (cur_playnew == 1)
	strmcat (&globes, "playnew\n");

    curpos = firstpos;
    if (curpos < 0)
	curpos = 0;

    fseek (fp, curpos, SEEK_SET);

    if (firstpos < (globesize = globes != NULL ? strlen (globes) : 0)) {
	while (1) {
	    temp[sizeof (temp) - 1] = 'x';

	    if (fgets (temp, sizeof (temp), fp) == NULL) {
		fseek (fp, 0, SEEK_SET);
		if (globes != NULL) {
		    fwrite (globes, globesize, 1, fp);
		    free (globes);
		}
		if (cdentry != NULL) {
		    fwrite (cdentry, strlen (cdentry), 1, fp);
		    free (cdentry);
		}
		return;
	    }
	    if (!strncmp (temp, "tracks ", 7)) {
		hit_cdent = 1;
		if (curpos >= globesize)
		    break;
	    }
	    if (!hit_cdent) {
		curpos += strlen (temp);
		if (temp[sizeof (temp) - 1] == '\0')
		    while ((c = getc (fp)) != '\n' &&
			   c != EOF)
			curpos++;
		if (c == '\n')
		    curpos++;

		continue;
	    }
	    strmcat (&cdentry, temp);
	    curpos += strlen (temp);
	    while (temp[sizeof (temp) - 1] == '\0') {
		temp[sizeof (temp) - 1] = 'x';
		if (fgets (temp, sizeof (temp), fp) == NULL)
		    break;
		strmcat (&cdentry, temp);
		curpos += strlen (temp);
	    }
	}

	if (cdentry != NULL) {
	    fseek (fp, 0, SEEK_END);
	    fwrite (cdentry, strlen (cdentry), 1, fp);
	    free (cdentry);
	}
    }
    if (globes != NULL) {
	fseek (fp, 0, SEEK_SET);
	fwrite (globes, globesize, 1, fp);
	free (globes);
    }
    while (globesize++ < curpos)
	putc ('\n', fp);
}

/*
 * save_entry()
 *
 * Save the CD information to one database.
 *
 *	fp	FILE* of database to save to.
 *	pref	0 for hard data, 1 for preferences.
 *
 * If an entry for this CD exists already, overwrite it with the new entry
 * if the new entry is the same size or smaller, or with newlines if the new
 * entry is larger (in which case the new entry is appended to the file.)
 *
 * Also, if the preference information is being updated, save it to the
 * file while we've got it locked.  Scoot stuff from the beginning of
 * the file to the end as needed to facilitate this.
 *
 * XXX Preference-saving should probably be done elsewhere, like in an
 * Apply button on the Goodies popup.
 */
int save_entry (fp, pref)
     FILE                   *fp;
     int                     pref;
{
    char                   *buf;
    int                     len,
                            i,
                            locked = 0;

    if (lockit (fileno (fp), F_WRLCK))
	perror ("Warning: Couldn't get write lock");
    else
	locked = 1;

    buf = print_cdinfo (cd, pref);
    len = strlen (buf);		/* doesn't return if there's an error */

    rcpos = -1;
    search_db (fp, pref, 1, len);
    if (rcpos != -1) {		/* XXX */
	fseek (fp, rcpos, SEEK_SET);
	if (rclen >= len && holepos == -1) {
	    fputs (buf, fp);
	    for (i = len; i < rclen; i++)
		fputc ('\n', fp);
	} else {
	    for (i = 0; i < rclen; i++)
		fputc ('\n', fp);
	    if (holepos >= 0)
		fseek (fp, holepos, SEEK_SET);
	    else
		fseek (fp, 0, SEEK_END);
	    fputs (buf, fp);
	}
    } else {
	if (holepos >= 0)
	    fseek (fp, holepos, SEEK_SET);
	else
	    fseek (fp, 0, SEEK_END);
	fputs (buf, fp);
    }

    if (pref)
	save_globals (fp);

    fflush (fp);

    if (locked && lockit (fileno (fp), F_UNLCK))
	perror ("Warning: Couldn't relinquish write lock");

    return  0;
}

/*
 * save()
 *
 * Save CD information to the appropriate datafile (the first file in the
 * list, unless the entry came from another database file) and to the
 * personal prefs file.
 */
int   save ( void )
{
    FILE                   *fp;

    fp = open_rcfile (rcfile, "r+");
    if (fp == NULL) {
	if (errno == ENOENT)	/* doesn't exist already */
	    fp = open_rcfile (rcfile, "w");
	if (fp == NULL)
	    return (0);
    }
    save_entry (fp, 1);

    fclose (fp);

    if (cd->whichdb == NULL || access (cd->whichdb, W_OK))
	cd->whichdb = databases[0];

    if ((fp = open_rcfile (cd->whichdb, "r+")) == NULL) {
	if (errno == ENOENT)
	    fp = open_rcfile (cd->whichdb, "w");
	if (fp == NULL)
	    return (0);
    }
    save_entry (fp, 0);
    fclose (fp);

    return (1);
}
