# HG changeset patch # User Matti Hamalainen # Date 1054749603 0 # Node ID f5d82424b0ed94b2af52c885d7e107d360def4a9 # Parent 7e664541ea3670d899e6edaf066594a23e1070c3 Made song-length database support FINALLY work! diff -r 7e664541ea36 -r f5d82424b0ed src/xs_length.c --- a/src/xs_length.c Wed Jun 04 14:04:02 2003 +0000 +++ b/src/xs_length.c Wed Jun 04 18:00:03 2003 +0000 @@ -21,52 +21,174 @@ */ #include "xmms-sid.h" +#include "xs_support.h" #include "xs_length.h" +#include "xs_config.h" +#include "xs_md5.h" #include #include #include #include + /* * Pointer to database in memory */ -static t_xs_dbentry *xs_database = NULL; +static t_xs_dbentry *xs_database = NULL, *xs_dblast = NULL; +static t_xs_dbentry **xs_dbindex = NULL; +static gint xs_dbnodes = 0; + + +/* + * Hash-database handling functions + */ +t_xs_dbentry *xs_db_node_new(void) +{ + t_xs_dbentry *pResult; + + /* Allocate memory for new node */ + pResult = (t_xs_dbentry *) calloc(1, sizeof(t_xs_dbentry)); + if (pResult == NULL) return NULL; + + return pResult; +} + + +void xs_db_node_free(t_xs_dbentry *pNode) +{ + if (pNode) free(pNode); +} + + +/* + * Insert given node to db linked list + */ +void xs_db_node_insert(t_xs_dbentry *pNode) +{ + if (xs_dblast) + { + xs_dblast->pNext = pNode; + xs_dblast = pNode; + } else { + xs_dblast = xs_database = pNode; + pNode->pNext = NULL; + } +} + + +/* + * Compare two given MD5-hashes. + * Return: 0 if equal + * negative if testHash1 < testHash2 + * positive if testHash1 > testHash2 + */ +gint xs_db_cmphash(t_xs_md5hash testHash1, t_xs_md5hash testHash2) +{ + register gint i, res = 0; + + /* Compute difference of hashes */ + for (i = 0; (i < XS_MD5HASH_LENGTH) && (!res); i++) + res = (testHash1[i] - testHash2[i]); + + return res; +} + + +/* + * Get song length from database + */ +t_xs_dbentry * xs_db_get(t_xs_md5hash pHash) +{ + gint iStartNode, iEndNode, iQNode, iFound, r, i; + t_xs_dbentry *pResult; + + /* Check the database pointers */ + if ((xs_database == NULL) || (xs_dbindex == NULL)) + return NULL; + + /* Look-up via index using binary search */ + pResult = NULL; + iStartNode = 0; + iEndNode = (xs_dbnodes - 1); + iQNode = (iEndNode / 2); + iFound = 0; + + while ((!iFound) && ((iEndNode - iStartNode) > 128)) + { + fprintf(stderr, "["); + fprinth(stderr, xs_dbindex[iQNode]->md5Hash); + r = xs_db_cmphash(pHash, xs_dbindex[iQNode]->md5Hash); + fprintf(stderr, "] = %i\n", r); + if (r < 0) + { + /* Hash was in the <- LEFT side */ + iEndNode = iQNode; + iQNode = iStartNode + ((iEndNode - iStartNode) / 2); + } else + if (r > 0) + { + /* Hash was in the RIGHT -> side */ + iStartNode = iQNode; + iQNode = iStartNode + ((iEndNode - iStartNode) / 2); + } else + iFound = 1; + } + + /* If not found already */ + if (!iFound) + { + /* Search the are linearly */ + iFound = 0; + i = iStartNode; + while ((i <= iEndNode) && (!iFound)) + { + if (xs_db_cmphash(pHash, xs_dbindex[i]->md5Hash) == 0) + iFound = 1; + else + i++; + } + + /* Check the result */ + if (iFound) + pResult = xs_dbindex[i]; + + } else { + /* Found via binary search */ + pResult = xs_dbindex[iEndNode]; + } + + + return pResult; +} /* * Parses a time-entry in SLDB format */ -long int xs_gettime(char *pcStr, int *piPos) +gint32 xs_gettime(gchar *pcStr, int *piPos) { - long int iResult; - int iTemp; - char chTempBuf[16]; + gint32 iResult, iTemp; /* Check if it starts with a digit */ if (isdigit(pcStr[*piPos])) { /* Get minutes-field */ - iTemp = *piPos; - xs_findnum(pcStr, piPos); - - strncpy(chTempBuf, &pcStr[iTemp], (*piPos - iTemp)); - chTempBuf[*piPos - iTemp] = 0; - - iResult = (atol(chTempBuf) * 60); + iResult = 0; + while (isdigit(pcStr[*piPos])) + iResult = (iResult * 10) + (pcStr[(*piPos)++] - '0'); + + iResult *= 60; /* Check the field separator char */ if (pcStr[*piPos] == ':') { /* Get seconds-field */ (*piPos)++; - iTemp = *piPos; - xs_findnum(pcStr, piPos); + iTemp = 0; + while (isdigit(pcStr[*piPos])) + iTemp = (iTemp * 10) + (pcStr[(*piPos)++] - '0'); - strncpy(chTempBuf, &pcStr[iTemp], (*piPos - iTemp)); - chTempBuf[*piPos - iTemp] = 0; - - iResult += atol(chTempBuf); - + iResult += iTemp; } else iResult = -2; } else @@ -80,86 +202,105 @@ /* - * Initialize, read database to memory + * Read database to memory */ -gint xs_db_initialize(gchar *pcFilename) +gint xs_db_read(gchar *dbFilename, t_xs_dbentry **dataBase) { FILE *inFile; - char inLine[256]; - int lineNum, linePos, i, j, k; - t_xs_hash tmpHash; - - + gchar inLine[XMMS_SID_BUFSIZE]; + gint lineNum, linePos, iOK; + t_xs_dbentry *tmpNode; + /* Try to open the file */ - if ((inFile = fopen(pcFilename, "r")) == NULL) + if ((inFile = fopen(dbFilename, "ra")) == NULL) { - fprintf(stderr, "hv!\n"); + XSERR("Could not open SongLengthDB '%s'\n", dbFilename); return -1; } - /* Read and parse the data */ lineNum = 0; while (!feof(inFile)) { fgets(inLine, sizeof(inLine), inFile); + lineNum++; + + /* Check if it is datafield */ + if (isxdigit(inLine[0])) + { + /* Check the length of the hash */ linePos = 0; - - if (strlen(inLine) > 1) - { - /* Find first character */ - xs_findnext(inLine, &linePos); + while (isxdigit(inLine[linePos])) linePos++; - /* Check if it is a hash-line */ - if (isxdigit(inLine[linePos])) - { - i = linePos; - while (isxdigit(inLine[linePos])) linePos++; - - if ((linePos - i) != XS_HASH_LENGTH_CH) + if (linePos != XS_MD5HASH_LENGTH_CH) + { + XSERR("Invalid hash in SongLengthDB file '%s' line #%d!\n", + dbFilename, lineNum); + } else { + /* Allocate new node */ + if ((tmpNode = xs_db_node_new()) == NULL) { - XSERR("Invalid hash/syntax error in SongLengthDB file '%s' line #%d!\n", - pcFilename, lineNum); + XSERR("Error allocating new node. Fatal error.\n"); + exit(5); + } + + /* Get hash value */ +#if (XS_MD5HASH_LENGTH != 16) +#error Mismatch in hashcode length. Fix here. +#endif + sscanf(&inLine[0], "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x", + (guint *) &(tmpNode->md5Hash[0]), (guint *) &(tmpNode->md5Hash[1]), + (guint *) &(tmpNode->md5Hash[2]), (guint *) &(tmpNode->md5Hash[3]), + (guint *) &(tmpNode->md5Hash[4]), (guint *) &(tmpNode->md5Hash[5]), + (guint *) &(tmpNode->md5Hash[6]), (guint *) &(tmpNode->md5Hash[7]), + (guint *) &(tmpNode->md5Hash[8]), (guint *) &(tmpNode->md5Hash[9]), + (guint *) &(tmpNode->md5Hash[10]), (guint *) &(tmpNode->md5Hash[11]), + (guint *) &(tmpNode->md5Hash[12]), (guint *) &(tmpNode->md5Hash[13]), + (guint *) &(tmpNode->md5Hash[14]), (guint *) &(tmpNode->md5Hash[15])); + + /* Get playtimes */ + if (inLine[linePos] != '=') + { + XSERR("'=' expected in SongLengthDB file '%s' line #%d, column #%d\n", + dbFilename, lineNum, linePos); + + xs_db_node_free(tmpNode); } else { - /* Get hashcode value */ - for (j = 0; j < XS_HASH_LENGTH; j++) + /* First playtime is after '=' */ + linePos++; + iOK = 1; + + while ((linePos < strlen(inLine)) && (iOK)) { - + xs_findnext(inLine, &linePos); + + if (tmpNode->nLengths < XS_STIL_MAXENTRY) + { + tmpNode->sLengths[tmpNode->nLengths] = + xs_gettime(inLine, &linePos); + tmpNode->nLengths++; + } else + iOK = 0; } - /* Get playtimes */ - xs_findnext(line, &linePos); - - if (line[linePos] != '=') - { - XSERR("Warning: '=' expected in line #%d in SongLengthDB file '%s'\n", - lineNum, pcFilename); - } else { - linePos++; + /* Add an entry to db in memory */ + if (iOK) + xs_db_node_insert(tmpNode); + else + xs_db_node_free(tmpNode); + } + } + } else + if ((inLine[0] != ';') && (inLine[0] != '[')) + { + XSERR("Invalid line in SongLengthDB file '%s' line #%d\n", + dbFilename, lineNum); + } - while (linePos < strlen(inLine)) - { - xs_findnext(line, &linePos); - printf("[%lis]", xs_gettime(inLine, &linePos)); - } - + } /* while */ - /* Add an entry to db in memory */ - } - } - } - else - if ((line[linePos] != ';') && (line[linePos] != '[')) - { - XSERR("Invalid line #%d in SongLengthDB file '%s'\n", lineNum, pcFilename); - } - - } /* strlen(line) > 1 */ - - lineNum++; - } - + /* Close the file */ fclose(inFile); return 0; @@ -167,72 +308,281 @@ /* - * + * Compare two nodes' hashes + */ +gint xs_db_cmp(const void *pNode1, const void *pNode2) +{ + /* We assume here that we never ever get NULL-pointers or similar */ + return xs_db_cmphash((*(t_xs_dbentry **) pNode1)->md5Hash, + (*(t_xs_dbentry **) pNode2)->md5Hash); +} + + +/* + * Initialize the song-length system */ -gint xs_db_delete(void) +gint xs_songlen_init(void) { + t_xs_dbentry *pCurr; + gint i; + + /* Read the database */ + if (xs_cfg.playDBPath == NULL) + return -10; + +fprintf(stderr, "reading '%s'\n", xs_cfg.playDBPath); + + if (xs_db_read(xs_cfg.playDBPath, &xs_database) < 0) + return -9; + +fprintf(stderr, "read_done, now size DB for index\n"); + + /* Get size of db */ + pCurr = xs_database; + xs_dbnodes = 0; + while (pCurr) + { + xs_dbnodes++; + pCurr = pCurr->pNext; + } + + /* Check number of nodes */ + if (xs_dbnodes > 0) + { +fprintf(stderr, "allocating %i nodes...\n", xs_dbnodes); + /* Allocate memory for index-table */ + xs_dbindex = (t_xs_dbentry **) malloc(sizeof(t_xs_dbentry *) * xs_dbnodes); + if (xs_dbindex == NULL) return -6; + + /* Get node-pointers to table */ + i = 0; + pCurr = xs_database; + while (pCurr) + { + xs_dbindex[i++] = pCurr; + pCurr = pCurr->pNext; + } + +fprintf(stderr, "sorting!\n"); + /* Sort the indexes */ + qsort(xs_dbindex, xs_dbnodes, sizeof(t_xs_dbentry *), xs_db_cmp); + } + + /* OK */ + return 0; +} + + +/* + * Close song-length system + */ +gint xs_songlen_close(void) +{ + t_xs_dbentry *pCurr, *pNext; + /* Free the memory allocated for database */ + pCurr = xs_database; + while (pCurr) + { + pNext = pCurr->pNext; + xs_db_node_free(pCurr); + pCurr = pNext; + } + + /* Free memory allocated for indexes */ + if (xs_dbindex) + free(xs_dbindex); return 0; } /* - * + * Compute md5hash of given SID-file */ -int xs_comparehashes(t_xs_hash *testHash1, t_xs_hash *testHash2) -{ - int iIndex, iOK; +typedef struct { + guint8 magicID[4]; /* "PSID" magic identifier */ + guint16 version, /* Version number */ + dataOffset, /* Start of actual c64 data in file */ + loadAddress, /* Loading address */ + initAddress, /* Initialization address */ + playAddress, /* Play one frame */ + nSongs, /* Number of subsongs */ + startSong; /* Default starting song */ + guint32 speed; /* Speed */ + gchar sidName[32]; /* Descriptive text-fields, ASCIIZ */ + gchar sidAuthor[32]; + gchar sidCopyright[32]; +} t_xs_psidv1_header; + - iOK = 1; - iIndex = 0; - while ((iIndex < 16) && (iOK)) - { - if (testHash1[iIndex] != testHash2[iIndex]) - iOK = 0; - else - iIndex++; - } +typedef struct { + guint16 flags; /* Flags */ + guint8 startPage, + pageLength; + guint16 reserved; +} t_xs_psidv2_header; + + +guint16 rd_be16(FILE *f) +{ + return (((guint16) fgetc(f)) * 256) + + ((guint16) fgetc(f)); +} - return iOK; + +guint32 rd_be32(FILE *f) +{ + return (((guint32) fgetc(f)) * 16777216) + + (((guint32) fgetc(f)) * 65536) + + (((guint32) fgetc(f)) * 256) + + ((guint32) fgetc(f)); +} + + +gint rd_str(FILE *f, gchar *s, gint l) +{ + return fread(s, sizeof(gchar), l, f); } +gint xs_get_sid_hash(gchar *fileName, t_xs_md5hash hash) +{ + FILE *inFile; + t_xs_md5state inState; + t_xs_psidv1_header psidH; + t_xs_psidv2_header psidH2; + guint8 *songData, ib8[2], i8; + gint iIndex, iRes, songDataLen; + + /* Try to open the file */ + if ((inFile = fopen(fileName, "rb")) == NULL) + return -1; + + /* Read PSID header in */ + rd_str(inFile, psidH.magicID, sizeof(psidH.magicID)); + if ((psidH.magicID[0] != 'P') || (psidH.magicID[1] != 'S') || + (psidH.magicID[2] != 'I') || (psidH.magicID[3] != 'D')) + return -2; + + psidH.version = rd_be16(inFile); + psidH.dataOffset = rd_be16(inFile); + psidH.loadAddress = rd_be16(inFile); + psidH.initAddress = rd_be16(inFile); + psidH.playAddress = rd_be16(inFile); + psidH.nSongs = rd_be16(inFile); + psidH.startSong = rd_be16(inFile); + psidH.speed = rd_be32(inFile); + + rd_str(inFile, psidH.sidName, sizeof(psidH.sidName)); + rd_str(inFile, psidH.sidAuthor, sizeof(psidH.sidAuthor)); + rd_str(inFile, psidH.sidCopyright, sizeof(psidH.sidCopyright)); + + /* Check if we need to load PSIDv2NG header ... */ + if (psidH.version == 2) + { + /* Yes, we need to */ + psidH2.flags = rd_be16(inFile); + psidH2.startPage = fgetc(inFile); + psidH2.pageLength = fgetc(inFile); + psidH2.reserved = rd_be16(inFile); + } + + /* Get data length and seek to data offset */ + fseek(inFile, 0L, SEEK_END); + songDataLen = ftell(inFile) - psidH.dataOffset; + fseek(inFile, psidH.dataOffset, SEEK_SET); + + /* Allocate memory */ + songData = (guint8 *) malloc(sizeof(guint8) * songDataLen); + if (songData == NULL) + { + fclose(inFile); + return -7; + } + + /* Read data to buffer */ + iRes = fread(songData, sizeof(guint8), songDataLen, inFile); + fclose(inFile); + + if (iRes != songDataLen) return -9; + + /* Initialize and start MD5-hash calculation */ + xs_md5_init(&inState); + if (psidH.loadAddress == 0) + /* COULD SOMEONE EXPLAIN WHY DO WE NEED THIS +2 STRIP???? */ + xs_md5_append(&inState, songData+2, iRes-2); + else + xs_md5_append(&inState, songData, iRes); + + free(songData); + + /* Append header data to hash */ +#define XSADDHASH(QDATAB) { ib8[0] = (QDATAB & 0xff); ib8[1] = (QDATAB >> 8); xs_md5_append(&inState, (guint8 *) &ib8, sizeof(ib8)); } + + XSADDHASH(psidH.initAddress) + XSADDHASH(psidH.playAddress) + XSADDHASH(psidH.nSongs) + +#undef XSADDHASH + + /* Append song speed data to hash */ + i8 = 0; + for (iIndex = 0; (iIndex < psidH.nSongs) && (iIndex < 32); iIndex++) + { + i8 = (psidH.speed & (1 << iIndex)) ? 60 : 0; + xs_md5_append(&inState, &i8, sizeof(i8)); + } + + /* Rest of songs (more than 32) */ + for (iIndex = 32; iIndex < psidH.nSongs; iIndex++) + { + xs_md5_append(&inState, &i8, sizeof(i8)); + } + + + /* PSIDv2NG specific */ + if (psidH.version == 2) + { + /* SEE SIDPLAY HEADERS FOR INFO */ + i8 = (psidH2.flags >> 2) & 3; + if (i8 == 2) + xs_md5_append(&inState, &i8, sizeof(i8)); + } + + /* Calculate the hash */ + xs_md5_finish(&inState, hash); + + return 0; +} /* - * Get song length from database + * Get song length */ -t_xs_dbentry * xs_db_get(gchar *pcFilename) +gint32 xs_get_songlength(gchar *fileName, gint subTune) { - - return NULL; -} - - -gint32 xs_get_length(gchar *pcFilename, gint iSubTune) -{ + t_xs_dbentry *dbEntry; + t_xs_md5hash dbHash; gint32 iResult; - t_xs_dbentry *dbEntry; - t_xs_hash dbHash; iResult = -1; - + switch (xs_cfg.playMethod) { case XMMS_SID_PMETHOD_DATABASE: - - iResult = xs_db_get(pcFilename, &dbHash); - if (iResult >= 0) + /* Get the hash and then look up from db */ + if (xs_get_sid_hash(fileName, dbHash) == 0) { - } - - if (dbEntry) - { - if ((iSubTune >= 0) && (iSubTune < dbEntry->nlengths)) - iResult = dbEntry->lengths[iSubTune]; - else - iResult = -1; + fprinth(stderr, dbHash); + fprintf(stderr, "\n"); + dbEntry = xs_db_get(dbHash); + + if (dbEntry) + { + if ((subTune >= 0) && (subTune < dbEntry->nLengths)) + iResult = dbEntry->sLengths[subTune]; + } } break; @@ -255,8 +605,9 @@ iResult = -1; } - XSDEBUG("fname='%s', sub=%i, res=%li\n", pcFilename, iSubTune, iResult); + XSDEBUG("fname='%s', sub=%i, res=%i\n", fileName, subTune, iResult); return iResult; } + diff -r 7e664541ea36 -r f5d82424b0ed src/xs_length.h --- a/src/xs_length.h Wed Jun 04 14:04:02 2003 +0000 +++ b/src/xs_length.h Wed Jun 04 18:00:03 2003 +0000 @@ -5,27 +5,30 @@ extern "C" { #endif +#include +#include "xs_md5.h" /* * Defines and typedefs */ -#define XS_HASH_LENGTH (16) -#define XS_HASH_LENGTH_CH (XS_HASH_LENGTH * 2) - -typedef guint8 t_xs_hash[XS_HASH_LENGTH]; +#define XS_STIL_MAXENTRY (32) +typedef struct dbnode { + t_xs_md5hash md5Hash; /* 128-bit MD5 hash-digest */ + gint nLengths; /* Number of lengths */ + gint32 sLengths[XS_STIL_MAXENTRY]; + /* Lengths in seconds */ -typedef struct { - t_xs_hash hashcode; /* 128-bit MD5 hash-digest */ - gint nlengths; /* Number of lengths */ - long int lengths[XMMS_SID_STIL_MAXENTRY]; - /* Lengths in seconds */ + struct dbnode *pNext; } t_xs_dbentry; /* * Functions */ -gint32 xs_get_length(gchar *, gint); +gint xs_songlen_init(void); /* Initialize songlength subsystem */ +gint xs_songlen_close(void); /* Close/shutdown */ + +gint32 xs_songlen_get(gchar *, gint); /* Get length in seconds */ #ifdef __cplusplus diff -r 7e664541ea36 -r f5d82424b0ed src/xs_md5.h --- a/src/xs_md5.h Wed Jun 04 14:04:02 2003 +0000 +++ b/src/xs_md5.h Wed Jun 04 18:00:03 2003 +0000 @@ -5,7 +5,7 @@ extern "C" { #endif -#include "xs_length.h" +#include /* * Typedefs @@ -16,6 +16,11 @@ guint8 buf[64]; /* accumulate block */ } t_xs_md5state; +#define XS_MD5HASH_LENGTH (16) +#define XS_MD5HASH_LENGTH_CH (XS_MD5HASH_LENGTH * 2) + +typedef guint8 t_xs_md5hash[XS_MD5HASH_LENGTH]; + /* * Functions