Mercurial > hg > xmms-sid
view src/xs_length.c @ 35:3fcc147b253a
Updates towards 0.8beta0
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 19 Jun 2003 20:13:00 +0000 |
parents | 271be59be975 |
children | 85a7753e2a9a |
line wrap: on
line source
/* XMMS-SID - SIDPlay input plugin for X MultiMedia System (XMMS) Get song length (from database or by other means) Written by Matti "ccr" Hamalainen <ccr@tnsp.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "xmms-sid.h" #include "xs_support.h" #include "xs_length.h" #include "xs_config.h" #include "xs_md5.h" #include <stdlib.h> #include <stdio.h> #include <ctype.h> #include <string.h> /* * Pointer to database in memory */ 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)) { r = xs_db_cmphash(pHash, xs_dbindex[iQNode]->md5Hash); 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 */ gint32 xs_gettime(gchar *pcStr, int *piPos) { gint32 iResult, iTemp; /* Check if it starts with a digit */ if (isdigit(pcStr[*piPos])) { /* Get minutes-field */ 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 = 0; while (isdigit(pcStr[*piPos])) iTemp = (iTemp * 10) + (pcStr[(*piPos)++] - '0'); iResult += iTemp; } else iResult = -2; } else iResult = -1; /* Ignore and skip the possible attributes */ while (!isspace(pcStr[*piPos])) (*piPos)++; return iResult; } /* * Read database to memory */ gint xs_db_read(gchar *dbFilename, t_xs_dbentry **dataBase) { FILE *inFile; gchar inLine[XS_BUFSIZE]; gint lineNum, linePos, iOK; t_xs_dbentry *tmpNode; /* Try to open the file */ if ((inFile = fopen(dbFilename, "ra")) == NULL) { 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; while (isxdigit(inLine[linePos])) linePos++; 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("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 { /* 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; } /* 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 */ /* Close the file */ fclose(inFile); return 0; } /* * 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_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 */ void 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); } /* * Compute md5hash of given SID-file */ 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; 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)); } 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 */ gint32 xs_songlen_get(gchar *fileName, gint subTune) { t_xs_dbentry *dbEntry; t_xs_md5hash dbHash; gint32 iResult; iResult = -1; switch (xs_cfg.playMethod) { case XMMS_SID_PMETHOD_DATABASE: /* Get the hash and then look up from db */ if (xs_get_sid_hash(fileName, dbHash) == 0) { dbEntry = xs_db_get(dbHash); if (dbEntry) { if ((subTune >= 0) && (subTune < dbEntry->nLengths)) iResult = dbEntry->sLengths[subTune]; } } break; case XMMS_SID_PMETHOD_MAXSILENCE: case XMMS_SID_PMETHOD_NONE: iResult = -1; break; default: XSERR("Invalid play-method! (%d, unknown)\n", xs_cfg.playMethod); break; } /* Check for max playtime */ if (iResult == -1) { if (xs_cfg.playUseMaxTime) iResult = xs_cfg.playMaxTime; else iResult = -1; } XSDEBUG("fname='%s', sub=%i, res=%i\n", fileName, subTune, iResult); return iResult; }