1 /*
2 	playlist: playlist logic
3 
4 	copyright 1995-2008 by the mpg123 project - free software under the terms of the LGPL 2.1
5 	see COPYING and AUTHORS files in distribution or http://mpg123.org
6 	initially written by Michael Hipp, outsourced/reorganized by Thomas Orgis
7 
8 	If we officially support Windows again, we should have this reworked to really cope with Windows paths, too.
9 */
10 
11 /* Need random(). */
12 #define _DEFAULT_SOURCE
13 #define _BSD_SOURCE
14 
15 #include "mpg123app.h"
16 #include "sysutil.h"
17 #include "getlopt.h" /* for loptind */
18 #include "term.h" /* for term_restore */
19 #include "playlist.h"
20 #include "httpget.h"
21 #include <time.h> /* For srand(). */
22 #include "debug.h"
23 
24 #ifdef HAVE_RANDOM
25 #define RAND random
26 #define SRAND srandom
27 #else
28 #define RAND rand
29 #define SRAND srand
30 #endif
31 
32 /* increase linebuf in blocks of ... bytes */
33 #define LINEBUF_STEP 100
34 
35 enum playlist_type { UNKNOWN = 0, M3U, PLS, NO_LIST };
36 
37 typedef struct listitem
38 {
39 	char* url; /* the filename */
40 	char freeit; /* if it was allocated and should be free()d here */
41 	size_t playcount; /* has been played as ...th track in overall counting */
42 } listitem;
43 
44 typedef struct playlist_struct
45 {
46 	FILE* file; /* the current playlist stream */
47 	size_t entry; /* entry in the playlist file */
48 	size_t playcount; /* overall track counter for playback */
49 	long loop;    /* repeat a track n times */
50 	size_t size;
51 	size_t fill;
52 	size_t pos; /* (next) position, internal use */
53 	size_t num; /* current track number */
54 	size_t alloc_step;
55 	struct listitem* list;
56 	mpg123_string linebuf;
57 	mpg123_string dir;
58 	enum playlist_type type;
59 #if defined (WANT_WIN32_SOCKETS)
60 	int sockd; /* Is Win32 socket descriptor working? */
61 #endif
62 } playlist_struct;
63 
64 /* one global instance... add a pointer to this to every function definition and you have OO-style... */
65 static playlist_struct pl;
66 
67 static int add_next_file (int argc, char *argv[]);
68 static void shuffle_playlist(void);
69 static void init_playlist(void);
70 static int add_copy_to_playlist(char* new_entry);
71 static int add_to_playlist(char* new_entry, char freeit);
72 
73 /* used to be init_input */
prepare_playlist(int argc,char ** argv)74 void prepare_playlist(int argc, char** argv)
75 {
76 	/*
77 		fetch all playlist entries ... I don't consider playlists to be an endless stream.
78 		If you want to intentionally hang mpg123 on some other prog that may take infinite time to produce the full list (perhaps load tracks on demand), then just use the remote control and let that program print "load filename" instead of "filename".
79 		We may even provide a simple wrapper script that emulates the old playlist reading behaviour (for files and stdin, http playlists are actually a strong point on reading the list in _before_ starting playback since http connections don't last forever).
80 	*/
81 	init_playlist();
82 	while (add_next_file(argc, argv)) {}
83 	if(param.verbose > 1)
84 	{
85 		fprintf(stderr, "\nplaylist in normal order:\n");
86 		print_playlist(stderr, 0);
87 		fprintf(stderr, "\n");
88 	}
89 	if(param.shuffle == 1) shuffle_playlist();
90 	/* Don't need these anymore, we have copies! */
91 	mpg123_free_string(&pl.linebuf);
92 	mpg123_free_string(&pl.dir);
93 }
94 
95 /* Return a random number >= 0 and < n */
rando(size_t n)96 static size_t rando(size_t n)
97 {
98 	long ran;
99 	long limit = RAND_MAX - (RAND_MAX % (long)n);
100 	if(n<2) return 0; /* Better settle that here than in an endless loop... */
101 	do{ ran = RAND(); }while( ran >= limit );
102 	return (size_t)(ran%n);
103 }
104 
get_next_file(void)105 char *get_next_file(void)
106 {
107 	struct listitem *newitem = NULL;
108 
109 	/* Zero looping is nothing, as is nothing at all. */
110 	if(pl.fill == 0 || param.loop == 0)
111 		return NULL;
112 
113 	++pl.playcount;
114 
115 	/* normal order, just pick next thing */
116 	if(param.shuffle < 2)
117 	{
118 		do
119 		{
120 			if(pl.pos < pl.fill)
121 			{
122 				newitem = &pl.list[pl.pos];
123 				pl.num = pl.pos+1;
124 			}
125 			else newitem = NULL;
126 			/* if we have rounds left, decrease loop, else reinit loop because it's a new track */
127 			if(pl.loop > 0) --pl.loop; /* loop for current track... */
128 			if(pl.loop == 0)
129 			{
130 				pl.loop = param.loop;
131 				++pl.pos;
132 			}
133 		} while(pl.loop == 0 && newitem != NULL);
134 	}
135 	else
136 	{
137 		/* Handle looping first, but only if there is a random track selection
138 		   presently active (see playlist_jump() for interaction). */
139 		if(!(pl.num && ((pl.loop > 0 && --pl.loop) || pl.loop < 0)))
140 		{
141 			/* Randomly select the next track. */
142 			do /* limiting randomness: don't repeat too early */
143 			{
144 				pl.pos = rando(pl.fill);
145 			} while( pl.list[pl.pos].playcount
146 				&& (pl.playcount - pl.list[pl.pos].playcount) <= pl.fill/2 );
147 			pl.loop = param.loop;
148 		}
149 
150 		newitem = &pl.list[pl.pos];
151 		pl.num = pl.pos+1;
152 	}
153 
154 	/* "-" is STDOUT, "" is dumb, NULL is nothing */
155 	if(newitem != NULL)
156 	{
157 		/* Remember the playback position of the track. */
158 		newitem->playcount = pl.playcount;
159 		return newitem->url;
160 	}
161 	else return NULL;
162 }
163 
playlist_pos(size_t * total,long * loop)164 size_t playlist_pos(size_t *total, long *loop)
165 {
166 	if(total)
167 		*total = pl.fill;
168 	if(loop)
169 		*loop = pl.loop;
170 	return pl.num;
171 }
172 
playlist_jump(ssize_t incr)173 void playlist_jump(ssize_t incr)
174 {
175 	size_t off = incr < 0 ? -incr : incr;
176 
177 	pl.loop = 0; /* This loop is done, always. */
178 	/* Straight or shuffled lists can be jumped around in. */
179 	if(pl.fill && param.shuffle < 2)
180 	{
181 		debug3("jump %"SIZE_P" (%ld) + %"SSIZE_P, pl.pos, pl.loop, incr);
182 		if(pl.pos)
183 			--pl.pos;
184 		/* Now we're at the _current_ position. */
185 		if(incr < 0)
186 			pl.pos -= off > pl.pos ? pl.pos : off;
187 		else
188 		{
189 			if(off >= pl.fill - pl.pos)
190 				pl.pos = pl.fill; /* Any value >= pl.fill would actually be OK. */
191 			else
192 				pl.pos += off;
193 		}
194 		debug2("jumped %"SIZE_P" (%ld)", pl.pos, pl.loop);
195 	}
196 }
197 
198 /* Directory jumping based on comparing the directory part of the playlist
199    URLs. */
cmp_dir(const char * patha,const char * pathb)200 static int cmp_dir(const char* patha, const char* pathb)
201 {
202 	size_t dirlen[2];
203 	dirlen[0] = dir_length(patha);
204 	dirlen[1] = dir_length(pathb);
205 	return (dirlen[0] < dirlen[1])
206 	?	-1
207 	:	( dirlen[0] > dirlen[1]
208 		?	1
209 		:	memcmp(patha, pathb, dirlen[0])
210 		);
211 }
212 
playlist_next_dir(void)213 void playlist_next_dir(void)
214 {
215 	if(pl.fill && param.shuffle < 2)
216 	{
217 		size_t npos = pl.pos ? pl.pos-1 : 0;
218 		do ++npos;
219 		while(npos < pl.fill && !cmp_dir(pl.list[npos-1].url, pl.list[npos].url));
220 		pl.pos = npos;
221 	}
222 	pl.loop = 0;
223 }
224 
playlist_prev_dir(void)225 void playlist_prev_dir(void)
226 {
227 	if(pl.fill && param.shuffle < 2)
228 	{
229 		size_t npos = pl.pos ? pl.pos-1 : 0;
230 		/* 1. Find end of previous directory. */
231 		if(npos && npos < pl.fill)
232 			do --npos;
233 			while(npos && !cmp_dir(pl.list[npos+1].url, pl.list[npos].url));
234 		/* npos == the last track of previous directory */
235 		/* 2. Find the first track of this directory */
236 		if(npos < pl.fill)
237 			while(npos && !cmp_dir(pl.list[npos-1].url, pl.list[npos].url))
238 				--npos;
239 		pl.pos = npos;
240 	}
241 	pl.loop = 0;
242 }
243 
244 /* It doesn't really matter on program exit, but anyway...
245    Make sure you don't free() an item of argv! */
free_playlist(void)246 void free_playlist(void)
247 {
248 	if(pl.list != NULL)
249 	{
250 		debug("going to free() the playlist");
251 		while(pl.fill)
252 		{
253 			--pl.fill;
254 			debug1("free()ing entry %lu", (unsigned long)pl.fill);
255 			if(pl.list[pl.fill].freeit) free(pl.list[pl.fill].url);
256 		}
257 		free(pl.list);
258 		pl.list = NULL;
259 		pl.size = 0;
260 		debug("free()d the playlist");
261 	}
262 	mpg123_free_string(&pl.linebuf);
263 	mpg123_free_string(&pl.dir);
264 }
265 
266 /* the constructor... */
init_playlist(void)267 static void init_playlist(void)
268 {
269 	SRAND(time(NULL));
270 	pl.file = NULL;
271 	pl.entry = 0;
272 	pl.playcount = 0;
273 	pl.size = 0;
274 	pl.fill = 0;
275 	pl.pos = 0;
276 	pl.num = 0;
277 	if(APPFLAG(MPG123APP_CONTINUE) && param.listentry > 0)
278 	pl.pos = param.listentry - 1;
279 
280 	pl.list = NULL;
281 	pl.alloc_step = 10;
282 	mpg123_init_string(&pl.dir);
283 	mpg123_init_string(&pl.linebuf);
284 	pl.type = UNKNOWN;
285 	pl.loop = param.loop;
286 #ifdef WANT_WIN32_SOCKETS
287 	pl.sockd = -1;
288 #endif
289 }
290 
291 /*
292 	slightly modified find_next_file from mpg123.c
293 	now doesn't return the next entry but adds it to playlist struct
294 	returns 1 if it found something, 0 on end
295 */
add_next_file(int argc,char * argv[])296 static int add_next_file (int argc, char *argv[])
297 {
298 	int firstline = 0;
299 
300 	/* hack for url that has been detected as track, not playlist */
301 	if(pl.type == NO_LIST) return 0;
302 
303 	/* Get playlist dirname to append it to the files in playlist */
304 	if (param.listname)
305 	{
306 		char* slashpos;
307 		/* Oh, right... that doesn't look good for Windows... */
308 		if ((slashpos=strrchr(param.listname, '/')))
309 		{
310 			/* up to and including /, with space for \0 */
311 			if(mpg123_resize_string(&pl.dir, 2 + slashpos - param.listname))
312 			{
313 				memcpy(pl.dir.p, param.listname, pl.dir.size-1);
314 				pl.dir.p[pl.dir.size-1] = 0;
315 			}
316 			else
317 			{
318 				error("cannot allocate memory for list directory!");
319 				pl.dir.size = 0;
320 			}
321 		}
322 	}
323 
324 	if (param.listname || pl.file)
325 	{
326 		size_t line_offset = 0;
327 #ifndef WANT_WIN32_SOCKETS
328 		if (!pl.file)
329 #else
330 		if (!pl.file && pl.sockd == -1)
331 #endif
332 		{
333 			/* empty or "-" */
334 			if (!*param.listname || !strcmp(param.listname, "-"))
335 			{
336 				pl.file = stdin;
337 				param.listname = NULL;
338 				pl.entry = 0;
339 			}
340 			else if (!strncmp(param.listname, "http://", 7))
341 			{
342 				int fd;
343 				struct httpdata htd;
344 				httpdata_init(&htd);
345 #ifndef WANT_WIN32_SOCKETS
346 				fd = http_open(param.listname, &htd);
347 #else
348 				fd = win32_net_http_open(param.listname, &htd);
349 #endif
350 				debug1("htd.content_type.p: %p", (void*) htd.content_type.p);
351 				if(!APPFLAG(MPG123APP_IGNORE_MIME) && htd.content_type.p != NULL)
352 				{
353 					int mimi;
354 					debug1("htd.content_type.p value: %s", htd.content_type.p);
355 					mimi = debunk_mime(htd.content_type.p);
356 
357 					if(mimi & IS_M3U) pl.type = M3U;
358 					else if(mimi & IS_PLS)	pl.type = PLS;
359 					else
360 					{
361 #ifndef WANT_WIN32_SOCKETS
362 						if(fd >= 0) close(fd);
363 #else
364 						if(fd != SOCKET_ERROR) win32_net_close(fd);
365 #endif
366 						fd = -1;
367 
368 						if(mimi & IS_FILE)
369 						{
370 							pl.type = NO_LIST;
371 							if(param.listentry < 0)
372 							{
373 								printf("#note you gave me a file url, no playlist, so...\n#entry 1\n%s\n", param.listname);
374 								return 0;
375 							}
376 							else
377 							{
378 								fprintf(stderr, "Note: MIME type indicates that this is no playlist but an mpeg audio file... reopening as such.\n");
379 								add_to_playlist(param.listname, 0);
380 								return 1;
381 							}
382 						}
383 						error1("Unknown playlist MIME type %s; maybe "PACKAGE_NAME" can support it in future if you report this to the maintainer.", htd.content_type.p);
384 					}
385 					httpdata_free(&htd);
386 				}
387 				if(fd < 0)
388 				{
389 					param.listname = NULL;
390 					pl.file = NULL;
391 #ifdef WANT_WIN32_SOCKETS
392 					pl.sockd = -1;
393 #endif
394 					error("Invalid playlist from http_open()!\n");
395 				}
396 				else
397 				{
398 					pl.entry = 0;
399 #ifndef WANT_WIN32_SOCKETS
400 					pl.file = compat_fdopen(fd,"r");
401 #else
402 					pl.sockd = fd;
403 #endif
404 				}
405 			}
406 			else if (!(pl.file = fopen(param.listname, "rb")))
407 			{
408 				perror (param.listname);
409 				return 0;
410 			}
411 			else
412 			{
413 				debug("opened ordinary list file");
414 				pl.entry = 0;
415 			}
416 			if (param.verbose && pl.file) fprintf (stderr, "Using playlist from %s ...\n",	param.listname ? param.listname : "standard input");
417 			firstline = 1; /* just opened */
418 		}
419 		/* reading the file line by line */
420 #ifndef WANT_WIN32_SOCKETS
421 		while (pl.file)
422 #else
423 		while (pl.file || (pl.sockd) != -1)
424 #endif
425 		{
426 			/*
427 				now read a string of arbitrary size...
428 				read a chunk, see if lineend, realloc, read another chunk
429 
430 				fgets reads at most size-1 bytes and always appends the \0
431 			*/
432 			size_t have = 0;
433 			do
434 			{
435 				/* have is the length of the string read, without the closing \0 */
436 				if(pl.linebuf.size <= have+1)
437 				{
438 					if(!mpg123_resize_string(&pl.linebuf, pl.linebuf.size+LINEBUF_STEP))
439 					{
440 						error("cannot increase line buffer");
441 						break;
442 					}
443 				}
444 				/* I rely on fgets writing the \0 at the end! */
445 #ifndef WANT_WIN32_SOCKETS
446 				if(fgets(pl.linebuf.p+have, pl.linebuf.size-have, pl.file))
447 #else
448 				if( (pl.file ? (fgets(pl.linebuf.p+have, pl.linebuf.size-have, pl.file)) : (win32_net_fgets(pl.linebuf.p+have, pl.linebuf.size-have, pl.sockd))))
449 #endif
450 				{
451 					have += strlen(pl.linebuf.p+have);
452 					debug2("have read %lu characters into linebuf: [%s]", (unsigned long)have, pl.linebuf.p);
453 				}
454 				else
455 				{
456 					debug("fgets failed to deliver something... file ended?");
457 					break;
458 				}
459 			} while(have && pl.linebuf.p[have-1] != '\r' && pl.linebuf.p[have-1] != '\n');
460 			if(have)
461 			{
462 				pl.linebuf.p[strcspn(pl.linebuf.p, "\t\n\r")] = '\0';
463 				/* a bit of fuzzyness */
464 				if(firstline)
465 				{
466 					if(pl.type == UNKNOWN)
467 					{
468 						if(!strcmp("[playlist]", pl.linebuf.p))
469 						{
470 							fprintf(stderr, "Note: detected Shoutcast/Winamp PLS playlist\n");
471 							pl.type = PLS;
472 							continue;
473 						}
474 						else if
475 						(
476 							(!strncasecmp("#M3U", pl.linebuf.p ,4))
477 							||
478 							(!strncasecmp("#EXTM3U", pl.linebuf.p ,7))
479 							||
480 							(param.listname != NULL && (strrchr(param.listname, '.')) != NULL && !strcasecmp(".m3u", strrchr(param.listname, '.')))
481 						)
482 						{
483 							if(param.verbose) fprintf(stderr, "Note: detected M3U playlist type\n");
484 							pl.type = M3U;
485 						}
486 						else
487 						{
488 							if(param.verbose) fprintf(stderr, "Note: guessed M3U playlist type\n");
489 							pl.type = M3U;
490 						}
491 					}
492 					else
493 					{
494 						if(param.verbose)
495 						{
496 							fprintf(stderr, "Note: Interpreting as ");
497 							switch(pl.type)
498 							{
499 								case M3U: fprintf(stderr, "M3U"); break;
500 								case PLS: fprintf(stderr, "PLS (Winamp/Shoutcast)"); break;
501 								default: fprintf(stderr, "???");
502 							}
503 							fprintf(stderr, " playlist\n");
504 						}
505 					}
506 					firstline = 0;
507 				}
508 				#if !defined(WIN32)
509 				{
510 				size_t i;
511 				/* convert \ to / (from MS-like directory format) */
512 				for (i=0;pl.linebuf.p[i]!='\0';i++)
513 				{
514 					if (pl.linebuf.p[i] == '\\')	pl.linebuf.p[i] = '/';
515 				}
516 				}
517 				#endif
518 				if (pl.linebuf.p[0]=='\0') continue; /* skip empty lines... */
519 				if (((pl.type == M3U) && (pl.linebuf.p[0]=='#')))
520 				{
521 					/* a comment line in m3u file */
522 					if(param.listentry < 0) printf("%s\n", pl.linebuf.p);
523 					continue;
524 				}
525 
526 				/* real filename may start at an offset */
527 				line_offset = 0;
528 				/* extract path out of PLS */
529 				if(pl.type == PLS)
530 				{
531 					if(!strncasecmp("File", pl.linebuf.p, 4))
532 					{
533 						/* too lazy to really check for file number... would have to change logic to support unordered file entries anyway */
534 						char* in_line;
535 						if((in_line = strchr(pl.linebuf.p+4, '=')) != NULL)
536 						{
537 							/* FileN=? */
538 							if(in_line[1] != 0)
539 							{
540 								++in_line;
541 								line_offset = (size_t) (in_line-pl.linebuf.p);
542 							}
543 							else
544 							{
545 								fprintf(stderr, "Warning: Invalid PLS line (empty filename) - corrupt playlist file?\n");
546 								continue;
547 							}
548 						}
549 						else
550 						{
551 							fprintf(stderr, "Warning: Invalid PLS line (no '=' after 'File') - corrupt playlist file?\n");
552 							continue;
553 						}
554 					}
555 					else
556 					{
557 						if(param.listentry < 0) printf("#metainfo %s\n", pl.linebuf.p);
558 						continue;
559 					}
560 				}
561 
562 				/* make paths absolute */
563 				/* Windows knows absolute paths with c: in front... should handle this if really supporting win32 again */
564 				if
565 				(
566 					(pl.dir.p != NULL)
567 					&& (pl.linebuf.p[line_offset]!='/')
568 					&& (pl.linebuf.p[line_offset]!='\\')
569 					&& strncmp(pl.linebuf.p+line_offset, "http://", 7)
570 				)
571 				{
572 					size_t need;
573 					need = pl.dir.size + strlen(pl.linebuf.p+line_offset);
574 					if(pl.linebuf.size < need)
575 					{
576 						if(!mpg123_resize_string(&pl.linebuf, need))
577 						{
578 							error("unable to enlarge linebuf for appending path! skipping");
579 							continue;
580 						}
581 					}
582 					/* move to have the space at beginning */
583 					memmove(pl.linebuf.p+pl.dir.size-1, pl.linebuf.p+line_offset, strlen(pl.linebuf.p+line_offset)+1);
584 					/* prepend path */
585 					memcpy(pl.linebuf.p, pl.dir.p, pl.dir.size-1);
586 					line_offset = 0;
587 				}
588 				++pl.entry;
589 				if(param.listentry < 0) printf("#entry %lu\n%s\n", (unsigned long)pl.entry,pl.linebuf.p+line_offset);
590 				else if((param.listentry == 0) || (param.listentry == pl.entry) || APPFLAG(MPG123APP_CONTINUE))
591 				{
592 					add_copy_to_playlist(pl.linebuf.p+line_offset);
593 					return 1;
594 				}
595 			}
596 			else
597 			{
598 				if (param.listname)
599 				if(pl.file) fclose (pl.file);
600 				param.listname = NULL;
601 				pl.file = NULL;
602 #ifdef WANT_WIN32_SOCKETS
603 				if( pl.sockd != -1)
604 				{
605 				  win32_net_close(pl.sockd);
606 				  pl.sockd = -1;
607 				}
608 #endif
609 			}
610 		}
611 	}
612 	if(loptind < argc)
613 	{
614 		add_to_playlist(argv[loptind++], 0);
615 		return 1;
616 	}
617 	return 0;
618 }
619 
shuffle_playlist(void)620 static void shuffle_playlist(void)
621 {
622 	size_t loop;
623 	size_t rannum;
624 	if(pl.fill >= 2)
625 	{
626 		/* Refer to bug 1777621 for discussion on that.
627 		   It's Durstenfeld... */
628 		for (loop = 0; loop < pl.fill; loop++)
629 		{
630 			struct listitem tmp;
631 			rannum = loop + rando(pl.fill-loop);
632 			/*
633 				Small test on your binary operation skills (^ is XOR):
634 				a = b^(a^b)
635 				b = (a^b)^(b^(a^b))
636 				And, understood? ;-)
637 
638 				pl.list[loop] ^= pl.list[rannum];
639 				pl.list[rannum] ^= pl.list[loop];
640 				pl.list[loop] ^= pl.list[rannum];
641 
642 				But since this is not allowed with pointers and any speed gain questionable (well, saving _some_ memory...), doing it the lame way:
643 			*/
644 			tmp = pl.list[rannum];
645 			pl.list[rannum] = pl.list[loop];
646 			pl.list[loop] = tmp;
647 		}
648 	}
649 
650 	if(param.verbose > 1)
651 	{
652 		/* print them */
653 		fprintf(stderr, "\nshuffled playlist:\n");
654 		print_playlist(stderr, 0);
655 		fprintf(stderr, "\n");
656 	}
657 }
658 
print_playlist(FILE * out,int showpos)659 void print_playlist(FILE* out, int showpos)
660 {
661 	size_t loop;
662 	for (loop = 0; loop < pl.fill; loop++)
663 	{
664 		char *pre = "";
665 		if(showpos)
666 		pre = (loop+1==pl.num) ? "> " : "  ";
667 
668 		fprintf(out, "%s%s\n", pre, pl.list[loop].url);
669 	}
670 }
671 
672 
add_copy_to_playlist(char * new_entry)673 static int add_copy_to_playlist(char* new_entry)
674 {
675 	char* cop;
676 	if((cop = (char*) malloc(strlen(new_entry)+1)) != NULL)
677 	{
678 		strcpy(cop, new_entry);
679 		return add_to_playlist(cop, 1);
680 	}
681 	else return 0;
682 }
683 
684 /* add new entry to playlist - no string copy, just the pointer! */
add_to_playlist(char * new_entry,char freeit)685 static int add_to_playlist(char* new_entry, char freeit)
686 {
687 	if(pl.fill == pl.size)
688 	{
689 		struct listitem* tmp = NULL;
690 		/* enlarge the list */
691 		tmp = (struct listitem*) safe_realloc(pl.list, (pl.size + pl.alloc_step) * sizeof(struct listitem));
692 		if(!tmp)
693 		{
694 			error("unable to allocate more memory for playlist");
695 			perror("");
696 			return 0;
697 		}
698 		else
699 		{
700 			pl.list = tmp;
701 			pl.size += pl.alloc_step;
702 		}
703 	}
704 	/* paranoid */
705 	if(pl.fill < pl.size)
706 	{
707 		pl.list[pl.fill].freeit = freeit;
708 		pl.list[pl.fill].url = new_entry;
709 		pl.list[pl.fill].playcount = 0;
710 		++pl.fill;
711 	}
712 	else
713 	{
714 		error("playlist memory still too small?!");
715 		return 0;
716 	}
717 	return 1;
718 }
719 
720