aboutsummaryrefslogblamecommitdiffstats
path: root/src/playlist.c
blob: cb2be91d71d8da28c949ae779e3ed6aefab742bc (plain) (tree)
1
2
                                
                                                                   





















                                                                            


                         
                       







                      




















                                                                
                                 

                                                         
                          
                     
                                                  


                            



                    
                           
           
                                                
                                                             
                                
                               
                               
                               
 
                                                                             
 
                                    
                                                         
                                               
 

                                                              
                           
                                      
                      
                                                       





                                                
                                
                  
                                               


                                                       
 










                                                      
 

                       
              
                           

                            
                             
                             
                              
 
                                                         
 
                    
                                                                      
                                    
                                                                              
                                                                      
                                           
         
 
                                                                                  
                                                                                                                
                                                                    
 


                                                                             
                                                           
                                                                           
 
                                                                        
 
                            
 
                                                                        
                                              

         
                          
                            
 
            
                      
                                                                      
                                
                                                   
                   
 
                         
              
                                                               
                                                        
                 
         

                            
                             
                              
                                
                             
                              


                                     
 
                         
 
              
                                 
                          
 
                                                               
                                                        
                 
                                                                     
                                         
                            
                              




                              




































                                                                             
                        
 
              
                                               
                                                                          



                 
                                
 




                                                                             
                              
                                                                            
                 






                                                                    
         






                                                                           
 
                                                                       
 
                   
                 
                                                       
                                                                  
                                               
                                                                                
                                                         
                                                           
                                                                     
                         
                                                          
                                                           
                         
                                                         
                                                                 
                                                                              
                         
                                                               

         
                                
 









                                                                                
                              
















                                                                              
                              


















                                                                                  
                              

















                                                                           
                 

         
                                                   
 
                                                                             
 
                                               
 
              
 


                                                     
                                                     




                 
                                                    
 
              
 


                                                     
                                                              




                 
                                  
 


                                  
                        
                             
                               
         
                                      
                                                    
                                                                 

                          
                                     
                                             


                 
                                                                      
                                                           
           
                                                      
                                                               


                            
                              
 


                                  
                      
                                
                                                  
                                
         
                                     
                                             


                 
                                           
                    
                  
 
                                      
                                                      
                                      
                                                   
 
                                                                    
 

                                                                    
 
                                         

                                                       
                                                         



                                                                                

                                                

                                                               

                                                         



                                                                                




                                                
                                            
                                                                   
                                          




                                                                  
                                                  
                                           


                                                                     
                                                  

         
                                         

                                                         



                                         
                                           

                            
                                  
 
                             
                                        









                                                               
                                                 
                   
 



                                                                   
                                                    
                                                                   

                          
                                                    
 
















                                                                   
                                                       
 
               
                                                     
                                                        
                                                            

                          

                                                                 




                                                    
                         
                                               
                                                             
                                                          
                                                    
                                                                       
                          
                              
                          
                                                                    









                                                                    
                              
                    
                                             
 

                 












































                                                                            
                                                     
 

                             
                                                    
                                                    
                                                                  
                          
                                                    
                                                    
                                                                  
                          

                                                    


                                                                     
                                                                      




                                                    
                                

                           

                                                              
                      







                                                       
         



                              
                                                     
 

                         
                                                                  
                                                               
 
                                    
                                                                  


                                                    
                                        
 

                      
                                                  
                                                    
                                                                 

                          


                                                                       
                                                  
                                                    

                 
                                                          

                                                   
                                                                
 
                                          
                                                      
                                            

                                                   

                                                   
                                                          
                                                

                                                   
                                               
                                                   
                          
                              
                                                 

                                                                               
                                        
 
                                           
                                   
                                                         
                                      
 
                                          




                                  
                                          
 
                        
                                                                 
 
                                         
              
 



                                                
                                                             


                 
                        
 
                                  
                               
                          


                                             
                                                       

                 
                                                        
 
 
                               
                          



                                             
 
                                                                    
 
                                                                             
                          
          
                                    


                 
                                                   
 


                           

                                         
 
                                                            
                                                     
                 
                                                                                  
                                             
                        
                              
                                                         
                                                    
                                                                 

                          





                                                                             
                                                     
                                                       
                                             
                 



                                           
                                              
 
                                                     
                       
                                                         


                        
                                                                        
 
                                                 

                                                     
                    
 
                        
 
                                                  
 
                                                   
                                       



                                                             
                                                       
                                                             
                                      
 


                                                  
 


                                                            
 
                                          
 
                                        

                                                  



                                 
                                                                          
                                                                     
              
                                        


                 
                              

                                                  
                                 
 
                                 
                                                     
                                                                         

                                                               
                                                      
                
                                      
                                        



                 

                                                    
                                             


                                                  
 



                                                                               
                                                    
                                               
                                                             
                    
                                                          

         
                                 

                               
                                 

                               
                                               
                                         
                                                                                

                          
                                                                         









                                                    
                                                
 
              
                      
                  

                             
                                                  
                                                    
                                                                 

                          
                                              
                                                    
                                                               

                          
                                                    

                                                                     
                                                                  





                                                    
                                            
                                                
                                         
         
                                                
                                         
         
                              
                                          
                                     
                                                
                                 

                                                                                  
                                                    
                                                              
                                                    
                                                               

                                                       


















                                                                               
         



                              
                                                   
 
                         
 
                                                                      
 
                               
              
                                                                          
                                                                    
 
                                                    




                                                    
                                               

                                      
 
                                   



                                              
                                              

               
                                                                 
 
                                                                         




                                                    





                                                          
         
 
                                               
 
                                        
                                         
                                                                                



                                 
                                      
                                                                   




                                                                          
                                                               
                                                     
                         
                                        



                 
                                  
 
                               


                                            
                                                  

                                 
                                                                            
                                                                     
                                           
                                                          
                                                                             
                                                                                
                        
                                                                             
                 



                 
                           
 

               
                                                            


                                                            
                                                                       
                                      
                                                                          
                                                     
                                                     
                              

                                              
                 
                                                  

                                                                  
                 





                                      
                                          
                                               
                                                          
                                                               
 

                                            
 
                                                                    
                            
                                                    
                                                                    

                          
                                     
                                                  
                                                       




                          
                                        
                    
                       

                         
 
                                    
                                               
                                                                          

                          
                                         
 
                                                    
                                                         
 

                                            
 


                                    
                                         
                                                                                
                                                                      

                          
                                                                     
                                                                            

                          

                                                                
                                              

                                                                              
                                                                                
                 
         
                                                 


                 



                                                                          
 
                  
 
                                      

                                
                           

                               
                                                    
 
                     
                                                  
                                                    
                                                                 

                          
                                                             



                                 
                                                    


                                                    
                                                      
                          
 
                                    
                                                       
                                  
         
                                                                       
 
                                                      
 
                        
                                                                       
 
                               

                                           
                                                                      


                                                                   
                        
                                               
                                                          

                                                               
                                       
                                 
                    
                                 
 

                                            
 
                                                                    
                            
                                                    
                                                                    

                          
                                                                     
                                                  
                                                                       

                          
                                                 
                                     
                                                               
                                             
                                          
                                                       
                                                     

                                                                          
                                                  



                                                                             
                                                        
                                                                              
                                                   


                                                                   



                                                  
                                                  

                                                


                                                                      
                                                              
                         
                                                   
                                          
                                                                 
                                                                             
                                                                             


                                                                 
                                  
                                              
         
                                                 
 
                          
                                                         
                                                                   





                                  
                                                                             

                                             
                                        
                
                                                         
         
 
                                                                         
 
                                                 
 
                                                                        

                                                         
                                                                 
                                                                
                                            


                                         
                            
                 
                                                                   
                                            
                                                             

                            
         
 
 
                                                    
                     
                                                                             
         
                                                                 
 
 
                                        
 
                                                                    
 































                                                                                
/* the Music Player Daemon (MPD)
 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
 * This project's homepage is: http://www.musicpd.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 "playlist.h"
#include "player.h"
#include "command.h"
#include "ls.h"
#include "tag.h"
#include "conf.h"
#include "directory.h"
#include "log.h"
#include "path.h"
#include "utils.h"
#include "sig_handlers.h"
#include "state_file.h"

#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>

#define PLAYLIST_COMMENT	'#'

#define PLAYLIST_STATE_STOP		0
#define PLAYLIST_STATE_PLAY		1

#define PLAYLIST_PREV_UNLESS_ELAPSED    10

#define PLAYLIST_STATE_FILE_STATE		"state: "
#define PLAYLIST_STATE_FILE_RANDOM		"random: "
#define PLAYLIST_STATE_FILE_REPEAT		"repeat: "
#define PLAYLIST_STATE_FILE_CURRENT		"current: "
#define PLAYLIST_STATE_FILE_TIME		"time: "
#define PLAYLIST_STATE_FILE_CROSSFADE		"crossfade: "
#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN	"playlist_begin"
#define PLAYLIST_STATE_FILE_PLAYLIST_END	"playlist_end"

#define PLAYLIST_STATE_FILE_STATE_PLAY		"play"
#define PLAYLIST_STATE_FILE_STATE_PAUSE		"pause"
#define PLAYLIST_STATE_FILE_STATE_STOP		"stop"

#define PLAYLIST_BUFFER_SIZE	2*MAXPATHLEN

#define PLAYLIST_HASH_MULT	4

#define DEFAULT_PLAYLIST_MAX_LENGTH		(1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS	0

typedef struct _Playlist {
	Song **songs;
	/* holds version a song was modified on */
	mpd_uint32 *songMod;
	int *order;
	int *positionToId;
	int *idToPosition;
	int length;
	int current;
	int queued;
	int repeat;
	int random;
	mpd_uint32 version;
} Playlist;

