/* * $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: * milliByte@DeathsDoor.com * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 #include #include #include #include #include #include #include #include #include #include #include #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 time_t t1, t2; if( getenv( "WORKMAN_DEBUG" ) != NULL ) { time(&t1); printf("%s (%d): search start = %.0f\n", __FILE__, __LINE__, difftime(t1, (time_t)0)); 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 = %.0f, elapsed = %.0f\n", __FILE__, __LINE__, difftime(t2, (time_t)0), difftime(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 = %.0f, elapsed = %.0f\n", __FILE__, __LINE__, difftime(t2, (time_t)0), difftime(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() */