view src/xs_length.c @ 230:608f31f6c095

Raw cleanup.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 21 Dec 2004 09:25:03 +0000
parents 92bad4c7b998
children 291715a519e2
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 "xs_length.h"
#include "xs_support.h"
#include "xs_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>


/* Database handling functions
 */
static t_xs_sldb_node *xs_sldb_node_new(void)
{
 t_xs_sldb_node *pResult;
 
 /* Allocate memory for new node */
 pResult = (t_xs_sldb_node *) g_malloc0(sizeof(t_xs_sldb_node));
 if (!pResult) return NULL;
 
 return pResult;
}


static void xs_sldb_node_free(t_xs_sldb_node *pNode)
{
 if (pNode)
 	{
 	/* Nothing much to do here ... */
 	g_free(pNode);
 	}
}


/* Insert given node to db linked list
 */
static void xs_sldb_node_insert(t_xs_sldb *db, t_xs_sldb_node *pNode)
{
 assert(db);
 
 if (db->pNodes)
 	{
 	/* The first node's pPrev points to last node */
	LPREV = db->pNodes->pPrev;		/* New node's prev = Previous last node */
	db->pNodes->pPrev->pNext = pNode;	/* Previous last node's next = New node */
	db->pNodes->pPrev = pNode;		/* New last node = New node */
	LNEXT = NULL;				/* But next is NULL! */
 	} else {
 	db->pNodes = pNode;			/* First node ... */
 	LPREV = pNode;				/* ... it's also last */
 	LNEXT = NULL;				/* But next is NULL! */
 	}
}


/* Parses a time-entry in SLDB format
 */
static gint32 xs_sldb_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 (pcStr[*piPos] && !isspace(pcStr[*piPos])) (*piPos)++;
 
 return iResult;
}


/* Read database to memory
 */