static Playlist playlist;
static int playlist_state = PLAYLIST_STATE_STOP;
static int playlist_max_length = DEFAULT_PLAYLIST_MAX_LENGTH;
static int playlist_stopOnError;
static int playlist_errorCount;
static int playlist_queueError;
static int playlist_noGoToNext;

static int playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;

static void swapOrder(int a, int b);
static int playPlaylistOrderNumber(int fd, int orderNum);
static void randomizeOrder(int start, int end);

static void incrPlaylistVersion(void)
{
	static unsigned long max = ((mpd_uint32) 1 << 31) - 1;
	playlist.version++;
	if (playlist.version >= max) {
		int i;

		for (i = 0; i < playlist.length; i++) {
			playlist.songMod[i] = 0;
		}

		playlist.version = 1;
	}
}

void playlistVersionChange(void)
{
	int i = 0;

	for (i = 0; i < playlist.length; i++) {
		playlist.songMod[i] = playlist.version;
	}

	incrPlaylistVersion();
}

static void incrPlaylistCurrent(void)
{
	if (playlist.current < 0)
		return;

	if (playlist.current >= playlist.length - 1) {
		if (playlist.repeat)
			playlist.current = 0;
		else
			playlist.current = -1;
	} else
		playlist.current++;
}

void initPlaylist(void)
{
	char *test;
	int i;
	ConfigParam *param;

	playlist.length = 0;
	playlist.repeat = 0;
	playlist.version = 1;
	playlist.random = 0;
	playlist.queued = -1;
	playlist.current = -1;

	param = getConfigParam(CONF_MAX_PLAYLIST_LENGTH);

	if (param) {
		playlist_max_length = strtol(param->value, &test, 10);
		if (*test != '\0') {
			ERROR("max playlist length \"%s\" is not an integer, "
			      "line %i\n", param->value, param->line);
			exit(EXIT_FAILURE);
		}
	}

	playlist_saveAbsolutePaths = getBoolConfigParam(CONF_SAVE_ABSOLUTE_PATHS);
	if (playlist_saveAbsolutePaths == -1) playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;
	else if (playlist_saveAbsolutePaths < 0) exit(EXIT_FAILURE);

	playlist.songs = xmalloc(sizeof(Song *) * playlist_max_length);
	playlist.songMod = xmalloc(sizeof(mpd_uint32) * playlist_max_length);
	playlist.order = xmalloc(sizeof(int) * playlist_max_length);
	playlist.idToPosition = xmalloc(sizeof(int) * playlist_max_length *
				       PLAYLIST_HASH_MULT);
	playlist.positionToId = xmalloc(sizeof(int) * playlist_max_length);

	memset(playlist.songs, 0, sizeof(char *) * playlist_max_length);

	srandom(time(NULL));

	for (i = 0; i < playlist_max_length * PLAYLIST_HASH_MULT; i++) {
		playlist.idToPosition[i] = -1;
	}
}

static int getNextId(void)
{
	static int cur = -1;

	do {
		cur++;
		if (cur >= playlist_max_length * PLAYLIST_HASH_MULT) {
			cur = 0;
		}
	} while (playlist.idToPosition[cur] != -1);

	return cur;
}

void finishPlaylist(void)
{
	int i;
	for (i = 0; i < playlist.length; i++) {
		if (playlist.songs[i]->type == SONG_TYPE_URL) {
			freeJustSong(playlist.songs[i]);
		}
	}

	playlist.length = 0;

	free(playlist.songs);
	playlist.songs = NULL;
	free(playlist.songMod);
	playlist.songMod = NULL;
	free(playlist.order);
	playlist.order = NULL;
	free(playlist.idToPosition);
	playlist.idToPosition = NULL;
	free(playlist.positionToId);
	playlist.positionToId = NULL;
}

