summaryrefslogtreecommitdiffstats
path: root/kscd/libwm/database.c
diff options
context:
space:
mode:
Diffstat (limited to 'kscd/libwm/database.c')
-rw-r--r--kscd/libwm/database.c1543
1 files changed, 1543 insertions, 0 deletions
diff --git a/kscd/libwm/database.c b/kscd/libwm/database.c
new file mode 100644
index 00000000..f7f4e7cc
--- /dev/null
+++ b/kscd/libwm/database.c
@@ -0,0 +1,1543 @@
+/*
+ * $Id$
+ *
+ * This file is part of WorkMan, the civilized CD player library
+ * (c) 1991-1997 by Steven Grimm (original author)
+ * (c) by Dirk F�rsterling (current 'author' = maintainer)
+ * The maintainer can be contacted by his e-mail address:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * 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"
+#define FUZZFRAMES 75
+
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <time.h>
+#include "include/wm_config.h"
+#include "include/wm_helpers.h"
+#include "include/wm_struct.h"
+#include "include/wm_cdinfo.h"
+#include "include/wm_cddb.h"
+#include "include/wm_index.h"
+#include "include/wm_database.h"
+
+#define WM_MSG_CLASS WM_MSG_CLASS_DB
+
+#define SWALLOW_LINE(fp) { int _c; while ((_c = getc(fp)) != '\n' && _c != EOF); }
+
+/* local prototypes */
+char *print_cdinfo(struct wm_cdinfo *pcd, int prefs);
+FILE *open_rcfile(const char *name, const char *mode);
+int *reset_tracks(void);
+int search_db( FILE *fp, int prefs, int scan, int holesize_wanted );
+void spinwheels(int secs);
+int lockit(int fd, int type);
+void save_globals(FILE *fp);
+int save_entry(char *filename, int pref);
+/* local prototypes END */
+
+static int suppress_locking = 0; /* Turn off locking of datafile (dangerous) */
+
+static char *rcfile = NULL; /* Personal rcfile */
+static char *dbfiles = NULL; /* Colon-separated list of databases */
+static char **databases = NULL; /* NULL-terminated list of databases */
+
+static char *otherrc = NULL; /* Unrecognized cruft from start of rcfile */
+
+static long rcpos, rclen; /* XXX */
+
+static int found_in_db, found_in_rc;
+static long holepos, firstpos;
+
+static int fuzz_frames = FUZZFRAMES;
+
+static int wm_db_save_disabled = FALSE;
+
+static int cur_playnew = -1;
+
+extern int cur_ntracks, cur_nsections;
+
+int mark_a = 0;
+int mark_b = 0;
+
+
+/*
+ *
+ */
+int wm_db_get_playnew( void )
+{
+ return 0;
+}
+
+/*
+ * 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.
+ *
+ * The environment variables should have already been read and placed in the
+ * "rcfile" and "dbfiles" globals, respectively.
+ */
+void
+split_workmandb( void )
+{
+ int ndbs, i;
+ char *home, *wmdb;
+ int no_rc = 0, no_db = 0;
+
+ if (rcfile == NULL)
+ {
+ if ((home = getenv("HOME")) != NULL)
+ {
+ rcfile = malloc(strlen(home) + sizeof(RCFILE));
+ if (rcfile == NULL)
+ {
+
+nomem:
+ perror("split_workmandb()");
+ exit(1);
+ }
+
+ strcpy(rcfile, home);
+ strcat(rcfile, RCFILE);
+ }
+ else
+ no_rc = 1;
+
+ }
+
+ if ((wmdb = dbfiles) == NULL)
+ {
+ if ((home = getenv("HOME")) != NULL)
+ {
+ 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
+ {
+ static char *emptydb = NULL;
+
+ databases = &emptydb;
+ no_db = 1;
+ }
+ }
+ 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;
+ }
+
+ if (no_db || no_rc)
+ {
+#ifndef NDEBUG
+ fprintf(stderr,
+"WorkMan was run without a home directory, probably by a system daemon.\n");
+ fprintf(stderr, "It doesn't know where to find ");
+ if (no_rc)
+ {
+ fprintf(stderr, "your personal preferences file ");
+ if (no_db)
+ fprintf(stderr, "or the\ndatabase of CD descriptions");
+ }
+ else
+ fprintf(stderr, "the database of CD descriptions");
+
+ fprintf(stderr,
+".\nYou can use the X resources \"workman.db.shared\" and \"workman.db.personal\"\nto tell WorkMan where to look.\n");
+#endif
+ wm_db_save_disabled = TRUE;
+ }
+}
+
+/*
+ * 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 wm_strmcat() and strlen().
+ */
+char *
+print_cdinfo(struct wm_cdinfo *cd, int prefs)
+{
+ int i;
+ char tempbuf[2000]; /* XXX - is this always big enough? */
+ static char *cdibuf = NULL;
+ struct wm_playlist *l;
+
+ sprintf(tempbuf, "\ntracks %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);
+
+ wm_strmcpy(&cdibuf, tempbuf);
+
+ if (cur_nsections)
+ {
+ sprintf(tempbuf, "sections %d", cur_nsections);
+ /* fixed a bug here */
+ 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");
+
+ wm_strmcat(&cdibuf, tempbuf);
+ }
+
+ if (prefs)
+ {
+ if (cd->autoplay)
+ wm_strmcat(&cdibuf, "autoplay\n");
+ for (l = cd->lists; l != NULL && l->name != NULL; l++)
+ {
+ wm_strmcat(&cdibuf, "playlist ");
+
+ i = strlen(cdibuf) - 1;
+ wm_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);
+ wm_strmcat(&cdibuf, tempbuf);
+ for (i = 0; l->list[i]; i++)
+ {
+ sprintf(tempbuf, " %d", l->list[i]);
+ wm_strmcat(&cdibuf, tempbuf);
+ }
+ wm_strmcat(&cdibuf, "\n");
+ }
+ else
+ wm_strmcat(&cdibuf, " 0\n");
+ }
+
+ if (cd->volume)
+ {
+ /*
+ * Have to maintain compatibility with old versions,
+ * where volume was 0-32.
+ */
+ sprintf(tempbuf, "cdvolume %d\n", (cd->volume * 32) / 100);
+ wm_strmcat(&cdibuf, tempbuf);
+ }
+
+ if (cd->playmode)
+ {
+ sprintf(tempbuf, "playmode %d\n", cd->playmode);
+ wm_strmcat(&cdibuf, tempbuf);
+ }
+
+ if (mark_a)
+ {
+ sprintf(tempbuf, "mark %d START\n", mark_a);
+ wm_strmcat(&cdibuf, tempbuf);
+ }
+ if (mark_b)
+ {
+ sprintf(tempbuf, "mark %d END\n", mark_b);
+ wm_strmcat(&cdibuf, tempbuf);
+ }
+
+ if (cd->otherrc)
+ wm_strmcat(&cdibuf, cd->otherrc);
+
+ for (i = 0; i < cur_ntracks; i++)
+ {
+ if (cd->trk[i].avoid)
+ {
+ sprintf(tempbuf, "dontplay %d\n", i + 1);
+ wm_strmcat(&cdibuf, tempbuf);
+ }
+ if (cd->trk[i].volume)
+ {
+ sprintf(tempbuf, "volume %d %d\n", i + 1,
+ (cd->trk[i].volume * 32) / 100);
+ wm_strmcat(&cdibuf, tempbuf);
+ }
+ if (cd->trk[i].otherrc)
+ wm_strmcat(&cdibuf, cd->trk[i].otherrc);
+ }
+ }
+ else
+ {
+ if (cd->cdname[0])
+ {
+ wm_strmcat(&cdibuf, "cdname ");
+ wm_strmcat(&cdibuf, cd->cdname);
+ wm_strmcat(&cdibuf, "\n");
+ }
+
+ if (cd->artist[0])
+ {
+ wm_strmcat(&cdibuf, "artist ");
+ wm_strmcat(&cdibuf, cd->artist);
+ wm_strmcat(&cdibuf, "\n");
+ }
+
+ if (cd->otherdb)
+ wm_strmcat(&cdibuf, cd->otherdb);
+
+ for (i = 0; i < cur_ntracks; i++)
+ {
+ if (cd->trk[i].section > 1)
+ wm_strmcat(&cdibuf, "s-");
+ wm_strmcat(&cdibuf, "track ");
+ if (cd->trk[i].songname != NULL)
+ wm_strmcat(&cdibuf, cd->trk[i].songname);
+ wm_strmcat(&cdibuf, "\n");
+ if (cd->trk[i].contd)
+ {
+ if (cd->trk[i].section > 1)
+ wm_strmcat(&cdibuf, "s-");
+ wm_strmcat(&cdibuf, "continue\n");
+ }
+ if (cd->trk[i].otherdb)
+ wm_strmcat(&cdibuf, cd->trk[i].otherdb);
+ }
+ }
+
+ return (cdibuf);
+} /* print_cdinfo() */
+
+/*
+ * Open the rcfile for reading or writing.
+ *
+ * name Filename
+ * mode "r" or "w"
+ */
+FILE *
+open_rcfile(const char *name, const char *mode)
+{
+ FILE *fp;
+ struct stat st;
+
+ fp = fopen(name, mode);
+ if (fp == NULL)
+ {
+ if (errno != ENOENT || mode[0] == 'w')
+ perror(name);
+ }
+ else
+ {
+ /* Don't let people open directories or devices */
+ if (fstat(fileno(fp), &st) < 0)
+ {
+ perror(name);
+ fclose(fp);
+ return (NULL);
+ }
+
+#ifdef S_ISREG
+ if (! S_ISREG(st.st_mode))
+#else
+ if ((st.st_mode & S_IFMT) != S_IFREG)
+#endif
+ {
+ errno = EISDIR;
+ perror(name);
+ fclose(fp);
+ return (NULL);
+ }
+
+ 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);
+}
+
+/*
+ *
+ * allocate and clear "trackmap".
+ *
+ */
+int *reset_tracks(void)
+{
+ int i, j;
+ int *trackmap;
+ /*
+ * 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)
+ ;
+ }
+ return trackmap;
+} /* reset_tracks() */
+
+/*
+ * 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,
+ * 2 if just reading settings
+ * 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( FILE *fp, int prefs, int scan, int holesize_wanted )
+
+{
+ char keyword[64], listname[64], *c;
+ int b, i, j, track = 0, listsize, ntracks, scratch, searching = 1;
+ int *trackmap = 0, gotsections = 0;
+ int fudge, maxfudge, sizediff, bestfudge = 0;
+ long pos = 0, thisholepos = -1, holesize = 99991239;
+ struct wm_playlist *l;
+
+ rclen = 0;
+
+ wm_lib_message(WM_MSG_CLASS_DB|WM_MSG_LEVEL_DEBUG , "db: Reached search_db()\n" );
+
+ /* We may not find any holes at all! */
+ if (holesize_wanted)
+ holepos = -1;
+
+ if( prefs != 2 )
+ trackmap = reset_tracks();
+
+ if (prefs)
+ freeup(&otherrc);
+ firstpos = -1;
+ while (! feof(fp))
+ {
+ pos = ftell(fp);
+ keyword[0] = '\0';
+ do
+ b = getc(fp);
+ while (b != EOF && b != '\n' && isspace(b));
+
+ if (b == EOF || feof(fp))
+ break;
+
+ if (b != '\n')
+ {
+ keyword[0] = b;
+ fscanf(fp, "%62s", &keyword[1]);
+ }
+ 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++)
+ ;
+ wm_lib_message(WM_MSG_CLASS_DB|WM_MSG_LEVEL_DEBUG , "db: stripped off the 's-'. Result is %s\n", keyword);
+ }
+
+ /* If this is the start of a CD entry, see if it matches. */
+ if (! strcmp(keyword, "tracks"))
+ {
+ if (prefs == 2)
+ break;
+
+ /* 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;
+ }
+
+ /* If we have a near match, indicate that we
+ should stop reading tracks, etc now */
+ if (searching == 2)
+ {
+ searching = 3;
+ continue;
+ }
+
+ fscanf(fp, "%d", &ntracks);
+
+ if (ntracks != cd->ntracks)
+ {
+chomp:
+ SWALLOW_LINE(fp);
+ continue;
+ }
+
+ fudge = 0;
+ maxfudge = (ntracks * fuzz_frames) >> 1;
+ track = 0;
+ for (i = 0; i < ntracks; i++)
+ {
+ fscanf(fp, "%d", &scratch);
+ if (scratch != cd->trk[track].start)
+ {
+ sizediff = abs(scratch - cd->trk[track].start);
+ if (sizediff > fuzz_frames ||
+ (sizediff && scan))
+ break;
+ fudge += sizediff;
+ }
+ while (cd->trk[++track].section > 1)
+ ;
+ }
+ if (i != ntracks)
+ goto chomp;
+
+ if (fudge > 0) /* best near match? */
+ {
+ if (fudge > maxfudge)
+ goto chomp;
+ if (bestfudge == 0 || fudge < bestfudge)
+ bestfudge = fudge;
+ else
+ goto chomp;
+ rcpos = pos;
+ track = 0;
+ searching = 2;
+ }
+ else /* probably exact match */
+ {
+ 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, "cddbprotocol"))
+ {
+ getc(fp);
+ i = getc(fp); /* only first letter is used */
+ cddb.protocol = i == 'c' ? 1 :
+ i == 'h' ? 2 : 3 ;
+ do
+ i = getc(fp);
+ while (i != '\n' && i != EOF);
+ }
+
+ else if (! strcmp(keyword, "cddbserver"))
+ {
+ getc(fp); /* lose the space */
+ if (cddb.cddb_server[0])
+ do
+ i = getc(fp);
+ while (i != '\n' && i != EOF);
+ else
+ {
+ fgets(cddb.cddb_server,
+ sizeof(cddb.cddb_server), fp);
+ if ((i = strlen(cddb.cddb_server)))
+ cddb.cddb_server[i - 1] = '\0';
+ }
+ }
+
+ else if (! strcmp(keyword, "cddbmailadress"))
+ {
+ getc(fp); /* lose the space */
+ if (cddb.mail_adress[0])
+ do
+ i = getc(fp);
+ while (i != '\n' && i != EOF);
+ else
+ {
+ fgets(cddb.mail_adress,
+ sizeof(cddb.mail_adress), fp);
+ if ((i = strlen(cddb.mail_adress)))
+ cddb.mail_adress[i - 1] = '\0';
+ }
+ }
+
+ else if (! strcmp(keyword, "cddbpathtocgi"))
+ {
+ getc(fp); /* lose the space */
+ if (cddb.path_to_cgi[0])
+ do
+ i = getc(fp);
+ while (i != '\n' && i != EOF);
+ else
+ {
+ fgets(cddb.path_to_cgi,
+ sizeof(cddb.path_to_cgi), fp);
+ if ((i = strlen(cddb.path_to_cgi)))
+ cddb.path_to_cgi[i - 1] = '\0';
+ }
+ }
+
+ else if (! strcmp(keyword, "cddbproxy"))
+ {
+ getc(fp); /* lose the space */
+ if (cddb.proxy_server[0])
+ do
+ i = getc(fp);
+ while (i != '\n' && i != EOF);
+ else
+ {
+ fgets(cddb.proxy_server,
+ sizeof(cddb.proxy_server), fp);
+ if ((i = strlen(cddb.proxy_server)))
+ cddb.proxy_server[i - 1] = '\0';
+ }
+ }
+
+ 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;
+ SWALLOW_LINE(fp);
+ }
+
+ /* If we're searching, skip to the next "tracks" line. */
+ else if (((searching & 1)|| 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. */
+ /* However, overwrite them if there was a "bad" fuzzy match before */
+ if ((trackmap[track] == -1 || track > (cd->ntracks + cur_nsections)) && (searching == 2))
+ 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);
+ wm_lib_message(WM_MSG_CLASS_DB|WM_MSG_LEVEL_DEBUG, "found track %s\n", buf);
+ if( (i = strlen(buf)) )
+ buf[i - 1] = '\0';
+ wm_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"))
+ {
+ /* because of fuzzy matching that may change
+ the disk contents, we reset everything when
+ we find the name, in hopes that we will recover
+ most, if not all, of the information from the
+ file. */
+/*
+ * nasty bug was here. Was it? BUGBUGBUG
+ *
+ * wipe_cdinfo();
+ * trackmap = reset_tracks();
+ */
+
+ getc(fp); /* lose the space */
+ /* don't overwrite existing cd name. */
+ if (cd->cdname[0] && (searching == 2))
+ do
+ i = getc(fp);
+ while (i != '\n' && i != EOF);
+ else
+ {
+ if (searching > 1)
+ {
+ strcpy(cd->cdname, "Probably://");
+ fgets(cd->cdname + strlen(cd->cdname), sizeof(cd->cdname), fp);
+ }
+ else
+ {
+ fgets(cd->cdname, sizeof(cd->cdname), fp);
+ }
+ if ( (i = strlen(cd->cdname)) )
+ 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)) )
+ cd->artist[i - 1] = '\0';
+ }
+ }
+
+ else if (! strcmp(keyword, "cdvolume"))
+ {
+ fscanf(fp, "%d", &cd->volume);
+ cd->volume = (cd->volume * 100) / 32;
+ }
+
+ 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);
+ cd->trk[i].volume = (cd->trk[i].volume*100)/32;
+ if (cd->trk[i].volume > 32)
+ cd->trk[i].volume = 0;
+ }
+ }
+
+ else if (! strcmp(keyword, "playlist"))
+ {
+ getc(fp);
+ fscanf(fp, "%63s", 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_playlist(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)) )
+ 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;
+ } /* if() else */
+ } /* if() */
+ wm_strmcat(buf, keyword);
+ do {
+ input[sizeof(input) - 1] = 'x';
+ fgets(input, sizeof(input), fp);
+ wm_strmcat(buf, input);
+ } while (input[sizeof(input) - 1] != 'x');
+ }
+ }
+
+ if (rclen == 0 && !searching)
+ rclen = pos - rcpos;
+
+ if (searching > 1) /* A near match has been found. Good enough. */
+ searching = 0;
+
+ cddb_struct2cur();
+ return (! searching);
+
+} /* search_db() */
+
+/*
+ * Delay some amount of time without using interval timers.
+ */
+void
+spinwheels(int secs) {
+ struct timeval tv;
+
+ tv.tv_usec = 0;
+ tv.tv_sec = secs;
+ select(0, NULL, NULL, NULL, &tv);
+} /* spinwheels() */
+
+/*
+ * lockit(fd, type)
+ *
+ * fd file descriptor
+ * type lock type
+ *
+ * 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(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);
+} /* lockit() */
+
+/*
+ * 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;
+ int dbfound = 0, *trklist, i;
+ unsigned long dbpos;
+
+/* This is some kind of profiling code. I don't change it
+ to wm_lib_message() for now... */
+#ifndef NDEBUG
+ long t1, t2;
+ if( getenv( "WORKMAN_DEBUG" ) != NULL )
+ {
+ time(&t1);
+ printf("%s (%d): search start = %ld\n", __FILE__, __LINE__, t1);
+ fflush(stdout);
+ }
+#endif
+
+ dbfile = databases;
+
+ found_in_db = 0;
+
+ /* Turn the cd->trk array into a simple array of ints. */
+ trklist = (int *)malloc(sizeof(int) * cd->ntracks);
+ for (i = 0; i < cd->ntracks; i++)
+ trklist[i] = cd->trk[i].start;
+
+ do {
+ if (*dbfile && idx_find_entry(*dbfile, cd->ntracks, trklist,
+ cd->length * 75, 0, &dbpos) == 0)
+ dbfound = 1;
+
+ fp = *dbfile ? open_rcfile(*dbfile, "r") : NULL;
+ if (fp != NULL)
+ {
+ if (lockit(fileno(fp), F_RDLCK))
+ perror("Couldn't get read (db) lock");
+ else
+ locked = 1;
+
+ if (dbfound)
+ fseek(fp, dbpos, 0);
+
+ 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 (db) lock");
+
+ fclose(fp);
+ }
+ } while (*++dbfile != NULL && cd->whichdb == NULL);
+
+#ifndef NDEBUG
+ if( getenv( "WORKMAN_DEBUG" ) != NULL )
+ {
+ time(&t2);
+ printf("%s (%d): db search end = %ld, elapsed = %ld\n", __FILE__, __LINE__, t2, t2 - t1);
+ fflush(stdout);
+ }
+#endif
+
+ fp = rcfile ? open_rcfile(rcfile, "r") : NULL;
+ if (fp != NULL)
+ {
+ locked = 0;
+ if (lockit(fileno(fp), F_RDLCK))
+ perror("Couldn't get read (rc) lock");
+ else
+ locked = 1;
+
+ rcpos = 0;
+ found_in_rc = search_db(fp, 1, 0, 0);
+ if (! found_in_rc)
+ cd->autoplay = wm_db_get_playnew();
+
+ if (locked && lockit(fileno(fp), F_UNLCK))
+ perror("Couldn't relinquish (rc) lock");
+
+ fclose(fp);
+ }
+
+ free(trklist);
+
+ if (cur_playnew == -1)
+ cur_playnew = 0;
+
+#ifndef NDEBUG
+ if( getenv( "WORKMAN_DEBUG" ) != NULL )
+ {
+ time(&t2);
+ printf("%s (%d): search end = %ld, elapsed = %ld\n", __FILE__, __LINE__, t2, t2 - t1);
+ fflush(stdout);
+ }
+#endif
+} /* load() */
+
+/*
+ * Load program settings from the rcfile.
+ */
+void
+load_settings( void )
+{
+ FILE *fp;
+ int locked;
+
+ fp = rcfile ? open_rcfile(rcfile, "r") : NULL;
+ if (fp != NULL)
+ {
+ locked = 0;
+ if (lockit(fileno(fp), F_RDLCK))
+ perror("Couldn't get read (rc) lock");
+ else
+ locked = 1;
+
+ rcpos = 0;
+ found_in_rc = search_db(fp, 2, 0, 0);
+ if (! found_in_rc)
+ cd->autoplay = wm_db_get_playnew();
+
+ if (locked && lockit(fileno(fp), F_UNLCK))
+ perror("Couldn't relinquish (rc) lock");
+
+ fclose(fp);
+ }
+} /* load_settings() */
+
+/*
+ * 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(FILE *fp)
+{
+ char *globes = NULL, *cdentry = NULL, temp[100];
+ long curpos;
+ int globesize, hit_cdent = 0, c = 0;
+
+ if (otherrc)
+ wm_strmcpy(&globes, otherrc);
+
+ if (cddb.protocol)
+ {
+ sprintf(temp, "cddbprotocol ");
+ switch(cddb.protocol)
+ {
+ case 1: /* cddbp */
+ sprintf(temp + strlen(temp), "cddbp\n");
+ break;
+ case 2: /* http */
+ sprintf(temp + strlen(temp), "http\n");
+ break;
+ case 3: /* proxy */
+ sprintf(temp + strlen(temp), "proxy\n");
+ break;
+ default:
+ break;
+ }
+ wm_strmcat(&globes, temp);
+
+ if(cddb.mail_adress[0])
+ {
+ sprintf(temp,"cddbmailadress %s\n",
+ cddb.mail_adress);
+ wm_strmcat(&globes, temp);
+ }
+
+ if(cddb.cddb_server[0])
+ {
+ sprintf(temp,"cddbserver %s\n",
+ cddb.cddb_server);
+ wm_strmcat(&globes, temp);
+ }
+
+ if(cddb.path_to_cgi[0])
+ {
+ sprintf(temp,"cddbpathtocgi %s\n",
+ cddb.mail_adress);
+ wm_strmcat(&globes, temp);
+ }
+
+ if(cddb.proxy_server[0])
+ {
+ sprintf(temp,"cddbproxy %s\n",
+ cddb.mail_adress);
+ wm_strmcat(&globes, temp);
+ }
+ }
+
+ if (cur_stopmode == 1 || cur_stopmode == 2)
+ {
+ sprintf(temp, "whendone %s\n", cur_stopmode == 1 ? "repeat" :
+ "eject");
+ wm_strmcat(&globes, temp);
+ }
+
+ if (cur_playnew == 1)
+ wm_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;
+ }
+
+ wm_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;
+ wm_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_globals() */
+
+/*
+ * save_entry()
+ *
+ * Save the CD information to one database.
+ *
+ * filename 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, and in any case needs to go to a
+ * different file (.Xdefaults?)
+ *
+ * Returns 0 on success.
+ */
+int
+save_entry(char *filename, int pref)
+{
+ FILE *fp;
+ char *buf;
+ int len, i, locked = 0;
+
+
+ if( filename == NULL )
+ return (-1);
+
+ fp = open_rcfile(filename, "r+");
+ if (fp == NULL)
+ {
+ if (errno == ENOENT) /* doesn't exist already */
+ fp = open_rcfile(filename, "w");
+ if (fp == NULL)
+ return (-1);
+ }
+
+ 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 */
+ {
+ /*
+ * Jump to the entry's position in the database file, if
+ * it was found.
+ */
+ fseek(fp, rcpos, SEEK_SET);
+
+ if (rclen >= len && holepos == -1)
+ {
+ /*
+ * If the new entry will fit in the space occupied by
+ * the old one, overwrite the old one and make a hole
+ * of the appropriate size at its end.
+ *
+ * No need to update the index file in this case, as
+ * the entry's position hasn't changed.
+ */
+ fputs(buf, fp);
+ for (i = len; i < rclen; i++)
+ fputc('\n', fp);
+ }
+ else
+ {
+ /*
+ * Overwrite the old entry with a hole and delete
+ * its pointer in the index file.
+ */
+ for (i = 0; i < rclen; i++)
+ fputc('\n', fp);
+ idx_delete_entry(filename, cd->trk[cd->ntracks-1].start,
+ 0, rcpos);
+
+ rcpos = -1;
+ }
+ }
+
+ /*
+ * Old entry wasn't found, or its new version wouldn't fit where
+ * the old one was.
+ */
+ if (rcpos == -1)
+ {
+ /*
+ * Write the new entry in a hole, if there is one,
+ * or at the end of the file.
+ */
+ if (holepos >= 0)
+ {
+ fseek(fp, holepos, SEEK_SET);
+ if (holepos < firstpos)
+ firstpos = holepos;
+ }
+ else
+ {
+ fseek(fp, 0, SEEK_END);
+ holepos = ftell(fp);
+ }
+ fputs(buf, fp);
+
+ /*
+ * Write a new index entry for this CD.
+ */
+ idx_write_entry(filename, cd->trk[cd->ntracks - 1].start,
+ holepos);
+ }
+
+ if (pref)
+ save_globals(fp);
+
+ fflush(fp);
+
+ if (locked && lockit(fileno(fp), F_UNLCK))
+ perror("Warning: Couldn't relinquish write lock");
+
+ fclose(fp);
+
+ return (0);
+} /* save_entry() */
+
+/*
+ * 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 )
+{
+
+ if( wm_db_save_disabled == FALSE )
+ {
+ if (save_entry(rcfile, 1))
+ return (0);
+
+ if (cd->whichdb == NULL || access(cd->whichdb, W_OK))
+ cd->whichdb = databases[0];
+
+ if (save_entry(cd->whichdb, 0))
+ return (0);
+
+ return( WM_DB_SAVE_ERROR );
+ } else {
+ return( WM_DB_SAVE_DISABLED );
+ }
+} /* save() */