comparison src/xs_length.c @ 14:f5d82424b0ed

Made song-length database support FINALLY work!
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 04 Jun 2003 18:00:03 +0000
parents 4bb09e405eab
children ac2972a7ccd5
comparison
equal deleted inserted replaced
13:7e664541ea36 14:f5d82424b0ed
19 along with this program; if not, write to the Free Software 19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */ 21 */
22 22
23 #include "xmms-sid.h" 23 #include "xmms-sid.h"
24 #include "xs_support.h"
24 #include "xs_length.h" 25 #include "xs_length.h"
26 #include "xs_config.h"
27 #include "xs_md5.h"
25 #include <stdlib.h> 28 #include <stdlib.h>
26 #include <stdio.h> 29 #include <stdio.h>
27 #include <ctype.h> 30 #include <ctype.h>
28 #include <string.h> 31 #include <string.h>
29 32
33
30 /* 34 /*
31 * Pointer to database in memory 35 * Pointer to database in memory
32 */ 36 */
33 static t_xs_dbentry *xs_database = NULL; 37 static t_xs_dbentry *xs_database = NULL, *xs_dblast = NULL;
38 static t_xs_dbentry **xs_dbindex = NULL;
39 static gint xs_dbnodes = 0;
40
41
42 /*
43 * Hash-database handling functions
44 */
45 t_xs_dbentry *xs_db_node_new(void)
46 {
47 t_xs_dbentry *pResult;
48
49 /* Allocate memory for new node */
50 pResult = (t_xs_dbentry *) calloc(1, sizeof(t_xs_dbentry));
51 if (pResult == NULL) return NULL;
52
53 return pResult;
54 }
55
56
57 void xs_db_node_free(t_xs_dbentry *pNode)
58 {
59 if (pNode) free(pNode);
60 }
61
62
63 /*
64 * Insert given node to db linked list
65 */
66 void xs_db_node_insert(t_xs_dbentry *pNode)
67 {
68 if (xs_dblast)
69 {
70 xs_dblast->pNext = pNode;
71 xs_dblast = pNode;
72 } else {
73 xs_dblast = xs_database = pNode;
74 pNode->pNext = NULL;
75 }
76 }
77
78
79 /*
80 * Compare two given MD5-hashes.
81 * Return: 0 if equal
82 * negative if testHash1 < testHash2
83 * positive if testHash1 > testHash2
84 */
85 gint xs_db_cmphash(t_xs_md5hash testHash1, t_xs_md5hash testHash2)
86 {
87 register gint i, res = 0;
88
89 /* Compute difference of hashes */
90 for (i = 0; (i < XS_MD5HASH_LENGTH) && (!res); i++)
91 res = (testHash1[i] - testHash2[i]);
92
93 return res;
94 }
95
96
97 /*
98 * Get song length from database
99 */
100 t_xs_dbentry * xs_db_get(t_xs_md5hash pHash)
101 {
102 gint iStartNode, iEndNode, iQNode, iFound, r, i;
103 t_xs_dbentry *pResult;
104
105 /* Check the database pointers */
106 if ((xs_database == NULL) || (xs_dbindex == NULL))
107 return NULL;
108
109 /* Look-up via index using binary search */
110 pResult = NULL;
111 iStartNode = 0;
112 iEndNode = (xs_dbnodes - 1);
113 iQNode = (iEndNode / 2);
114 iFound = 0;
115
116 while ((!iFound) && ((iEndNode - iStartNode) > 128))
117 {
118 fprintf(stderr, "[");
119 fprinth(stderr, xs_dbindex[iQNode]->md5Hash);
120 r = xs_db_cmphash(pHash, xs_dbindex[iQNode]->md5Hash);
121 fprintf(stderr, "] = %i\n", r);
122 if (r < 0)
123 {
124 /* Hash was in the <- LEFT side */
125 iEndNode = iQNode;
126 iQNode = iStartNode + ((iEndNode - iStartNode) / 2);
127 } else
128 if (r > 0)
129 {
130 /* Hash was in the RIGHT -> side */
131 iStartNode = iQNode;
132 iQNode = iStartNode + ((iEndNode - iStartNode) / 2);
133 } else
134 iFound = 1;
135 }
136
137 /* If not found already */
138 if (!iFound)
139 {
140 /* Search the are linearly */
141 iFound = 0;
142 i = iStartNode;
143 while ((i <= iEndNode) && (!iFound))
144 {
145 if (xs_db_cmphash(pHash, xs_dbindex[i]->md5Hash) == 0)
146 iFound = 1;
147 else
148 i++;
149 }
150
151 /* Check the result */
152 if (iFound)
153 pResult = xs_dbindex[i];
154
155 } else {
156 /* Found via binary search */
157 pResult = xs_dbindex[iEndNode];
158 }
159
160
161 return pResult;
162 }
34 163
35 164
36 /* 165 /*
37 * Parses a time-entry in SLDB format 166 * Parses a time-entry in SLDB format
38 */ 167 */
39 long int xs_gettime(char *pcStr, int *piPos) 168 gint32 xs_gettime(gchar *pcStr, int *piPos)
40 { 169 {
41 long int iResult; 170 gint32 iResult, iTemp;
42 int iTemp;
43 char chTempBuf[16];
44 171
45 /* Check if it starts with a digit */ 172 /* Check if it starts with a digit */
46 if (isdigit(pcStr[*piPos])) 173 if (isdigit(pcStr[*piPos]))
47 { 174 {
48 /* Get minutes-field */ 175 /* Get minutes-field */
49 iTemp = *piPos; 176 iResult = 0;
50 xs_findnum(pcStr, piPos); 177 while (isdigit(pcStr[*piPos]))
51 178 iResult = (iResult * 10) + (pcStr[(*piPos)++] - '0');
52 strncpy(chTempBuf, &pcStr[iTemp], (*piPos - iTemp)); 179
53 chTempBuf[*piPos - iTemp] = 0; 180 iResult *= 60;
54
55 iResult = (atol(chTempBuf) * 60);
56 181
57 /* Check the field separator char */ 182 /* Check the field separator char */
58 if (pcStr[*piPos] == ':') 183 if (pcStr[*piPos] == ':')
59 { 184 {
60 /* Get seconds-field */ 185 /* Get seconds-field */
61 (*piPos)++; 186 (*piPos)++;
62 iTemp = *piPos; 187 iTemp = 0;
63 xs_findnum(pcStr, piPos); 188 while (isdigit(pcStr[*piPos]))
64 189 iTemp = (iTemp * 10) + (pcStr[(*piPos)++] - '0');
65 strncpy(chTempBuf, &pcStr[iTemp], (*piPos - iTemp)); 190
66 chTempBuf[*piPos - iTemp] = 0; 191 iResult += iTemp;
67
68 iResult += atol(chTempBuf);
69
70 } else 192 } else
71 iResult = -2; 193 iResult = -2;
72 } else 194 } else
73 iResult = -1; 195 iResult = -1;
74 196
78 return iResult; 200 return iResult;
79 } 201 }
80 202
81 203
82 /* 204 /*
83 * Initialize, read database to memory 205 * Read database to memory
84 */ 206 */
85 gint xs_db_initialize(gchar *pcFilename) 207 gint xs_db_read(gchar *dbFilename, t_xs_dbentry **dataBase)
86 { 208 {
87 FILE *inFile; 209 FILE *inFile;
88 char inLine[256]; 210 gchar inLine[XMMS_SID_BUFSIZE];
89 int lineNum, linePos, i, j, k; 211 gint lineNum, linePos, iOK;
90 t_xs_hash tmpHash; 212 t_xs_dbentry *tmpNode;
91 213
92
93 /* Try to open the file */ 214 /* Try to open the file */
94 if ((inFile = fopen(pcFilename, "r")) == NULL) 215 if ((inFile = fopen(dbFilename, "ra")) == NULL)
95 { 216 {
96 fprintf(stderr, "hv!\n"); 217 XSERR("Could not open SongLengthDB '%s'\n", dbFilename);
97 return -1; 218 return -1;
98 } 219 }
99 220
100
101 /* Read and parse the data */ 221 /* Read and parse the data */
102 lineNum = 0; 222 lineNum = 0;
103 223
104 while (!feof(inFile)) 224 while (!feof(inFile))
105 { 225 {
106 fgets(inLine, sizeof(inLine), inFile); 226 fgets(inLine, sizeof(inLine), inFile);
227 lineNum++;
228
229 /* Check if it is datafield */
230 if (isxdigit(inLine[0]))
231 {
232 /* Check the length of the hash */
107 linePos = 0; 233 linePos = 0;
108 234 while (isxdigit(inLine[linePos])) linePos++;
109 if (strlen(inLine) > 1) 235
110 { 236 if (linePos != XS_MD5HASH_LENGTH_CH)
111 /* Find first character */ 237 {
112 xs_findnext(inLine, &linePos); 238 XSERR("Invalid hash in SongLengthDB file '%s' line #%d!\n",
113 239 dbFilename, lineNum);
114 /* Check if it is a hash-line */ 240 } else {
115 if (isxdigit(inLine[linePos])) 241 /* Allocate new node */
116 { 242 if ((tmpNode = xs_db_node_new()) == NULL)
117 i = linePos;
118 while (isxdigit(inLine[linePos])) linePos++;
119
120 if ((linePos - i) != XS_HASH_LENGTH_CH)
121 { 243 {
122 XSERR("Invalid hash/syntax error in SongLengthDB file '%s' line #%d!\n", 244 XSERR("Error allocating new node. Fatal error.\n");
123 pcFilename, lineNum); 245 exit(5);
246 }
247
248 /* Get hash value */
249 #if (XS_MD5HASH_LENGTH != 16)
250 #error Mismatch in hashcode length. Fix here.
251 #endif
252 sscanf(&inLine[0], "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x",
253 (guint *) &(tmpNode->md5Hash[0]), (guint *) &(tmpNode->md5Hash[1]),
254 (guint *) &(tmpNode->md5Hash[2]), (guint *) &(tmpNode->md5Hash[3]),
255 (guint *) &(tmpNode->md5Hash[4]), (guint *) &(tmpNode->md5Hash[5]),
256 (guint *) &(tmpNode->md5Hash[6]), (guint *) &(tmpNode->md5Hash[7]),
257 (guint *) &(tmpNode->md5Hash[8]), (guint *) &(tmpNode->md5Hash[9]),
258 (guint *) &(tmpNode->md5Hash[10]), (guint *) &(tmpNode->md5Hash[11]),
259 (guint *) &(tmpNode->md5Hash[12]), (guint *) &(tmpNode->md5Hash[13]),
260 (guint *) &(tmpNode->md5Hash[14]), (guint *) &(tmpNode->md5Hash[15]));
261
262 /* Get playtimes */
263 if (inLine[linePos] != '=')
264 {
265 XSERR("'=' expected in SongLengthDB file '%s' line #%d, column #%d\n",
266 dbFilename, lineNum, linePos);
267
268 xs_db_node_free(tmpNode);
124 } else { 269 } else {
125 /* Get hashcode value */ 270 /* First playtime is after '=' */
126 for (j = 0; j < XS_HASH_LENGTH; j++) 271 linePos++;
272 iOK = 1;
273
274 while ((linePos < strlen(inLine)) && (iOK))
127 { 275 {
128 276 xs_findnext(inLine, &linePos);
277
278 if (tmpNode->nLengths < XS_STIL_MAXENTRY)
279 {
280 tmpNode->sLengths[tmpNode->nLengths] =
281 xs_gettime(inLine, &linePos);
282 tmpNode->nLengths++;
283 } else
284 iOK = 0;
129 } 285 }
130 286
131 /* Get playtimes */ 287 /* Add an entry to db in memory */
132 xs_findnext(line, &linePos); 288 if (iOK)
133 289 xs_db_node_insert(tmpNode);
134 if (line[linePos] != '=') 290 else
291 xs_db_node_free(tmpNode);
292 }
293 }
294 } else
295 if ((inLine[0] != ';') && (inLine[0] != '['))
296 {
297 XSERR("Invalid line in SongLengthDB file '%s' line #%d\n",
298 dbFilename, lineNum);
299 }
300
301 } /* while */
302
303 /* Close the file */
304 fclose(inFile);
305
306 return 0;
307 }
308
309
310 /*
311 * Compare two nodes' hashes
312 */
313 gint xs_db_cmp(const void *pNode1, const void *pNode2)
314 {
315 /* We assume here that we never ever get NULL-pointers or similar */
316 return xs_db_cmphash((*(t_xs_dbentry **) pNode1)->md5Hash,
317 (*(t_xs_dbentry **) pNode2)->md5Hash);
318 }
319
320
321 /*
322 * Initialize the song-length system
323 */
324 gint xs_songlen_init(void)
325 {
326 t_xs_dbentry *pCurr;
327 gint i;
328
329 /* Read the database */
330 if (xs_cfg.playDBPath == NULL)
331 return -10;
332
333 fprintf(stderr, "reading '%s'\n", xs_cfg.playDBPath);
334
335 if (xs_db_read(xs_cfg.playDBPath, &xs_database) < 0)
336 return -9;
337
338 fprintf(stderr, "read_done, now size DB for index\n");
339
340 /* Get size of db */
341 pCurr = xs_database;
342 xs_dbnodes = 0;
343 while (pCurr)
344 {
345 xs_dbnodes++;
346 pCurr = pCurr->pNext;
347 }
348
349 /* Check number of nodes */
350 if (xs_dbnodes > 0)
351 {
352 fprintf(stderr, "allocating %i nodes...\n", xs_dbnodes);
353 /* Allocate memory for index-table */
354 xs_dbindex = (t_xs_dbentry **) malloc(sizeof(t_xs_dbentry *) * xs_dbnodes);
355 if (xs_dbindex == NULL) return -6;
356
357 /* Get node-pointers to table */
358 i = 0;
359 pCurr = xs_database;
360 while (pCurr)
361 {
362 xs_dbindex[i++] = pCurr;
363 pCurr = pCurr->pNext;
364 }
365
366 fprintf(stderr, "sorting!\n");
367 /* Sort the indexes */
368 qsort(xs_dbindex, xs_dbnodes, sizeof(t_xs_dbentry *), xs_db_cmp);
369 }
370
371 /* OK */
372 return 0;
373 }
374
375
376 /*
377 * Close song-length system
378 */
379 gint xs_songlen_close(void)
380 {
381 t_xs_dbentry *pCurr, *pNext;
382
383 /* Free the memory allocated for database */
384 pCurr = xs_database;
385 while (pCurr)
386 {
387 pNext = pCurr->pNext;
388 xs_db_node_free(pCurr);
389 pCurr = pNext;
390 }
391
392 /* Free memory allocated for indexes */
393 if (xs_dbindex)
394 free(xs_dbindex);
395
396 return 0;
397 }
398
399
400 /*
401 * Compute md5hash of given SID-file
402 */
403 typedef struct {
404 guint8 magicID[4]; /* "PSID" magic identifier */
405 guint16 version, /* Version number */
406 dataOffset, /* Start of actual c64 data in file */
407 loadAddress, /* Loading address */
408 initAddress, /* Initialization address */
409 playAddress, /* Play one frame */
410 nSongs, /* Number of subsongs */
411 startSong; /* Default starting song */
412 guint32 speed; /* Speed */
413 gchar sidName[32]; /* Descriptive text-fields, ASCIIZ */
414 gchar sidAuthor[32];
415 gchar sidCopyright[32];
416 } t_xs_psidv1_header;
417
418
419 typedef struct {
420 guint16 flags; /* Flags */
421 guint8 startPage,
422 pageLength;
423 guint16 reserved;
424 } t_xs_psidv2_header;
425
426
427 guint16 rd_be16(FILE *f)
428 {
429 return (((guint16) fgetc(f)) * 256) +
430 ((guint16) fgetc(f));
431 }
432
433
434 guint32 rd_be32(FILE *f)
435 {
436 return (((guint32) fgetc(f)) * 16777216) +
437 (((guint32) fgetc(f)) * 65536) +
438 (((guint32) fgetc(f)) * 256) +
439 ((guint32) fgetc(f));
440 }
441
442
443 gint rd_str(FILE *f, gchar *s, gint l)
444 {
445 return fread(s, sizeof(gchar), l, f);
446 }
447
448
449 gint xs_get_sid_hash(gchar *fileName, t_xs_md5hash hash)
450 {
451 FILE *inFile;
452 t_xs_md5state inState;
453 t_xs_psidv1_header psidH;
454 t_xs_psidv2_header psidH2;
455 guint8 *songData, ib8[2], i8;
456 gint iIndex, iRes, songDataLen;
457
458 /* Try to open the file */
459 if ((inFile = fopen(fileName, "rb")) == NULL)
460 return -1;
461
462 /* Read PSID header in */
463 rd_str(inFile, psidH.magicID, sizeof(psidH.magicID));
464 if ((psidH.magicID[0] != 'P') || (psidH.magicID[1] != 'S') ||
465 (psidH.magicID[2] != 'I') || (psidH.magicID[3] != 'D'))
466 return -2;
467
468 psidH.version = rd_be16(inFile);
469 psidH.dataOffset = rd_be16(inFile);
470 psidH.loadAddress = rd_be16(inFile);
471 psidH.initAddress = rd_be16(inFile);
472 psidH.playAddress = rd_be16(inFile);
473 psidH.nSongs = rd_be16(inFile);
474 psidH.startSong = rd_be16(inFile);
475 psidH.speed = rd_be32(inFile);
476
477 rd_str(inFile, psidH.sidName, sizeof(psidH.sidName));
478 rd_str(inFile, psidH.sidAuthor, sizeof(psidH.sidAuthor));
479 rd_str(inFile, psidH.sidCopyright, sizeof(psidH.sidCopyright));
480
481 /* Check if we need to load PSIDv2NG header ... */
482 if (psidH.version == 2)
483 {
484 /* Yes, we need to */
485 psidH2.flags = rd_be16(inFile);
486 psidH2.startPage = fgetc(inFile);
487 psidH2.pageLength = fgetc(inFile);
488 psidH2.reserved = rd_be16(inFile);
489 }
490
491 /* Get data length and seek to data offset */
492 fseek(inFile, 0L, SEEK_END);
493 songDataLen = ftell(inFile) - psidH.dataOffset;
494 fseek(inFile, psidH.dataOffset, SEEK_SET);
495
496 /* Allocate memory */
497 songData = (guint8 *) malloc(sizeof(guint8) * songDataLen);
498 if (songData == NULL)
499 {
500 fclose(inFile);
501 return -7;
502 }
503
504 /* Read data to buffer */
505 iRes = fread(songData, sizeof(guint8), songDataLen, inFile);
506 fclose(inFile);
507
508 if (iRes != songDataLen) return -9;
509
510 /* Initialize and start MD5-hash calculation */
511 xs_md5_init(&inState);
512 if (psidH.loadAddress == 0)
513 /* COULD SOMEONE EXPLAIN WHY DO WE NEED THIS +2 STRIP???? */
514 xs_md5_append(&inState, songData+2, iRes-2);
515 else
516 xs_md5_append(&inState, songData, iRes);
517
518 free(songData);
519
520 /* Append header data to hash */
521 #define XSADDHASH(QDATAB) { ib8[0] = (QDATAB & 0xff); ib8[1] = (QDATAB >> 8); xs_md5_append(&inState, (guint8 *) &ib8, sizeof(ib8)); }
522
523 XSADDHASH(psidH.initAddress)
524 XSADDHASH(psidH.playAddress)
525 XSADDHASH(psidH.nSongs)
526
527 #undef XSADDHASH
528
529 /* Append song speed data to hash */
530 i8 = 0;
531 for (iIndex = 0; (iIndex < psidH.nSongs) && (iIndex < 32); iIndex++)
532 {
533 i8 = (psidH.speed & (1 << iIndex)) ? 60 : 0;
534 xs_md5_append(&inState, &i8, sizeof(i8));
535 }
536
537 /* Rest of songs (more than 32) */
538 for (iIndex = 32; iIndex < psidH.nSongs; iIndex++)
539 {
540 xs_md5_append(&inState, &i8, sizeof(i8));
541 }
542
543
544 /* PSIDv2NG specific */
545 if (psidH.version == 2)
546 {
547 /* SEE SIDPLAY HEADERS FOR INFO */
548 i8 = (psidH2.flags >> 2) & 3;
549 if (i8 == 2)
550 xs_md5_append(&inState, &i8, sizeof(i8));
551 }
552
553 /* Calculate the hash */
554 xs_md5_finish(&inState, hash);
555
556 return 0;
557 }
558
559
560 /*
561 * Get song length
562 */
563 gint32 xs_get_songlength(gchar *fileName, gint subTune)
564 {
565 t_xs_dbentry *dbEntry;
566 t_xs_md5hash dbHash;
567 gint32 iResult;
568
569 iResult = -1;
570
571 switch (xs_cfg.playMethod) {
572
573 case XMMS_SID_PMETHOD_DATABASE:
574 /* Get the hash and then look up from db */
575 if (xs_get_sid_hash(fileName, dbHash) == 0)
576 {
577 fprinth(stderr, dbHash);
578 fprintf(stderr, "\n");
579 dbEntry = xs_db_get(dbHash);
580
581 if (dbEntry)
135 { 582 {
136 XSERR("Warning: '=' expected in line #%d in SongLengthDB file '%s'\n", 583 if ((subTune >= 0) && (subTune < dbEntry->nLengths))
137 lineNum, pcFilename); 584 iResult = dbEntry->sLengths[subTune];
138 } else { 585 }
139 linePos++;
140
141 while (linePos < strlen(inLine))
142 {
143 xs_findnext(line, &linePos);
144 printf("[%lis]", xs_gettime(inLine, &linePos));
145 }
146
147
148 /* Add an entry to db in memory */
149 }
150 }
151 }
152 else
153 if ((line[linePos] != ';') && (line[linePos] != '['))
154 {
155 XSERR("Invalid line #%d in SongLengthDB file '%s'\n", lineNum, pcFilename);
156 }
157
158 } /* strlen(line) > 1 */
159
160 lineNum++;
161 }
162
163 fclose(inFile);
164
165 return 0;
166 }
167
168
169 /*
170 *
171 */
172 gint xs_db_delete(void)
173 {
174 /* Free the memory allocated for database */
175
176 return 0;
177 }
178
179
180 /*
181 *
182 */
183 int xs_comparehashes(t_xs_hash *testHash1, t_xs_hash *testHash2)
184 {
185 int iIndex, iOK;
186
187 iOK = 1;
188 iIndex = 0;
189 while ((iIndex < 16) && (iOK))
190 {
191 if (testHash1[iIndex] != testHash2[iIndex])
192 iOK = 0;
193 else
194 iIndex++;
195 }
196
197 return iOK;
198 }
199
200
201
202
203 /*
204 * Get song length from database
205 */
206 t_xs_dbentry * xs_db_get(gchar *pcFilename)
207 {
208
209 return NULL;
210 }
211
212
213 gint32 xs_get_length(gchar *pcFilename, gint iSubTune)
214 {
215 gint32 iResult;
216 t_xs_dbentry *dbEntry;
217 t_xs_hash dbHash;
218
219 iResult = -1;
220
221 switch (xs_cfg.playMethod) {
222
223 case XMMS_SID_PMETHOD_DATABASE:
224
225 iResult = xs_db_get(pcFilename, &dbHash);
226 if (iResult >= 0)
227 {
228 }
229
230 if (dbEntry)
231 {
232 if ((iSubTune >= 0) && (iSubTune < dbEntry->nlengths))
233 iResult = dbEntry->lengths[iSubTune];
234 else
235 iResult = -1;
236 } 586 }
237 break; 587 break;
238 588
239 case XMMS_SID_PMETHOD_MAXSILENCE: 589 case XMMS_SID_PMETHOD_MAXSILENCE:
240 case XMMS_SID_PMETHOD_NONE: 590 case XMMS_SID_PMETHOD_NONE:
253 iResult = xs_cfg.playMaxTime; 603 iResult = xs_cfg.playMaxTime;
254 else 604 else
255 iResult = -1; 605 iResult = -1;
256 } 606 }
257 607
258 XSDEBUG("fname='%s', sub=%i, res=%li\n", pcFilename, iSubTune, iResult); 608 XSDEBUG("fname='%s', sub=%i, res=%i\n", fileName, subTune, iResult);
259 609
260 return iResult; 610 return iResult;
261 } 611 }
262 612
613