int clearPlaylist(int fd)
{
	int i;

	if (stopPlaylist(fd) < 0)
		return -1;

	for (i = 0; i < playlist.length; i++) {
		if (playlist.songs[i]->type == SONG_TYPE_URL) {
			freeJustSong(playlist.songs[i]);
		}
		playlist.idToPosition[playlist.positionToId[i]] = -1;
		playlist.songs[i] = NULL;
	}
	playlist.length = 0;
	playlist.current = -1;

	incrPlaylistVersion();

	return 0;
}

int clearStoredPlaylist(int fd, char *utf8file)
{
	int fileD;
	char *file;
	char *rfile;
	char *actualFile;

	if (strstr(utf8file, "/")) {
		commandError(fd, ACK_ERROR_ARG,
		             "cannot clear \"%s\", saving playlists to "
		             "subdirectories is not supported", utf8file);
		return -1;
	}

	file = utf8ToFsCharset(utf8file);

	rfile = xmalloc(strlen(file) + strlen(".") +
	                strlen(PLAYLIST_FILE_SUFFIX) + 1);

	strcpy(rfile, file);
	strcat(rfile, ".");
	strcat(rfile, PLAYLIST_FILE_SUFFIX);

	actualFile = rpp2app(rfile);

	free(rfile);

	while ((fileD = open(actualFile, O_WRONLY | O_TRUNC | O_CREAT)) == -1
	       && errno == EINTR);
	if (fileD == -1) {
		commandError(fd, ACK_ERROR_SYSTEM, "problems opening file");
		return -1;
	}
	while (close(fileD) == -1 && errno == EINTR);

	return 0;
}

int showPlaylist(int fd)
{
	int i;

	for (i = 0; i < playlist.length; i++) {
		fdprintf(fd, "%i:%s\n", i, getSongUrl(playlist.songs[i]));
	}

	return 0;
}

void savePlaylistState(FILE *fp)
{
	fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE);
	switch (playlist_state) {
	case PLAYLIST_STATE_PLAY:
		switch (getPlayerState()) {
		case PLAYER_STATE_PAUSE:
			fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE);
			break;
		default:
			fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY);
		}
		fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT,
		        playlist.order[playlist.current]);
		fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
		        getPlayerElapsedTime());
		break;
	default:
		fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP);
		break;
	}
	fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, playlist.random);
	fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, playlist.repeat);
	fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
	        (int)(getPlayerCrossFade()));
	fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
	fflush(fp);
	showPlaylist(fileno(fp));
	fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
}

static void loadPlaylistFromStateFile(FILE *fp, char *buffer,
				      int state, int current, int time)
{
	char *temp;
	int song;

	if (!myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp))
		state_file_fatal();
	while (strcmp(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
		song = atoi(strtok(buffer, ":"));
		if (!(temp = strtok(NULL, "")))
			state_file_fatal();
		if (!addToPlaylist(STDERR_FILENO, temp, 0) && current == song) {
			if (state != PLAYER_STATE_STOP) {
				playPlaylist(STDERR_FILENO,
					     playlist.length - 1, 0);
			}
			if (state == PLAYER_STATE_PAUSE) {
				playerPause(STDERR_FILENO);
			}
			if (state != PLAYER_STATE_STOP) {
				seekSongInPlaylist(STDERR_FILENO,
						   playlist.length - 1, time);
			}
		}
		if (!myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp))
			state_file_fatal();
	}
}

void readPlaylistState(FILE *fp)
{
	int current = -1;
	int time = 0;
	int state = PLAYER_STATE_STOP;
	char buffer[PLAYLIST_BUFFER_SIZE];

	while (myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) {
		if (strncmp(buffer, PLAYLIST_STATE_FILE_STATE,
			    strlen(PLAYLIST_STATE_FILE_STATE)) == 0) {
			if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
				   PLAYLIST_STATE_FILE_STATE_PLAY) == 0) {
				state = PLAYER_STATE_PLAY;
			} else
			    if (strcmp
				(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
				 PLAYLIST_STATE_FILE_STATE_PAUSE)
				== 0) {
				state = PLAYER_STATE_PAUSE;
			}
		} else if (strncmp(buffer, PLAYLIST_STATE_FILE_TIME,
				   strlen(PLAYLIST_STATE_FILE_TIME)) == 0) {
			time =
			    atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)]));
		} else
		    if (strncmp
			(buffer, PLAYLIST_STATE_FILE_REPEAT,
			 strlen(PLAYLIST_STATE_FILE_REPEAT)) == 0) {
			if (strcmp
			    (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
			     "1") == 0) {
				setPlaylistRepeatStatus(STDERR_FILENO, 1);
			} else
				setPlaylistRepeatStatus(STDERR_FILENO, 0);
		} else
		    if (strncmp
			(buffer, PLAYLIST_STATE_FILE_CROSSFADE,
			 strlen(PLAYLIST_STATE_FILE_CROSSFADE)) == 0) {
			setPlayerCrossFade(atoi
					   (&
					    (buffer
					     [strlen
					      (PLAYLIST_STATE_FILE_CROSSFADE)])));
		} else
		    if (strncmp
			(buffer, PLAYLIST_STATE_FILE_RANDOM,
			 strlen(PLAYLIST_STATE_FILE_RANDOM)) == 0) {
			if (strcmp
			    (&
			     (buffer
			      [strlen(PLAYLIST_STATE_FILE_RANDOM)]),
			     "1") == 0) {
				setPlaylistRandomStatus(STDERR_FILENO, 1);
			} else
				setPlaylistRandomStatus(STDERR_FILENO, 0);
		} else if (strncmp(buffer, PLAYLIST_STATE_FILE_CURRENT,
				   strlen(PLAYLIST_STATE_FILE_CURRENT))
			   == 0) {
			if (strlen(buffer) ==
			    strlen(PLAYLIST_STATE_FILE_CURRENT))
				state_file_fatal();
			current = atoi(&(buffer
					 [strlen
					  (PLAYLIST_STATE_FILE_CURRENT)]));
		} else
		    if (strncmp
			(buffer, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN,
			 strlen(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)
			) == 0) {
			if (state == PLAYER_STATE_STOP)
				current = -1;
			loadPlaylistFromStateFile(fp, buffer, state,
						  current, time);
		}
	}
}

static void printPlaylistSongInfo(int fd, int song)
{
	printSongInfo(fd, playlist.songs[song]);
	fdprintf(fd, "Pos: %i\nId: %i\n", song, playlist.positionToId[song]);
}

int playlistChanges(int fd, mpd_uint32 version)
{
	int i;

	for (i = 0; i < playlist.length; i++) {
		if (version > playlist.version ||
		    playlist.songMod[i] >= version ||
		    playlist.songMod[i] == 0) {
			printPlaylistSongInfo(fd, i);
		}
	}

	return 0;
}

int playlistChangesPosId(int fd, mpd_uint32 version)
{
	int i;

	for (i = 0; i < playlist.length; i++) {
		if (version > playlist.version ||
		    playlist.songMod[i] >= version ||
		    playlist.songMod[i] == 0) {
			fdprintf(fd, "cpos: %i\nId: %i\n",
			         i, playlist.positionToId[i]);
		}
	}

	return 0;
}