gint xs_sldb_read(t_xs_sldb *db, gchar *dbFilename)
{
 FILE *inFile;
 gchar inLine[XS_BUFSIZE];
 guint lineNum, linePos;
 gboolean iOK;
 t_xs_sldb_node *tmpNode;
 assert(db);
 
 /* 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_sldb_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] != 0)
	{
	if (inLine[linePos] != '=')
		{
		XSERR("'=' expected in SongLengthDB file '%s' line #%d, column #%d\n",
		dbFilename, lineNum, linePos);

		xs_sldb_node_free(tmpNode);
		} else {
		/* First playtime is after '=' */
		linePos++;
		iOK = TRUE;

		while ((linePos < strlen(inLine)) && iOK)
			{
			xs_findnext(inLine, &linePos);

			if (tmpNode->nLengths < XS_STIL_MAXENTRY)
				{
				tmpNode->sLengths[tmpNode->nLengths] =
				xs_sldb_gettime(inLine, &linePos);
				tmpNode->nLengths++;
				} else
				iOK = FALSE;
			}

		/* Add an node to db in memory */
		if (iOK)
			xs_sldb_node_insert(db, tmpNode);
			else
			xs_sldb_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 given MD5-hashes.
 * Return: 0 if equal
 *         negative if testHash1 < testHash2
 *         positive if testHash1 > testHash2
 */
static gint xs_sldb_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 node from db index via binary search
 */
static t_xs_sldb_node * xs_sldb_get_node(t_xs_sldb *db, t_xs_md5hash pHash)
{
 gint iStartNode, iEndNode, iQNode, r, i;
 gboolean iFound;
 t_xs_sldb_node *pResult;
 
 /* Check the database pointers */
 if (!db || !db->pNodes || !db->ppIndex)
 	return NULL;
 	
 /* Look-up via index using binary search */
 pResult = NULL;
 iStartNode = 0;
 iEndNode = (db->n - 1);
 iQNode = (iEndNode / 2);
 iFound = FALSE;

 while ((!iFound) && ((iEndNode - iStartNode) > 128))
 	{
 	r = xs_sldb_cmphash(pHash, db->ppIndex[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 = TRUE;
 	}

 /* If not found already */
 if (!iFound)
 	{
	/* Search the are linearly */
	iFound = FALSE;
	i = iStartNode;
	while ((i <= iEndNode) && (!iFound))
		{
		if (xs_sldb_cmphash(pHash, db->ppIndex[i]->md5Hash) == 0)
			iFound = TRUE;
			else
			i++;
		}
	
	/* Check the result */
	if (iFound)
		pResult = db->ppIndex[i];

 	} else {
	/* Found via binary search */
 	pResult = db->ppIndex[iQNode];
	}

 return pResult;
}


/* Compare two nodes
 */
static gint xs_sldb_cmp(const void *pNode1, const void *pNode2)
{
 /* We assume here that we never ever get NULL-pointers or similar */
 return xs_sldb_cmphash((*(t_xs_sldb_node **) pNode1)->md5Hash,
                      (*(t_xs_sldb_node **) pNode2)->md5Hash);
}


/* (Re)create index
 */
gint xs_sldb_index(t_xs_sldb *db)
{
 t_xs_sldb_node *pCurr;
 gint i;
 assert(db);
 
 /* Free old index */
 if (db->ppIndex)
 	{
 	g_free(db->ppIndex);
 	db->ppIndex = NULL;
 	}
 
 /* Get size of db */
 pCurr = db->pNodes;
 db->n = 0;
 while (pCurr)
 	{
 	db->n++;
 	pCurr = pCurr->pNext;
 	}

 /* Check number of nodes */
 if (db->n > 0)
	{
	/* Allocate memory for index-table */
	db->ppIndex = (t_xs_sldb_node **) g_malloc(sizeof(t_xs_sldb_node *) * db->n);
	if (!db->ppIndex) return -1;

	/* Get node-pointers to table */
	i = 0;
	pCurr = db->pNodes;
	while (pCurr && (i < db->n))
		{
		db->ppIndex[i++] = pCurr;
		pCurr = pCurr->pNext;
		}

	/* Sort the indexes */
	qsort(db->ppIndex, db->n, sizeof(t_xs_sldb_node *), xs_sldb_cmp);
	}

 return 0;
}


/* Free a given song-length database
 */
void xs_sldb_free(t_xs_sldb *db)
{
 t_xs_sldb_node *pCurr, *pNext;
 
 if (!db) return;
 
 /* Free the memory allocated for nodes */
 pCurr = db->pNodes;
 while (pCurr)
 	{
 	pNext = pCurr->pNext;
	xs_sldb_node_free(pCurr);
 	pCurr = pNext;
 	}

 db->pNodes = NULL;
 
 /* Free memory allocated for index */
 if (db->ppIndex)
 	{
 	g_free(db->ppIndex);
 	db->ppIndex = NULL;
 	}

 /* Free structure */
 db->n = 0;
 g_free(db);
}


/* Compute md5hash of given SID-file
 */
typedef struct {
	gchar	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;


static gint xs_get_sid_hash(gchar *pcFilename, t_xs_md5hash hash)
{
 FILE *inFile;
 t_xs_md5state inState;
 t_xs_psidv1_header psidH;
 t_xs_psidv2_header psidH2;
#ifdef XS_SIDBUF_DYNAMIC
 guint8 *songData;
#else
 guint8 songData[XS_SIDBUF_SIZE];
#endif
 guint8 ib8[2], i8;
 gint iIndex, iRes;

 /* Try to open the file */
 if ((inFile = fopen(pcFilename, "rb")) == NULL)
 	return -1;

 /* Read PSID header in */
 xs_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'))
     {
     fclose(inFile);
     return -2;
     }
 
 psidH.version		= xs_rd_be16(inFile);
 psidH.dataOffset	= xs_rd_be16(inFile);
 psidH.loadAddress	= xs_rd_be16(inFile);
 psidH.initAddress	= xs_rd_be16(inFile);
 psidH.playAddress	= xs_rd_be16(inFile);
 psidH.nSongs		= xs_rd_be16(inFile);
 psidH.startSong	= xs_rd_be16(inFile);
 psidH.speed		= xs_rd_be32(inFile);

 xs_rd_str(inFile, psidH.sidName, sizeof(psidH.sidName));
 xs_rd_str(inFile, psidH.sidAuthor, sizeof(psidH.sidAuthor));
 xs_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		= xs_rd_be16(inFile);
 	psidH2.startPage	= fgetc(inFile);
 	psidH2.pageLength	= fgetc(inFile);
 	psidH2.reserved		= xs_rd_be16(inFile);
 	}

#ifdef XS_SIDBUF_DYNAMIC
 /* Allocate buffer */
 songData = (guint8 *) g_malloc(XS_SIDBUF_SIZE * sizeof(guint8));
 if (!songData)
 	{
 	fclose(inFile);
 	return -3;
 	}
#endif
 
 /* Read data to buffer */
 iRes = fread(songData, sizeof(guint8), XS_SIDBUF_SIZE, inFile);
 fclose(inFile);

 /* Initialize and start MD5-hash calculation */
 xs_md5_init(&inState);
 if (psidH.loadAddress == 0)
 	{
 	/* Strip load address (2 first bytes) */
	xs_md5_append(&inState, &songData[2], iRes-2);
	} else {
	/* Append "as is" */
	xs_md5_append(&inState, songData, iRes);
	}


#ifdef XS_SIDBUF_DYNAMIC
 /* Free buffer */
 g_free(songData);
#endif

 /* 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 lengths
 */
t_xs_sldb_node * xs_sldb_get(t_xs_sldb *db, gchar *pcFilename)
{
 t_xs_sldb_node *pResult;
 t_xs_md5hash dbHash;

 /* Get the hash and then look up from db */
 if (xs_get_sid_hash(pcFilename, dbHash) == 0)
	pResult = xs_sldb_get_node(db, dbHash);
	else
	pResult = NULL;
 
 return pResult;
}


/*
 * These should be moved out of this module some day ...
 */
static t_xs_sldb	*xs_sldb_db = NULL;
XS_MUTEX(xs_sldb_db);

gint xs_songlen_init(void)
{
 XS_MUTEX_LOCK(xs_cfg);
 
 if (!xs_cfg.songlenDBPath)
 	{
 	XS_MUTEX_UNLOCK(xs_cfg);
 	return -1;
 	}

 XS_MUTEX_LOCK(xs_sldb_db);
 
 /* Check if already initialized */
 if (xs_sldb_db) xs_sldb_free(xs_sldb_db);
 
 /* Allocate database */
 xs_sldb_db = (t_xs_sldb *) g_malloc0(sizeof(t_xs_sldb));
 if (!xs_sldb_db)
 	{
 	XS_MUTEX_UNLOCK(xs_cfg);
 	XS_MUTEX_UNLOCK(xs_sldb_db);
 	return -2;
 	}
 
 /* Read the database */
 if (xs_sldb_read(xs_sldb_db, xs_cfg.songlenDBPath) != 0)
 	{
 	xs_sldb_free(xs_sldb_db);
 	xs_sldb_db = NULL;
 	XS_MUTEX_UNLOCK(xs_cfg);
 	XS_MUTEX_UNLOCK(xs_sldb_db);
 	return -3;
 	}

 /* Create index */
 if (xs_sldb_index(xs_sldb_db) != 0)
 	{
 	xs_sldb_free(xs_sldb_db);
 	xs_sldb_db = NULL;
 	XS_MUTEX_UNLOCK(xs_cfg);
 	XS_MUTEX_UNLOCK(xs_sldb_db);
 	return -4;
 	}

 XS_MUTEX_UNLOCK(xs_cfg);
 XS_MUTEX_UNLOCK(xs_sldb_db);
 return 0;
}


void xs_songlen_close(void)
{
 XS_MUTEX_LOCK(xs_sldb_db);
 xs_sldb_free(xs_sldb_db);
 xs_sldb_db = NULL;
 XS_MUTEX_UNLOCK(xs_sldb_db);
}


t_xs_sldb_node * xs_songlen_get(gchar *pcFilename)
{
 t_xs_sldb_node *pResult;

 XS_MUTEX_LOCK(xs_sldb_db);

 if (xs_cfg.songlenDBEnable && xs_sldb_db)
 	pResult = xs_sldb_get(xs_sldb_db, pcFilename);
 	else
 	pResult = NULL;

 XS_MUTEX_UNLOCK(xs_sldb_db);
 
 return pResult;
}