int playlistInfo(int fd, int song)
{
	int i;
	int begin = 0;
	int end = playlist.length;

	if (song >= 0) {
		begin = song;
		end = song + 1;
	}
	if (song >= playlist.length) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "song doesn't exist: \"%i\"", song);
		return -1;
	}

	for (i = begin; i < end; i++)
		printPlaylistSongInfo(fd, i);

	return 0;
}

# define checkSongId(id) { \
	if(id < 0 || id >= PLAYLIST_HASH_MULT*playlist_max_length || \
			playlist.idToPosition[id] == -1 ) \
	{ \
		commandError(fd, ACK_ERROR_NO_EXIST, \
			"song id doesn't exist: \"%i\"", id); \
		return -1; \
	} \
}

int playlistId(int fd, int id)
{
	int i;
	int begin = 0;
	int end = playlist.length;

	if (id >= 0) {
		checkSongId(id);
		begin = playlist.idToPosition[id];
		end = begin + 1;
	}

	for (i = begin; i < end; i++)
		printPlaylistSongInfo(fd, i);

	return 0;
}

static void swapSongs(int song1, int song2)
{
	Song *sTemp;
	int iTemp;

	sTemp = playlist.songs[song1];
	playlist.songs[song1] = playlist.songs[song2];
	playlist.songs[song2] = sTemp;

	playlist.songMod[song1] = playlist.version;
	playlist.songMod[song2] = playlist.version;

	playlist.idToPosition[playlist.positionToId[song1]] = song2;
	playlist.idToPosition[playlist.positionToId[song2]] = song1;

	iTemp = playlist.positionToId[song1];
	playlist.positionToId[song1] = playlist.positionToId[song2];
	playlist.positionToId[song2] = iTemp;
}

static void queueNextSongInPlaylist(void)
{
	if (playlist.current < playlist.length - 1) {
		playlist.queued = playlist.current + 1;
		DEBUG("playlist: queue song %i:\"%s\"\n",
		      playlist.queued,
		      getSongUrl(playlist.
				 songs[playlist.order[playlist.queued]]));
		if (queueSong(playlist.songs[playlist.order[playlist.queued]]) <
		    0) {
			playlist.queued = -1;
			playlist_queueError = 1;
		}
	} else if (playlist.length && playlist.repeat) {
		if (playlist.length > 1 && playlist.random) {
			randomizeOrder(0, playlist.length - 1);
		}
		playlist.queued = 0;
		DEBUG("playlist: queue song %i:\"%s\"\n",
		      playlist.queued,
		      getSongUrl(playlist.
				 songs[playlist.order[playlist.queued]]));
		if (queueSong(playlist.songs[playlist.order[playlist.queued]]) <
		    0) {
			playlist.queued = -1;
			playlist_queueError = 1;
		}
	}
}

static void syncPlaylistWithQueue(int queue)
{
	if (queue && getPlayerQueueState() == PLAYER_QUEUE_BLANK) {
		queueNextSongInPlaylist();
	} else if (getPlayerQueueState() == PLAYER_QUEUE_DECODE) {
		if (playlist.queued != -1)
			setQueueState(PLAYER_QUEUE_PLAY);
		else
			setQueueState(PLAYER_QUEUE_STOP);
	} else if (getPlayerQueueState() == PLAYER_QUEUE_EMPTY) {
		setQueueState(PLAYER_QUEUE_BLANK);
		if (playlist.queued >= 0) {
			DEBUG("playlist: now playing queued song\n");
			playlist.current = playlist.queued;
		}
		playlist.queued = -1;
		if (queue)
			queueNextSongInPlaylist();
	}
}

static void lockPlaylistInteraction(void)
{
	if (getPlayerQueueState() == PLAYER_QUEUE_PLAY ||
	    getPlayerQueueState() == PLAYER_QUEUE_FULL) {
		playerQueueLock();
		syncPlaylistWithQueue(0);
	}
}

static void unlockPlaylistInteraction(void)
{
	playerQueueUnlock();
}

static void clearPlayerQueue(void)
{
	playlist.queued = -1;
	switch (getPlayerQueueState()) {
	case PLAYER_QUEUE_FULL:
		DEBUG("playlist: dequeue song\n");
		setQueueState(PLAYER_QUEUE_BLANK);
		break;
	case PLAYER_QUEUE_PLAY:
		DEBUG("playlist: stop decoding queued song\n");
		setQueueState(PLAYER_QUEUE_STOP);
		break;
	}
}

int addToPlaylist(int fd, char *url, int printId)
{
	Song *song;

	DEBUG("add to playlist: %s\n", url);

	if ((song = getSongFromDB(url))) {
	} else if (!(isValidRemoteUtf8Url(url) &&
		     (song = newSong(url, SONG_TYPE_URL, NULL)))) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "\"%s\" is not in the music db or is "
			     "not a valid url", url);
		return -1;
	}

	return addSongToPlaylist(fd, song, printId);
}

int addToStoredPlaylist(int fd, char *url, char *utf8file)
{
	Song *song;

	DEBUG("add to stored playlist: %s\n", url);

	if ((song = getSongFromDB(url))) {
	} else if (!(isValidRemoteUtf8Url(url) &&
	             (song = newSong(url, SONG_TYPE_URL, NULL)))) {
		commandError(fd, ACK_ERROR_NO_EXIST,
		             "\"%s\" is not in the music db or is "
		             "not a valid url", url);
		return -1;
	}

	return addSongToStoredPlaylist(fd, song, utf8file);
}

int addSongToPlaylist(int fd, Song * song, int printId)
{
	int id;

	if (playlist.length == playlist_max_length) {
		commandError(fd, ACK_ERROR_PLAYLIST_MAX,
			     "playlist is at the max size");
		return -1;
	}

	if (playlist_state == PLAYLIST_STATE_PLAY) {
		if (playlist.queued >= 0
		    && playlist.current == playlist.length - 1) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
		}
	}

	id = getNextId();

	playlist.songs[playlist.length] = song;
	playlist.songMod[playlist.length] = playlist.version;
	playlist.order[playlist.length] = playlist.length;
	playlist.positionToId[playlist.length] = id;
	playlist.idToPosition[playlist.positionToId[playlist.length]] =
	    playlist.length;
	playlist.length++;

	if (playlist.random) {
		int swap;
		int start;
		/*if(playlist_state==PLAYLIST_STATE_STOP) start = 0;
		   else */ if (playlist.queued >= 0)
			start = playlist.queued + 1;
		else
			start = playlist.current + 1;
		if (start < playlist.length) {
			swap = random() % (playlist.length - start);
			swap += start;
			swapOrder(playlist.length - 1, swap);
		}
	}

	incrPlaylistVersion();

	if (printId)
		fdprintf(fd, "Id: %i\n", id);

	return 0;
}

int addSongToStoredPlaylist(int fd, Song *song, char *utf8file)
{
	FILE *fileP;
	char *file;
	char *rfile;
	char *actualFile;
	char *url;

	if (strstr(utf8file, "/")) {
		commandError(fd, ACK_ERROR_ARG,
		             "cannot add to \"%s\", saving playlists to "
		             "subdirectories is not supported", utf8file);
		return -1;
	}

	file = utf8ToFsCharset(utf8file);

	rfile = xmalloc(strlen(file) + strlen(".") +
	                strlen(PLAYLIST_FILE_SUFFIX) + 1);

	strcpy(rfile, file);
	strcat(rfile, ".");
	strcat(rfile, PLAYLIST_FILE_SUFFIX);

	actualFile = rpp2app(rfile);

	free(rfile);

	while (!(fileP = fopen(actualFile, "a")) && errno == EINTR);
	if (fileP == NULL) {
		commandError(fd, ACK_ERROR_SYSTEM, "problems opening file");
		return -1;
	}

	url = utf8ToFsCharset(getSongUrl(song));

	if (playlist_saveAbsolutePaths && song->type == SONG_TYPE_FILE)
		fprintf(fileP, "%s\n", rmp2amp(url));
	else
		fprintf(fileP, "%s\n", url);

	while (fclose(fileP) && errno == EINTR);

	return 0;
}

int swapSongsInPlaylist(int fd, int song1, int song2)
{
	int queuedSong = -1;
	int currentSong = -1;

	if (song1 < 0 || song1 >= playlist.length) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "song doesn't exist: \"%i\"", song1);
		return -1;
	}
	if (song2 < 0 || song2 >= playlist.length) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "song doesn't exist: \"%i\"", song2);
		return -1;
	}

	if (playlist_state == PLAYLIST_STATE_PLAY) {
		if (playlist.queued >= 0) {
			queuedSong = playlist.order[playlist.queued];
		}
		currentSong = playlist.order[playlist.current];

		if (queuedSong == song1 || queuedSong == song2
		    || currentSong == song1 || currentSong == song2) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
		}
	}

	swapSongs(song1, song2);
	if (playlist.random) {
		int i;
		int k;
		int j = -1;
		for (i = 0; playlist.order[i] != song1; i++) {
			if (playlist.order[i] == song2)
				j = i;
		}
		k = i;
		for (; j == -1; i++)
			if (playlist.order[i] == song2)
				j = i;
		swapOrder(k, j);
	} else {
		if (playlist.current == song1)
			playlist.current = song2;
		else if (playlist.current == song2)
			playlist.current = song1;
	}

	incrPlaylistVersion();

	return 0;
}

int swapSongsInPlaylistById(int fd, int id1, int id2)
{
	checkSongId(id1);
	checkSongId(id2);

	return swapSongsInPlaylist(fd, playlist.idToPosition[id1],
				   playlist.idToPosition[id2]);
}

#define moveSongFromTo(from, to) { \
	playlist.idToPosition[playlist.positionToId[from]] = to; \
	playlist.positionToId[to] = playlist.positionToId[from]; \
	playlist.songs[to] = playlist.songs[from]; \
	playlist.songMod[to] = playlist.version; \
}

int deleteFromPlaylist(int fd, int song)
{
	int i;
	int songOrder;

	if (song < 0 || song >= playlist.length) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "song doesn't exist: \"%i\"", song);
		return -1;
	}

	if (playlist_state == PLAYLIST_STATE_PLAY) {
		if (playlist.queued >= 0
		    && (playlist.order[playlist.queued] == song
			|| playlist.order[playlist.current] == song)) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
		}
	}

	if (playlist.songs[song]->type == SONG_TYPE_URL) {
		freeJustSong(playlist.songs[song]);
	}

	playlist.idToPosition[playlist.positionToId[song]] = -1;

	/* delete song from songs array */
	for (i = song; i < playlist.length - 1; i++) {
		moveSongFromTo(i + 1, i);
	}
	/* now find it in the order array */
	for (i = 0; i < playlist.length - 1; i++) {
		if (playlist.order[i] == song)
			break;
	}
	songOrder = i;
	/* delete the entry from the order array */
	for (; i < playlist.length - 1; i++)
		playlist.order[i] = playlist.order[i + 1];
	/* readjust values in the order array */
	for (i = 0; i < playlist.length - 1; i++) {
		if (playlist.order[i] > song)
			playlist.order[i]--;
	}
	/* now take care of other misc stuff */
	playlist.songs[playlist.length - 1] = NULL;
	playlist.length--;

	incrPlaylistVersion();

	if (playlist_state != PLAYLIST_STATE_STOP
	    && playlist.current == songOrder) {
		/*if(playlist.current>=playlist.length) return playerStop(fd);
		   else return playPlaylistOrderNumber(fd,playlist.current); */
		playerStop(STDERR_FILENO);
		playlist_noGoToNext = 1;
	}

	if (playlist.current > songOrder) {
		playlist.current--;
	} else if (playlist.current >= playlist.length) {
		incrPlaylistCurrent();
	}

	if (playlist.queued > songOrder) {
		playlist.queued--;
	}

	return 0;
}

int deleteFromPlaylistById(int fd, int id)
{
	checkSongId(id);

	return deleteFromPlaylist(fd, playlist.idToPosition[id]);
}

void deleteASongFromPlaylist(Song * song)
{
	int i;

	if (NULL == playlist.songs)
		return;

	for (i = 0; i < playlist.length; i++) {
		if (song == playlist.songs[i]) {
			deleteFromPlaylist(STDERR_FILENO, i);
		}
	}
}

int stopPlaylist(int fd)
{
	DEBUG("playlist: stop\n");
	if (playerStop(fd) < 0)
		return -1;
	playerCloseAudio();
	playlist.queued = -1;
	playlist_state = PLAYLIST_STATE_STOP;
	playlist_noGoToNext = 0;
	if (playlist.random)
		randomizeOrder(0, playlist.length - 1);
	return 0;
}

static int playPlaylistOrderNumber(int fd, int orderNum)
{

	if (playerStop(fd) < 0)
		return -1;

	playlist_state = PLAYLIST_STATE_PLAY;
	playlist_noGoToNext = 0;
	playlist.queued = -1;
	playlist_queueError = 0;

	DEBUG("playlist: play %i:\"%s\"\n", orderNum,
	      getSongUrl(playlist.songs[playlist.order[orderNum]]));

	if (playerPlay(fd, (playlist.songs[playlist.order[orderNum]])) < 0) {
		stopPlaylist(fd);
		return -1;
	} 

	playlist.current = orderNum;

	return 0;
}

int playPlaylist(int fd, int song, int stopOnError)
{
	int i = song;

	clearPlayerError();

	if (song == -1) {
		if (playlist.length == 0)
			return 0;

		if (playlist_state == PLAYLIST_STATE_PLAY) {
			return playerSetPause(fd, 0);
		}
		if (playlist.current >= 0 && playlist.current < playlist.length) {
			i = playlist.current;
		} else {
			i = 0;
		}
	} else if (song < 0 || song >= playlist.length) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "song doesn't exist: \"%i\"", song);
		return -1;
	}

	if (playlist.random) {
		if (song == -1 && playlist_state == PLAYLIST_STATE_PLAY) {
			randomizeOrder(0, playlist.length - 1);
		} else {
			if (song >= 0)
				for (i = 0; song != playlist.order[i]; i++) ;
			if (playlist_state == PLAYLIST_STATE_STOP) {
				playlist.current = 0;
			}
			swapOrder(i, playlist.current);
			i = playlist.current;
		}
	}

	playlist_stopOnError = stopOnError;
	playlist_errorCount = 0;

	return playPlaylistOrderNumber(fd, i);
}

int playPlaylistById(int fd, int id, int stopOnError)
{
	if (id == -1) {
		return playPlaylist(fd, id, stopOnError);
	}

	checkSongId(id);

	return playPlaylist(fd, playlist.idToPosition[id], stopOnError);
}

static void syncCurrentPlayerDecodeMetadata(void)
{
	Song *songPlayer = playerCurrentDecodeSong();
	Song *song;
	int songNum;

	if (!songPlayer)
		return;

	if (playlist_state != PLAYLIST_STATE_PLAY)
		return;

	songNum = playlist.order[playlist.current];
	song = playlist.songs[songNum];

	if (song->type == SONG_TYPE_URL &&
	    0 == strcmp(getSongUrl(song), songPlayer->url) &&
	    !mpdTagsAreEqual(song->tag, songPlayer->tag)) {
		if (song->tag)
			freeMpdTag(song->tag);
		song->tag = mpdTagDup(songPlayer->tag);
		playlist.songMod[songNum] = playlist.version;
		incrPlaylistVersion();
	}
}

void syncPlayerAndPlaylist(void)
{
	if (playlist_state != PLAYLIST_STATE_PLAY)
		return;

	if (getPlayerState() == PLAYER_STATE_STOP)
		playPlaylistIfPlayerStopped();
	else
		syncPlaylistWithQueue(!playlist_queueError);

	syncCurrentPlayerDecodeMetadata();
}

static int currentSongInPlaylist(int fd)
{
	if (playlist_state != PLAYLIST_STATE_PLAY)
		return 0;

	playlist_stopOnError = 0;

	syncPlaylistWithQueue(0);

	if (playlist.current >= 0 && playlist.current < playlist.length) {
		return playPlaylistOrderNumber(fd, playlist.current);
	} else
		return stopPlaylist(fd);

	return 0;
}

int nextSongInPlaylist(int fd)
{
	if (playlist_state != PLAYLIST_STATE_PLAY)
		return 0;

	syncPlaylistWithQueue(0);

	playlist_stopOnError = 0;

	if (playlist.current < playlist.length - 1) {
		return playPlaylistOrderNumber(fd, playlist.current + 1);
	} else if (playlist.length && playlist.repeat) {
		if (playlist.random)
			randomizeOrder(0, playlist.length - 1);
		return playPlaylistOrderNumber(fd, 0);
	} else {
		incrPlaylistCurrent();
		return stopPlaylist(fd);
	}

	return 0;
}

void playPlaylistIfPlayerStopped(void)
{
	if (getPlayerState() == PLAYER_STATE_STOP) {
		int error = getPlayerError();

		if (error == PLAYER_ERROR_NOERROR)
			playlist_errorCount = 0;
		else
			playlist_errorCount++;

		if (playlist_state == PLAYLIST_STATE_PLAY
		    && ((playlist_stopOnError && error != PLAYER_ERROR_NOERROR)
			|| error == PLAYER_ERROR_AUDIO
			|| error == PLAYER_ERROR_SYSTEM
			|| playlist_errorCount >= playlist.length)) {
			stopPlaylist(STDERR_FILENO);
		} else if (playlist_noGoToNext)
			currentSongInPlaylist(STDERR_FILENO);
		else
			nextSongInPlaylist(STDERR_FILENO);
	}
}

int getPlaylistRepeatStatus(void)
{
	return playlist.repeat;
}

int getPlaylistRandomStatus(void)
{
	return playlist.random;
}

int setPlaylistRepeatStatus(int fd, int status)
{
	if (status != 0 && status != 1) {
		commandError(fd, ACK_ERROR_ARG, "\"%i\" is not 0 or 1", status);
		return -1;
	}

	if (playlist_state == PLAYLIST_STATE_PLAY) {
		if (playlist.repeat && !status && playlist.queued == 0) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
		}
	}

	playlist.repeat = status;

	return 0;
}

int moveSongInPlaylist(int fd, int from, int to)
{
	int i;
	Song *tmpSong;
	int tmpId;
	int queuedSong = -1;
	int currentSong = -1;

	if (from < 0 || from >= playlist.length) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "song doesn't exist: \"%i\"", from);
		return -1;
	}

	if (to < 0 || to >= playlist.length) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "song doesn't exist: \"%i\"", to);
		return -1;
	}

	if (playlist_state == PLAYLIST_STATE_PLAY) {
		if (playlist.queued >= 0) {
			queuedSong = playlist.order[playlist.queued];
		}
		currentSong = playlist.order[playlist.current];
		if (queuedSong == from || queuedSong == to
		    || currentSong == from || currentSong == to) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
		}
	}

	tmpSong = playlist.songs[from];
	tmpId = playlist.positionToId[from];
	/* move songs to one less in from->to */
	for (i = from; i < to; i++) {
		moveSongFromTo(i + 1, i);
	}
	/* move songs to one more in to->from */
	for (i = from; i > to; i--) {
		moveSongFromTo(i - 1, i);
	}
	/* put song at _to_ */
	playlist.idToPosition[tmpId] = to;
	playlist.positionToId[to] = tmpId;
	playlist.songs[to] = tmpSong;
	playlist.songMod[to] = playlist.version;
	/* now deal with order */
	if (playlist.random) {
		for (i = 0; i < playlist.length; i++) {
			if (playlist.order[i] > from && playlist.order[i] <= to) {
				playlist.order[i]--;
			} else if (playlist.order[i] < from &&
				   playlist.order[i] >= to) {
				playlist.order[i]++;
			} else if (from == playlist.order[i]) {
				playlist.order[i] = to;
			}
		}
	}
	else
	{
		if (playlist.current == from)
			playlist.current = to;
		else if (playlist.current > from && playlist.current <= to) {
			playlist.current--;
		} else if (playlist.current >= to && playlist.current < from) {
			playlist.current++;
		}

		/* this first if statement isn't necessary since the queue
		 * would have been cleared out if queued == from */
		if (playlist.queued == from)
			playlist.queued = to;
		else if (playlist.queued > from && playlist.queued <= to) {
			playlist.queued--;
		} else if (playlist.queued>= to && playlist.queued < from) {
			playlist.queued++;
		}
	}

	incrPlaylistVersion();

	return 0;
}

int moveSongInPlaylistById(int fd, int id1, int to)
{
	checkSongId(id1);

	return moveSongInPlaylist(fd, playlist.idToPosition[id1], to);
}

static void orderPlaylist(void)
{
	int i;

	if (playlist.current >= 0 && playlist.current < playlist.length) {
		playlist.current = playlist.order[playlist.current];
	}

	if (playlist_state == PLAYLIST_STATE_PLAY) {
		if (playlist.queued >= 0) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
		}
	}

	for (i = 0; i < playlist.length; i++) {
		playlist.order[i] = i;
	}

}

static void swapOrder(int a, int b)
{
	int bak = playlist.order[a];
	playlist.order[a] = playlist.order[b];
	playlist.order[b] = bak;
}

static void randomizeOrder(int start, int end)
{
	int i;
	int ri;

	DEBUG("playlist: randomize from %i to %i\n", start, end);

	if (playlist_state == PLAYLIST_STATE_PLAY) {
		if (playlist.queued >= start && playlist.queued <= end) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
		}
	}

	for (i = start; i <= end; i++) {
		ri = random() % (end - start + 1) + start;
		if (ri == playlist.current)
			playlist.current = i;
		else if (i == playlist.current)
			playlist.current = ri;
		swapOrder(i, ri);
	}

}

int setPlaylistRandomStatus(int fd, int status)
{
	int statusWas = playlist.random;

	if (status != 0 && status != 1) {
		commandError(fd, ACK_ERROR_ARG, "\"%i\" is not 0 or 1", status);
		return -1;
	}

	playlist.random = status;

	if (status != statusWas) {
		if (playlist.random) {
			/*if(playlist_state==PLAYLIST_STATE_PLAY) {
			   randomizeOrder(playlist.current+1,
			   playlist.length-1);
			   }
			   else */ randomizeOrder(0, playlist.length - 1);
			if (playlist.current >= 0 &&
			    playlist.current < playlist.length) {
				swapOrder(playlist.current, 0);
				playlist.current = 0;
			}
		} else
			orderPlaylist();
	}

	return 0;
}

int previousSongInPlaylist(int fd)
{
	static time_t lastTime;
	time_t diff = time(NULL) - lastTime;

	lastTime += diff;

	if (playlist_state != PLAYLIST_STATE_PLAY)
		return 0;

	syncPlaylistWithQueue(0);

	if (diff && getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) {
		return playPlaylistOrderNumber(fd, playlist.current);
	} else {
		if (playlist.current > 0) {
			return playPlaylistOrderNumber(fd,
						       playlist.current - 1);
		} else if (playlist.repeat) {
			return playPlaylistOrderNumber(fd, playlist.length - 1);
		} else {
			return playPlaylistOrderNumber(fd, playlist.current);
		}
	}

	return 0;
}

int shufflePlaylist(int fd)
{
	int i;
	int ri;

	if (playlist.length > 1) {
		if (playlist_state == PLAYLIST_STATE_PLAY) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
			/* put current playing song first */
			swapSongs(0, playlist.order[playlist.current]);
			if (playlist.random) {
				int j;
				for (j = 0; 0 != playlist.order[j]; j++) ;
				playlist.current = j;
			} else
				playlist.current = 0;
			i = 1;
		} else {
			i = 0;
			playlist.current = -1;
		}
		/* shuffle the rest of the list */
		for (; i < playlist.length; i++) {
			ri = random() % (playlist.length - 1) + 1;
			swapSongs(i, ri);
		}

		incrPlaylistVersion();
	}

	return 0;
}

int deletePlaylist(int fd, char *utf8file)
{
	char *file = utf8ToFsCharset(utf8file);
	char *rfile = xmalloc(strlen(file) + strlen(".") +
			     strlen(PLAYLIST_FILE_SUFFIX) + 1);
	char *actualFile;

	strcpy(rfile, file);
	strcat(rfile, ".");
	strcat(rfile, PLAYLIST_FILE_SUFFIX);

	if ((actualFile = rpp2app(rfile)) && isPlaylist(actualFile))
		free(rfile);
	else {
		free(rfile);
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "playlist \"%s\" not found", utf8file);
		return -1;
	}

	if (unlink(actualFile) < 0) {
		commandError(fd, ACK_ERROR_SYSTEM,
			     "problems deleting file");
		return -1;
	}

	return 0;
}

int savePlaylist(int fd, char *utf8file)
{
	FILE *fileP;
	int i;
	struct stat st;
	char *file;
	char *rfile;
	char *actualFile;

	if (strstr(utf8file, "/")) {
		commandError(fd, ACK_ERROR_ARG,
			     "cannot save \"%s\", saving playlists to "
			     "subdirectories is not supported", utf8file);
		return -1;
	}

	file = utf8ToFsCharset(utf8file);

	rfile = xmalloc(strlen(file) + strlen(".") +
		       strlen(PLAYLIST_FILE_SUFFIX) + 1);

	strcpy(rfile, file);
	strcat(rfile, ".");
	strcat(rfile, PLAYLIST_FILE_SUFFIX);

	actualFile = rpp2app(rfile);

	free(rfile);

	if (0 == stat(actualFile, &st)) {
		commandError(fd, ACK_ERROR_EXIST, "a file or directory already "
			     "exists with the name \"%s\"", utf8file);
		return -1;
	}

	while (!(fileP = fopen(actualFile, "w")) && errno == EINTR) ;
	if (fileP == NULL) {
		commandError(fd, ACK_ERROR_SYSTEM, "problems opening file");
		return -1;
	}

	for (i = 0; i < playlist.length; i++) {
		if (playlist_saveAbsolutePaths &&
		    playlist.songs[i]->type == SONG_TYPE_FILE) {
			fprintf(fileP, "%s\n",
				  rmp2amp(utf8ToFsCharset
					  ((getSongUrl(playlist.songs[i])))));
		} else {
			fprintf(fileP, "%s\n",
			        utf8ToFsCharset(getSongUrl(playlist.songs[i])));
		}
	}

	while (fclose(fileP) && errno == EINTR) ;

	return 0;
}

int getPlaylistCurrentSong(void)
{
	if (playlist.current >= 0 && playlist.current < playlist.length) {
		return playlist.order[playlist.current];
	}

	return -1;
}

unsigned long getPlaylistVersion(void)
{
	return playlist.version;
}

int getPlaylistLength(void)
{
	return playlist.length;
}

int seekSongInPlaylist(int fd, int song, float time)
{
	int i = song;

	if (song < 0 || song >= playlist.length) {
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "song doesn't exist: \"%i\"", song);
		return -1;
	}

	if (playlist.random)
		for (i = 0; song != playlist.order[i]; i++) ;

	clearPlayerError();
	playlist_stopOnError = 1;
	playlist_errorCount = 0;

	if (playlist_state == PLAYLIST_STATE_PLAY) {
		if (playlist.queued >= 0) {
			lockPlaylistInteraction();
			clearPlayerQueue();
			unlockPlaylistInteraction();
		}
	} else if (playPlaylistOrderNumber(fd, i) < 0)
		return -1;

	if (playlist.current != i) {
		if (playPlaylistOrderNumber(fd, i) < 0)
			return -1;
	}

	return playerSeek(fd, playlist.songs[playlist.order[i]], time);
}

int seekSongInPlaylistById(int fd, int id, float time)
{
	checkSongId(id);

	return seekSongInPlaylist(fd, playlist.idToPosition[id], time);
}

int getPlaylistSongId(int song)
{
	return playlist.positionToId[song];
}

static int PlaylistIterFunc(int fd, char *utf8file,
			    void (*IterFunc) (int fd, char *utf8_file,
					      char **errored_File))
{
	FILE *fileP;
	char s[MAXPATHLEN + 1];
	int slength = 0;
	char *temp = utf8ToFsCharset(utf8file);
	char *rfile = xmalloc(strlen(temp) + strlen(".") +
			     strlen(PLAYLIST_FILE_SUFFIX) + 1);
	char *actualFile;
	char *parent = parentPath(temp);
	int parentlen = strlen(parent);
	char *erroredFile = NULL;
	int tempInt;
	int commentCharFound = 0;

	strcpy(rfile, temp);
	strcat(rfile, ".");
	strcat(rfile, PLAYLIST_FILE_SUFFIX);

	if ((actualFile = rpp2app(rfile)) && isPlaylist(actualFile))
		free(rfile);
	else {
		free(rfile);
		commandError(fd, ACK_ERROR_NO_EXIST,
			     "playlist \"%s\" not found", utf8file);
		return -1;
	}

	while (!(fileP = fopen(actualFile, "r")) && errno == EINTR) ;
	if (fileP == NULL) {
		commandError(fd, ACK_ERROR_SYSTEM,
			     "problems opening file \"%s\"", utf8file);
		return -1;
	}

	while ((tempInt = fgetc(fileP)) != EOF) {
		s[slength] = tempInt;
		if (s[slength] == '\n' || s[slength] == '\0') {
			commentCharFound = 0;
			s[slength] = '\0';
			if (s[0] == PLAYLIST_COMMENT) {
				commentCharFound = 1;
			}
			if (strncmp(s, musicDir, strlen(musicDir)) == 0) {
				strcpy(s, &(s[strlen(musicDir)]));
			} else if (parentlen) {
				temp = xstrdup(s);
				memset(s, 0, MAXPATHLEN + 1);
				strcpy(s, parent);
				strncat(s, "/", MAXPATHLEN - parentlen);
				strncat(s, temp, MAXPATHLEN - parentlen - 1);
				if (strlen(s) >= MAXPATHLEN) {
					commandError(fd,
						     ACK_ERROR_PLAYLIST_LOAD,
						     "\"%s\" too long", temp);
					free(temp);
					while (fclose(fileP)
					       && errno == EINTR) ;
					if (erroredFile)
						free(erroredFile);
					return -1;
				}
				free(temp);
			}
			slength = 0;
			temp = fsCharsetToUtf8(s);
			if (!temp)
				continue;
			if (!commentCharFound) {
				/* using temp directly should be safe,
				 * for our current IterFunction set
				 * but just in case, we copy to s */
				strcpy(s, temp);
				IterFunc(fd, s, &erroredFile);
			}
		} else if (slength == MAXPATHLEN) {
			s[slength] = '\0';
			commandError(fd, ACK_ERROR_PLAYLIST_LOAD,
				     "line in \"%s\" is too long", utf8file);
			ERROR("line \"%s\" in playlist \"%s\" is too long\n",
			      s, utf8file);
			while (fclose(fileP) && errno == EINTR) ;
			if (erroredFile)
				free(erroredFile);
			return -1;
		} else if (s[slength] != '\r')
			slength++;
	}

	while (fclose(fileP) && errno == EINTR) ;

	if (erroredFile) {
		commandError(fd, ACK_ERROR_PLAYLIST_LOAD,
			     "can't add file \"%s\"", erroredFile);
		free(erroredFile);
		return -1;
	}

	return 0;
}

static void PlaylistInfoPrintInfo(int fd, char *utf8file, char **erroredfile)
{
	Song *song = getSongFromDB(utf8file);
	if (song) {
		printSongInfo(fd, song);
	} else {
		fdprintf(fd, SONG_FILE "%s\n", utf8file);
	}
}
static void PlaylistInfoPrint(int fd, char *utf8file, char **erroredfile)
{
	fdprintf(fd, SONG_FILE "%s\n", utf8file);
}

static void PlaylistLoadIterFunc(int fd, char *temp, char **erroredFile)
{
	if (!getSongFromDB(temp) && !isRemoteUrl(temp)) {

	} else if ((addToPlaylist(STDERR_FILENO, temp, 0)) < 0) {
		/* for windows compatibility, convert slashes */
		char *temp2 = xstrdup(temp);
		char *p = temp2;
		while (*p) {
			if (*p == '\\')
				*p = '/';
			p++;
		}
		if ((addToPlaylist(STDERR_FILENO, temp2, 0)) < 0) {
			if (!*erroredFile) {
				*erroredFile = xstrdup(temp);
			}
		}
		free(temp2);
	}
}

int PlaylistInfo(int fd, char *utf8file, int detail)
{
	if (detail) {
		return PlaylistIterFunc(fd, utf8file, PlaylistInfoPrintInfo);
	}
	return PlaylistIterFunc(fd, utf8file, PlaylistInfoPrint);
}

int loadPlaylist(int fd, char *utf8file)
{
	return PlaylistIterFunc(fd, utf8file, PlaylistLoadIterFunc);
}

void searchForSongsInPlaylist(int fd, int numItems, LocateTagItem * items)
{
	int i;
	char **originalNeedles = xmalloc(numItems * sizeof(char *));

	for (i = 0; i < numItems; i++) {
		originalNeedles[i] = items[i].needle;
		items[i].needle = strDupToUpper(originalNeedles[i]);
	}

	for (i = 0; i < playlist.length; i++) {
		if (strstrSearchTags(playlist.songs[i], numItems, items))
			printPlaylistSongInfo(fd, i);
	}

	for (i = 0; i < numItems; i++) {
		free(items[i].needle);
		items[i].needle = originalNeedles[i];
	}

	free(originalNeedles);
}

void findSongsInPlaylist(int fd, int numItems, LocateTagItem * items)
{
	int i;

	for (i = 0; i < playlist.length; i++) {
		if (tagItemsFoundAndMatches(playlist.songs[i], numItems, items))
			printPlaylistSongInfo(fd, i);
	}
}