Rev 45 | Rev 50 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* Copyright (C) 2015 by Andreas Theofilu <andreas@theosys.at>
*
* All rights reserved. No warranty, explicit or implicit, provided.
*
* NOTICE: All information contained herein is, and remains
* the property of Andreas Theofilu and his suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to Andreas Theofilu and its suppliers and
* may be covered by European and Foreign Patents, patents in process,
* and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Andreas Theofilu.
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <ctype.h>
#include <math.h>
#include <time.h>
#include <signal.h>
#include <syslog.h>
#include <iconv.h>
#include <errno.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <dirent.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sqlite3.h>
#include <id3tag.h>
#include "config.h"
#include "helplib.h"
#include "mdb.h"
#include "list.h"
#include "play.h"
#include "user.h"
#include "delete.h"
#include "search.h"
#define ID3_NR_OF_V1_GENRES 148
static const char *ID3_v1_genre_description[ID3_NR_OF_V1_GENRES] =
{
"Blues", //0
"Classic Rock", //1
"Country", //2
"Dance", //3
"Disco", //4
"Funk", //5
"Grunge", //6
"Hip-Hop", //7
"Jazz", //8
"Metal", //9
"New Age", //10
"Oldies", //11
"Other", //12
"Pop", //13
"R&B", //14
"Rap", //15
"Reggae", //16
"Rock", //17
"Techno", //18
"Industrial", //19
"Alternative", //20
"Ska", //21
"Death Metal", //22
"Pranks", //23
"Soundtrack", //24
"Euro-Techno", //25
"Ambient", //26
"Trip-Hop", //27
"Vocal", //28
"Jazz+Funk", //29
"Fusion", //30
"Trance", //31
"Classical", //32
"Instrumental", //33
"Acid", //34
"House", //35
"Game", //36
"Sound Clip", //37
"Gospel", //38
"Noise", //39
"AlternRock", //40
"Bass", //41
"Soul", //42
"Punk", //43
"Space", //44
"Meditative", //45
"Instrumental Pop", //46
"Instrumental Rock", //47
"Ethnic", //48
"Gothic", //49
"Darkwave", //50
"Techno-Industrial", //51
"Electronic", //52
"Pop-Folk", //53
"Eurodance", //54
"Dream", //55
"Southern Rock", //56
"Comedy", //57
"Cult", //58
"Gangsta", //59
"Top 40", //60
"Christian Rap", //61
"Pop/Funk", //62
"Jungle", //63
"Native American", //64
"Cabaret", //65
"New Wave", //66
"Psychedelic", //67
"Rave", //68
"Showtunes", //69
"Trailer", //70
"Lo-Fi", //71
"Tribal", //72
"Acid Punk", //73
"Acid Jazz", //74
"Polka", //75
"Retro", //76
"Musical", //77
"Rock & Roll", //78
"Hard Rock", //79
// following are winamp extentions
"Folk", //80
"Folk-Rock", //81
"National Folk", //82
"Swing", //83
"Fast Fusion", //84
"Bebob", //85
"Latin", //86
"Revival", //87
"Celtic", //88
"Bluegrass", //89
"Avantgarde", //90
"Gothic Rock", //91
"Progressive Rock", //92
"Psychedelic Rock", //93
"Symphonic Rock", //94
"Slow Rock", //95
"Big Band", //96
"Chorus", //97
"Easy Listening", //98
"Acoustic", //99
"Humour", //100
"Speech", //101
"Chanson", //102
"Opera", //103
"Chamber Music", //104
"Sonata", //105
"Symphony", //106
"Booty Bass", //107
"Primus", //108
"Porn Groove", //109
"Satire", //110
"Slow Jam", //111
"Club", //112
"Tango", //113
"Samba", //114
"Folklore", //115
"Ballad", //116
"Power Ballad", //117
"Rhythmic Soul", //118
"Freestyle", //119
"Duet", //120
"Punk Rock", //121
"Drum Solo", //122
"A capella", //123
"Euro-House", //124
"Dance Hall", //125
"Goa", //126
"Drum & Bass", //127
"Club-House", //128
"Hardcore", //129
"Terror", //130
"Indie", //131
"Britpop", //132
"Negerpunk", //133
"Polsk Punk", //134
"Beat", //135
"Christian Gangsta Rap", //136
"Heavy Metal", //137
"Black Metal", //138
"Crossover", //139
"Contemporary Christian",//140
"Christian Rock ", //141
"Merengue", //142
"Salsa", //143
"Thrash Metal", //144
"Anime", //145
"JPop", //146
"Synthpop" //147
};
static pthread_mutex_t fastmutex_proc = PTHREAD_MUTEX_INITIALIZER;
int musicFilter(const struct dirent *dir);
int grabMusicFiles(int s1, char *dir);
void evaluateMusicFile(char *file);
int cleanArchieve(int s1, char *fname);
char *getID3_Field(union id3_field *field, enum id3_field_textencoding te, char *ret, int len);
int checkID3FieldType(enum id3_field_type ft);
/* Global variables */
int file_found = 0;
char cmd_error[512];
char cmd_message[512];
int currentPage = PAGE_NONE;
int grabFilesCount; /* Temporary used to count the scanned files */
struct ST_PlayPars _playPars;
static pthread_t pthr_play;
/*
* This is called from the main listening thread as a thread of its own.
* The function gets the commands from the client, work with them and give
* back the result.
*/
void *processCommands(void *pV_data)
{
char ch, buf[512];
int i, s1, s;
struct SOCKETS *soc;
soc = (struct SOCKETS *)pV_data;
s1 = soc->newfd;
s = soc->sockfd;
i = 0;
/* Report the actual status to the client */
/* pthread_mutex_lock (&fastmutex_proc); */
sprintf(buf, "# MDB v%s\n", VERSION);
strcat (buf, "# (C) Copyright 2015 by Andreas Theofilu <andreas@theosys.at>\n");
strcat (buf, "# All rights reserved. No warranty, explicit or implicit, provided.\n");
write (s1, buf, strlen(buf));
if (userchain != NULL)
{
sprintf(buf, "USER:%s;", userchain->uname);
write (s1, buf, strlen(buf));
}
strcpy (buf, "PAGE:");
switch (currentPage)
{
case PAGE_NONE: strcat (buf, "NONE;"); break;
case PAGE_TITLE: strcat (buf, "TITLE;"); break;
case PAGE_ARTIST: strcat (buf, "ARTIST;"); break;
case PAGE_ALBUM: strcat (buf, "ALBUM;"); break;
case PAGE_GENRE: strcat (buf, "GENRE;"); break;
case PAGE_QUEUE: strcat (buf, "QUEUE;"); break;
case PAGE_PLAYLIST: strcat (buf, "PLAYLIST;"); break;
}
write (s1, buf, strlen(buf));
sprintf(buf, "REPEAT:%s;", playerRepeat ? "TRUE" : "FALSE");
write (s1, buf, strlen(buf));
sprintf(buf, "RANDOM:%s;", playerRandom ? "TRUE" : "FALSE");
write (s1, buf, strlen(buf));
/* In case the player is playing, we send the data of the
* current song */
if (playerActive)
{
char buffer[8192];
char *title, *artist, *album, *genre;
title = urlencode(playCurrent.title);
artist = urlencode(playCurrent.artist);
album = urlencode(playCurrent.album);
genre = urlencode(playCurrent.genre);
sprintf(buffer, "PLAYING:%d:%s:%s:%s:%s:%s;", playCurrent.id, title, artist, album, genre, playCurrent.cover);
write (s1, buffer, strlen(buffer));
if (title != NULL) free(title);
if (artist != NULL) free(artist);
if (album != NULL) free(album);
if (genre != NULL) free(genre);
}
/* Signal client, that we are finished the initial report */
strcpy (buf, "DONE;");
write (s1, buf, strlen(buf));
/* pthread_mutex_unlock(&fastmutex_proc); */
memset(buf, 0, sizeof(buf));
memset(cmd_message, 0, sizeof(cmd_message));
while (read(s1,&ch,1) > 0)
{
if (i < (int)(sizeof(buf) - 1))
{
buf[i] = ch;
if (ch == ';' || ch == 0x0d)
{
int pstat;
/* pthread_mutex_lock (&fastmutex_proc); */
if (!strncmp(buf, "quit", 4))
break;
if ((pstat = parseCommand(s1, buf)) == FALSE)
{
char hv0[128];
char *p = urlencode(cmd_error);
if (p != NULL)
{
sprintf(&hv0[0], "INVALID COMMAND: %s: %s;", buf, p);
free(p);
}
else
sprintf(&hv0[0], "INVALID COMMAND: %s;", buf);
write(s1, hv0, strlen(hv0));
}
else if (pstat == 2)
write(s1, "NAK;", 4);
else
{
if (strlen(cmd_message) > 0)
write(s1, cmd_message, strlen(cmd_message));
else
write(s1, "OK;", 3);
}
memset(&buf[0], 0, sizeof(buf));
memset(cmd_message, 0, sizeof(cmd_message));
i = 0;
/* pthread_mutex_unlock(&fastmutex_proc); */
continue;
}
}
i++;
}
close(s1);
return NULL;
}
/*
* This function parses the commands given by a network client.
*/
int parseCommand(int s1, char *cmd)
{
char bef[32],par[1024], *p;
char hv0[256];
int i;
memset(bef, 0, sizeof(bef));
memset(par, 0, sizeof(par));
memset(cmd_error, 0, sizeof(cmd_error));
if ((p = strchr(cmd, ':')) != NULL) /* do we have a parameter? */
{
strncpy(bef, cmd, p - cmd);
strncpy(par, p+1, strlen(p) - 2); /* Cut off the trailing semi colon */
}
else
strncpy(bef, cmd, strlen(cmd) - 1);
cmd_error[0] = 0;
cmd_message[0] = 0;
if (!strcasecmp(bef, "RESCAN")) /* Rescan all directories */
{
int fd;
char fname[512];
if (access(configs.Pathfile, R_OK))
{
syslog(LOG_WARNING, "Error accessing file %s", configs.Pathfile);
sprintf(cmd_error, "Error accessing file >%s<", configs.Pathfile);
return FALSE;
}
if ((fd = open(configs.Pathfile, O_RDONLY)) < 0)
{
syslog(LOG_WARNING, "Error opening file %s: %s", configs.Pathfile, strerror(errno));
sprintf(cmd_error, "Error opening file >%s<", configs.Pathfile);
return FALSE;
}
i = 0;
/* Delete the database entries before we start to scan.
* This makes shure, that there is no double entry.
*/
/* strcpy(fname, configs.home);
strcat(fname, MUSICDB);
if (!access(fname, R_OK | W_OK))
cleanArchieve(s1, fname);
*/
grabFilesCount = 0;
/* Scan all directories in the config file recursievly. */
while (readLine(fd, &hv0[0], sizeof(hv0)) != NULL)
{
grabMusicFiles(s1, hv0);
i++;
}
sprintf(cmd_message, "SCAN:%d:%d;", i, grabFilesCount);
close(fd);
}
/*
* Syntax: LIST:<type>:<start>:<length>;
* <type> TITLE | ARTIST | ALBUM | GENRE | QUEUE | PLAYLIST | USERS
* <start> The line to start from
* <length> The number of lines to report
*
* Returns: LINE:<type>:<id>:<line>:<title>:<artist>:<album>:<genre>:<cover>;
* <id> The unique ID of the file. Needed to play the file!
* <line> The line number counting from 1
*
* In case the type is USERS, the following is returned:
* USERS:<id>:<line>:<name>;
* <name> The name of the user
*/
if (!strcasecmp(bef, "LIST")) /* List content */
{
char p_type[16];
int start, length;
memset(p_type, 0, sizeof(p_type));
remove_string(par, ":", &hv0[0]);
start = strlen(hv0);
hv0[start-1] = 0;
strncpy(p_type, hv0, sizeof(p_type)-1);
start = atoi(par);
remove_string(par, ":", &hv0[0]);
length = atoi(par);
return listSongs(s1, p_type, start, length);
}
/*
* Syntax: PLIST:<user>:<playlist>:<start>:<length>;
* <user> The name of the user.
* <playlist> The name of the playlist
* <start> The line to start from
* <length> The number of lines to report
*
* Returns: LINE:<type>:<id>:<line>:<title>:<artist>:<album>:<genre>:<cover>;
* <id> The unique ID of the file. Needed to play the file!
* <line> The line number counting from 1
*/
if (!strcasecmp(bef, "PLIST")) /* List content */
{
char user[64], playlist[64], *t;
int start, length, x;
memset(user, 0, sizeof(user));
memset(playlist, 0, sizeof(playlist));
x = 0;
t = strtok (par, ":");
while (t)
{
switch(x)
{
case 0: strncpy (user, t, sizeof(user)-1); break;
case 1: strncpy (playlist, t, sizeof(playlist)-1); break;
case 2: start = atoi(t); break;
case 3: length = atoi(t); break;
}
x++;
t = strtok (NULL, ":");
}
t = urldecode(user);
if (t)
{
strncpy(user, t, sizeof(user)-1);
free(t);
}
t = urldecode(playlist);
if (t)
{
strncpy(playlist, t, sizeof(playlist)-1);
free(t);
}
return listUserPlaylist(s1, user, playlist, start, length);
}
/*
* Syntax: LISTFOLDER:<type>:<name>:<start>:<length>;
* <type> TITLE | ARTIST | ALBUM | GENRE
* <start> The line to start from
* <length> The number of lines to report
*
* Returns: LINE:<type>:<id>:<line>:<title>:<artist>:<album>:<genre>:<cover>;
* <id> The unique ID of the file. Needed to play the file!
* <line> The line number counting from 1
*/
if (!strcasecmp(bef, "LISTFOLDER")) /* List the content of a folder */
{
char p_type[64], name[256], *t, *type, *nm;
int x, start, length;
x = 0;
t = strtok(par, ":");
while (t)
{
switch(x)
{
case 0: strncpy(p_type, t, sizeof(p_type)); break;
case 1: strncpy(name, t, sizeof(name)); break;
case 2: start = atoi(t); break;
case 3: length = atoi(t); break;
}
x++;
t = strtok(NULL, ":");
}
type = urldecode(p_type);
nm = urldecode(name);
if (type)
{
strncpy(p_type, type, sizeof(p_type)-1);
free(type);
}
if (nm)
{
strncpy(name, nm, sizeof(name)-1);
free(nm);
}
return listFolderContent(s1, p_type, name, start, length);
}
/*
* Syntax: FOLDER:<type>:<start>:<length>;
* <type> TITLE | ARTIST | ALBUM | GENRE
* <start> The line to start from
* <length> The number of lines to report
*
* Returns: FOLDER:<type>:<id>:<line>:<content>;
* <id> The unique ID of the file. Needed to play the file!
* <line> The line number counting from 1
* <content> The name of the folder
*/
if (!strcasecmp(bef, "FOLDER")) /* List folders */
{
char p_type[16];
int start, length;
memset(p_type, 0, sizeof(p_type));
remove_string(par, ":", &hv0[0]);
start = strlen(hv0);
hv0[start-1] = 0;
strncpy(p_type, hv0, sizeof(p_type)-1);
start = atoi(par);
remove_string(par, ":", &hv0[0]);
length = atoi(par);
return listFolders(s1, p_type, start, length);
}
/*
* This command moves one or more files into the queue.
* If the queue already contains some files, the files are appended to
* the end of the queue. The command makes sure, that there are no
* duplicate entries in the queue.
* If the name of a folder is omitted, all files of the specified types
* are appended.
* If a folder name was specified, only the files of the folder are
* appended.
*
* Syntax: ADD:<type>:<folder>;
* <type> ID | TITLE | ARTIST | ALBUM | GENRE | PLAYLIST
* <folder> The name of a folder. This is optional.
*/
if (!strcasecmp(bef, "ADD"))
{
int x;
char *t, p_type[32], folder[64], *type, *fld;
memset(p_type, 0, sizeof(p_type));
memset(folder, 0, sizeof(folder));
x = 0;
t = strtok(par, ":");
while (t)
{
switch (x)
{
case 0: strncpy(p_type, t, sizeof(p_type)-1); break;
case 1: strncpy(folder, t, sizeof(folder)-1); break;
}
x++;
t = strtok(NULL, ":");
}
type = urldecode(p_type);
fld = urldecode(folder);
if (type)
{
strncpy(p_type, type, sizeof(p_type));
free(type);
}
if (fld)
{
strncpy(folder, fld, sizeof(folder));
free(fld);
}
if (strlen(p_type) && strlen(folder))
appendToQueue(s1, p_type, folder);
else
{
strcpy(cmd_message, "ERROR:ADD:Missing type or folder name;");
return FALSE;
}
}
/*
* This command moves one or more files into the queue and starts to play
* them.
* If the player is already playing, the file(s) are appended to the queue.
* If a play request for the queue is detected and the ID is 0, then the
* first or, if random is selected a random entry of the que is played. If
* the ID is grater than 0, the wanted ID is played even if random is
* selected.
*
* Syntax: PLAY:<type>:<what>;
* <type> ID | TITLE | ARTIST | ALBUM | GENRE | PLAYLIST | QUEUE
* <what> <id> or name of folder or name of playlist
*
* Returns: PLAYING:<id>:<title>:<artist>:<album>:<genre>;
* POSITION:<time>:<time left:<total>;
*/
if (!strcasecmp(bef, "PLAY")) /* Plays one or more files */
{
char p_type[16];
char what[256];
int i;
memset(p_type, 0, sizeof(p_type));
memset(hv0, 0, sizeof(hv0));
remove_string(par, ":", &hv0[0]);
i = strlen(hv0);
if (i > 0)
{
hv0[i-1] = 0;
strncpy(p_type, hv0, sizeof(p_type) - 1);
}
else
strcpy(p_type, "QUEUE");
if (strcasecmp(p_type, "ID")) /* If the type is not "ID" then */
{ /* decode the text */
char *w;
w = urldecode(par);
if (w)
{
strncpy (what, w, sizeof(what));
i = sizeof(what) - 1;
what[i] = 0;
free (w);
}
else
strncpy(what, par, sizeof(what));
}
else if (strlen(par))
strncpy(what, par, sizeof(what));
else
strcpy(what, "0");
if (playerActive && playStatus == PLAY_STATUS_PAUSE)
nextCommand = PLAY_PLAY;
else if (playerActive)
appendToQueue(s1, p_type, what);
else
{
_playPars.s1 = s1;
strcpy(_playPars.type, p_type);
strcpy(_playPars.what, what);
/* start a new thread to play the file(s) */
if (pthread_create(&pthr_play, NULL, pthr_playfile, (void *)&_playPars) != 0)
{
syslog (LOG_DAEMON,"Create of thread \"pthr_play\" failed!");
strcpy(cmd_error, "Error playing a file!");
return FALSE;
}
}
}
if (!strcasecmp(bef, "STOP") && playerActive) /* Stop the currently playing file */
nextCommand = PLAY_STOP;
if (!strcasecmp(bef, "PAUSE") && playerActive) /* Pause the currently playing file */
nextCommand = PLAY_PAUSE;
if (!strcasecmp(bef, "PLAYPAUSE")) /* Play/Pause the currently playing file, */
{ /* or play one in the queue if there are any. */
if (playerActive && (playStatus == PLAY_STATUS_PAUSE || playStatus == PLAY_STATUS_PLAY))
nextCommand = PLAY_PLAYPAUSE;
else if (playerActive && playStatus == PLAY_STATUS_STOP)
nextCommand = PLAY_PLAY;
else if (!playerActive)
{
_playPars.s1 = s1;
strcpy(_playPars.type, "QUEUE"); /* Select the queue to play. */
strcpy(_playPars.what, "0"); /* Play the first or a random file, if random is selected, in the queue. */
/* start a new thread to play the file(s) */
if (pthread_create(&pthr_play, NULL, pthr_playfile, (void *)&_playPars) != 0)
{
syslog (LOG_DAEMON,"Create of thread \"pthr_play\" failed!");
strcpy(cmd_error, "Error playing a file!");
return FALSE;
}
}
}
if (!strcasecmp(bef, "FORWARD") && playerActive) /* Fast forward */
nextCommand = PLAY_FWD;
if (!strcasecmp(bef, "REWIND") && playerActive) /* Fast rewind */
nextCommand = PLAY_REW;
if (!strcasecmp(bef, "SKIPFWD") && playerActive) /* Skip to next file in queue */
nextCommand = PLAY_SKIP_FWD;
if (!strcasecmp(bef, "SKIPREW") && playerActive) /* Skip to previous file in queue */
nextCommand = PLAY_SKIP_REW;
if (!strcasecmp(bef, "RANDOM")) /* Toggle random play */
{
playerRandom = !playerRandom;
sprintf(cmd_message, "RANDOM:%s;", playerRandom ? "TRUE" : "FALSE");
}
if (!strcasecmp(bef, "REPEAT")) /* Toggle repeat */
{
playerRepeat = !playerRepeat;
sprintf(cmd_message, "REPEAT:%s;", playerRepeat ? "TRUE" : "FALSE");
}
/*
* This command loads a user. If the user doesn't exist it creates
* the user. In this case the name of a playlist is needed.
* If the user already exist, the name of a playlist is optional.
*
* If this command is called without the name of a playlist and the
* user doesn't exist, an error occurs.
*
* This command should only be called when a new playlist is to be
* saved. After this command the command "SAVEQUEUE" should be called.
*
* Syntax: USER:<name>:[<playlist>];
* <name> the name of the user.
* <playlist> the name of the playlist; This is optional.
*/
if (!strcasecmp(bef, "USER")) /* In case the user doesn't exist, create a new one. Activate this user */
{
char user[64], playlist[64], *t;
int x;
memset(user, 0, sizeof(user));
memset(playlist, 0, sizeof(playlist));
x = 0;
t = strtok (par, ":");
while (t)
{
char *hv;
switch(x)
{
case 0:
strncpy (user, t, sizeof(user));
user[63] = 0;
hv = urldecode(user);
if (hv != NULL)
{
strcpy(user, hv);
free(hv);
}
break;
case 1:
strncpy (playlist, t, sizeof(playlist));
playlist[63] = 0;
hv = urldecode(playlist);
if (hv != NULL)
{
strcpy(playlist, hv);
free(hv);
}
break;
}
x++;
t = strtok(NULL, ":");
}
if (strlen(user) && strlen(playlist))
{
if (!createUser(s1, user, playlist))
return FALSE;
return selectUser(s1, user);
}
else if (strlen(user))
return selectUser(s1, user);
else
{
strcpy (cmd_error, "Missing the username and the name of the playlist");
return FALSE;
}
}
/*
* Syntax: SAVEQUEUE:<user>:<playlist>;
* <user> The name of the user. If this is empty, the actual
* user is taken, if any. If there's no user selected,
* an error occurs.
* <playlist> The name of the playlist. If the playlist already
* exist, it'll be overwritten.
*
* Return: TRANSFERED:<num>;
* <num> The number of entries transfered.
*/
if (!strcasecmp(bef, "SAVEQUEUE")) /* Saves the content of the queue into a playlist */
{
char user[64], playlist[64];
char *t;
int x;
if (!queueTotal)
{
readQueue();
if (!queueTotal)
{
strcpy(cmd_error, "Queue is empty");
return FALSE;
}
}
x = 0;
t = strtok(par, ":");
while (t)
{
char *hv;
switch(x)
{
case 0:
strncpy (user, t, sizeof(user));
user[63] = 0;
hv = urldecode(user);
if (hv != NULL)
{
strcpy(user, hv);
free(hv);
}
break;
case 1:
strncpy (playlist, t, sizeof(playlist));
playlist[63] = 0;
hv = urldecode(playlist);
if (hv != NULL)
{
strcpy(playlist, hv);
free(hv);
}
break;
}
x++;
t = strtok(NULL, ":");
}
if (!strlen(user) && userchain != NULL)
strcpy(user, userchain->uname);
else if (!selectUser(s1, user) && strlen(user) && strlen(playlist))
{
if (!createUser(s1, user, playlist))
return FALSE;
return QueueToPlaylist(s1, user, playlist);
}
else if (!selectUser(s1, user))
{
strcpy (cmd_error, "No or invalid user");
return FALSE;
}
if (!strlen(playlist))
{
strcpy (cmd_error, "No or invalid playlist");
return FALSE;
}
if (!QueueToPlaylist(s1, user, playlist))
return FALSE;
}
/*
* Syntax: DELETE:<type>:<id>;
* <type> PTITLE | QUEUE | PLAYLIST
* PTITLE: Delete one title from a playlist
* QUEUE: If the given ID is -1, then the whole
* queue is deleted. Otherwise only a title
* in the queue.
* PLAYLIST Delete a whole playlist from a user
*
* <id> -1 = Delete whole QUEUE or PLAYLIST
* >= 0 Delete only a particular entry
*/
if (!strcasecmp(bef, "DELETE"))
{
char p_type[32], *t;
int id, x;
x = 0;
t = strtok(par, ":");
while (t)
{
switch (x)
{
case 0:
strncpy(p_type, t, sizeof(p_type));
p_type[31] = 0;
break;
case 1: id = atoi(t); break;
}
x++;
t = strtok(NULL, ":");
}
if (!strcasecmp(p_type, "QUEUE"))
return deleteQueue(s1, id);
else if (!strcasecmp(p_type, "PTITLE"))
return deletePlaylistEntry(s1, id);
else if (!strcasecmp(p_type, "PLAYLIST"))
return deletePlaylist(s1, id);
else
return FALSE;
}
/*
* Syntax: SEARCH:<type>:<expression>:<start>:<lines>;
* <type> TITLE | ARTIST | ALBUM | GENRE
* <expression> This is what to search for
* <start> the data to start to
* <lines> number of lines to return
*
* Return: SEARCH:<type>:<id>:<line>:<title>:<artist>:<album>:<genre>;
*/
if (!strcasecmp(bef, "SEARCH"))
{
int start, lines, x;
char *t, p_type[32], expr[128];
memset(p_type, 0, sizeof(p_type));
memset(expr, 0, sizeof(expr));
start = lines = 0;
x = 0;
t = strtok (par, ":");
while (t)
{
char *hv;
switch(x)
{
case 0: strncpy(p_type, t, sizeof(p_type)-1); break;
case 1:
strncpy(expr, t, sizeof(expr)-1);
hv = urldecode(expr);
if (hv != NULL)
{
strncpy(expr, hv, sizeof(expr) - 1);
free(hv);
}
break;
case 2: start = atoi(t); break;
case 3: lines = atoi(t); break;
}
x++;
t = strtok (NULL, ":");
}
if (x != 4 || !strlen(p_type) || !strlen(expr) || !lines || !start)
{
strcpy (cmd_error, "Missing one ore more parameters");
return FALSE;
}
return searchTerm(s1, p_type, expr, start, lines);
}
/*
* This command reread the config file.
*/
if (!strcasecmp(bef, "RESET"))
{
if (playerActive)
nextCommand = PLAY_STOP;
readConf();
}
return TRUE; /* cmd was OK */
}
/*
* This function opens the database and deletes the whole archive of scanned
* MP3 files.
* This is called when the command "RESCAN" was detected. When this function
* is finished, the database is ready for new data.
*/
int cleanArchieve(int s1, char *fname)
{
char query[1024];
sqlite3 *db;
char *zErrMsg = 0;
int rc;
rc = sqlite3_open(fname, &db);
if (rc)
{
syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
strcpy(query, "ERROR:MDB:Error opening database;");
write (s1, query, strlen(query));
return FALSE;
}
strcpy (query, "delete from \"main\".\"playlists\"");
if ((rc = sqlite3_exec(db, query, NULL, NULL, &zErrMsg)) != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
strcpy(query, "ERROR:MDB:SQL error;");
write (s1, query, strlen(query));
return FALSE;
}
strcpy (query, "delete from \"main\".\"musicdb\"");
if ((rc = sqlite3_exec(db, query, NULL, NULL, &zErrMsg)) != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
strcpy(query, "ERROR:MDB:SQL error;");
write (s1, query, strlen(query));
return FALSE;
}
sqlite3_close(db);
return TRUE;
}
/*
* Callback function of directory scan.
* This function desides whether a directory entry will be treated as a
* music file or not.
*/
int musicFilter(const struct dirent *dir)
{
if (dir == NULL)
return 0;
if (dir->d_type == DT_REG)
{
char *p = getFileExtension(dir->d_name);
if (p != NULL && (strcasecmp(p, "mp3") == 0 || strcasecmp(p, "flac") == 0))
return 1;
}
else if (dir->d_type == DT_DIR)
{
if (strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0)
return 1;
}
return 0;
}
/*
* This function scans a directory for music files. If it finds some file name
* who is a possible music file, it gives it to a function to evaluate and
* store it.
*/
int grabMusicFiles(int s1, char *dir)
{
struct dirent **namelist;
int n;
if (dir == NULL || strlen(dir) == 0)
return FALSE;
n = scandir(dir, &namelist, musicFilter, alphasort);
if (n < 0)
{
syslog(LOG_DAEMON, "Error scanning directory %s: %s", dir, strerror(errno));
return FALSE;
}
else
{
while(n--)
{
char hv0[1024];
int len;
strcpy(hv0, dir);
strcat(hv0, "/");
len = sizeof(hv0) - strlen(hv0) - 1;
if (len > 0)
strncat(hv0, namelist[n]->d_name, len);
else
{
free(namelist[n]);
continue;
}
if (namelist[n]->d_type == DT_DIR)
grabMusicFiles(s1, hv0);
else
{
evaluateMusicFile(hv0);
if (!(grabFilesCount % 100))
{
sprintf(hv0, "SCANNING:%d;", grabFilesCount);
write (s1, hv0, strlen(hv0));
}
}
free(namelist[n]);
}
free(namelist);
}
return TRUE;
}
void evaluateMusicFile(char *file)
{
sqlite3 *db;
sqlite3_stmt *res;
char *zErrMsg = 0;
int rc, fType, update, id;
char fname[256], hv0[64];
char query[8192];
struct id3_tag *id3;
struct id3_file *ifile;
struct id3_frame *frame;
union id3_field *field;
id3_byte_t const *bt;
char id3_title[256];
char id3_singer[256];
char id3_album[256];
char id3_genre[256];
char id3_cover[256];
char *title, *singer, *album, *ext;
enum id3_field_type enc;
if (access(file, R_OK))
return;
bt = NULL;
strcpy(fname, configs.home);
strcat(fname, MUSICDB);
if (!access(fname, R_OK | W_OK))
file_found = TRUE;
else /* create the directory, if it doesn't exitst */
{
if (access(configs.home, R_OK | W_OK))
{
if (mkdir(configs.home, 0775) != 0)
{
syslog(LOG_WARNING, "Error creating directory %s: %s", configs.home, strerror(errno));
return;
}
else
syslog(LOG_INFO, "Directory %s was created.", configs.home);
}
sprintf(query, "%s/Covers", configs.home);
if (access(query, R_OK | W_OK))
{
if (mkdir(query, 0775) != 0)
syslog(LOG_WARNING, "Error creating directory %s: %s", query, strerror(errno));
else
syslog(LOG_INFO, "Directory %s was created.", query);
}
}
rc = sqlite3_open(fname, &db);
if (rc)
{
syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
return;
}
if (!file_found)
{
/* id autoincrement unique index
* path path to file containing an MP3 or FLAC file
* type 1 = MP3, 2 = FLAC
* title title of song
* interpret singer
* album the album the is from
* genre genre of the song
* cover name of file containing the cover
*/
strcpy(query, "create table \"main\".\"musicdb\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,");
strcat(query, "\"path\" TEXT NOT NULL, \"type\" INTEGER, \"title\" TEXT, \"interpret\" TEXT,");
strcat(query, "\"album\" TEXT, \"genre\" TEXT, \"cover\" TEXT)");
rc = sqlite3_exec(db, query, NULL, NULL, &zErrMsg);
if (rc != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
return;
}
else
syslog(LOG_INFO, "Database \"musicdb\" was successfully created.");
strcpy(query, "CREATE TABLE \"main\".\"users\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT,");
strcat(query, "\"uname\" TEXT NOT NULL, \"playlist\" TEXT)");
rc = sqlite3_exec(db, query, NULL, NULL, &zErrMsg);
if (rc != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
return;
}
else
syslog(LOG_INFO, "Database \"users\" was successfully created.");
strcpy (query, "CREATE TABLE \"main\".\"playlists\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,");
strcat (query, "\"userid\" INTEGER NOT NULL, \"musicid\" INTEGER NOT NULL)");
rc = sqlite3_exec(db, query, NULL, NULL, &zErrMsg);
if (rc != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
return;
}
else
syslog(LOG_INFO, "Database \"users\" was successfully created.");
}
/* Make sure, the file is not already in the database */
sprintf(query, "select id, path, cover from musicdb where path = \"%s\"", file);
if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
{
syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
sqlite3_close(db);
return;
}
rc = sqlite3_step(res);
id3_cover[0] = 0;
if (rc == SQLITE_ROW) /* If we've a row, the path exists and we'll not add it again */
{ /* Instead we'll update the information */
update = 1;
id = sqlite3_column_int(res, 0);
if ((ext = (char *)sqlite3_column_text(res, 2)) == NULL)
{
syslog(LOG_WARNING, "Error getting path of picture from database!");
id3_cover[0] = 0;
}
else
strncpy(&id3_cover[0], ext, sizeof(id3_cover));
}
else
update = 0;
sqlite3_finalize(res);
/* Check the file extension and decide whether it's an MP3 or FLAC file. */
ext = getFileExtension(file);
if (ext != NULL)
{
if (strcasecmp(ext, "flac") == 0)
fType = FILE_TYPE_FLAC;
else
fType = FILE_TYPE_MP3;
}
else
fType = FILE_TYPE_MP3;
/* Open the file and get the ID3 tag. */
if ((ifile = id3_file_open(file, ID3_FILE_MODE_READONLY)) == NULL)
{
syslog(LOG_WARNING, "Error opening file %s!", file);
sqlite3_close(db);
return;
}
if ((id3 = id3_file_tag(ifile)) == NULL)
{
syslog(LOG_WARNING, "Error initializing MP3 file %s", file);
sqlite3_close(db);
return;
}
memset(&id3_title, 0, sizeof(id3_title));
memset(&id3_singer, 0, sizeof(id3_singer));
memset(&id3_album, 0, sizeof(id3_album));
memset(&id3_genre, 0, sizeof(id3_genre));
if (!strlen(id3_cover))
memset(&id3_cover, 0, sizeof(id3_cover));
/* Get the required frames */
if ((frame = id3_tag_findframe(id3, ID3_FRAME_TITLE, 0)) != NULL)
{
int i;
for (i = 0; i < (int)frame->nfields; i++)
{
field = id3_frame_field(frame, i);
if (field->type == ID3_FIELD_TYPE_TEXTENCODING)
enc = id3_field_gettextencoding(field);
if (field && checkID3FieldType(field->type))
break;
}
getID3_Field(field, enc, &id3_title[0], sizeof(id3_title));
char_replace(id3_title, '"', '`');
char_replace(id3_title, '\\', ' ');
}
if ((frame = id3_tag_findframe(id3, ID3_FRAME_ARTIST, 0)) != NULL)
{
int i;
for (i = 0; i < (int)frame->nfields; i++)
{
field = id3_frame_field(frame, i);
if (field->type == ID3_FIELD_TYPE_TEXTENCODING)
enc = id3_field_gettextencoding(field);
if (field && checkID3FieldType(field->type))
break;
}
getID3_Field(field, enc, &id3_singer[0], sizeof(id3_singer));
char_replace(id3_singer, '"', '`');
char_replace(id3_singer, '\\', ' ');
}
if ((frame = id3_tag_findframe(id3, ID3_FRAME_ALBUM, 0)) != NULL)
{
int i;
for (i = 0; i < (int)frame->nfields; i++)
{
field = id3_frame_field(frame, i);
if (field->type == ID3_FIELD_TYPE_TEXTENCODING)
enc = id3_field_gettextencoding(field);
if (field && checkID3FieldType(field->type))
break;
}
getID3_Field(field, enc, &id3_album[0], sizeof(id3_album));
char_replace(id3_album, '"', '`');
char_replace(id3_album, '\\', ' ');
}
if ((frame = id3_tag_findframe(id3, "APIC", 0)) != NULL)
{
char fpname[512], uid[128], hv0[256];
struct id3_frame *fpframe;
int i, fd, np;
long unsigned len;
memset(uid, 0, sizeof(uid));
strcpy(fpname, configs.home);
strcat(fpname, "/Covers");
if (access(fpname, R_OK | W_OK | X_OK))
mkdir(fpname, 0775);
if (!strlen(id3_cover))
{
char *md5;
if ((md5 = str2hash(file, (int)strlen(file))) != NULL)
{
sprintf(uid, "Picture-%s", md5);
free(md5);
}
else
sprintf(uid, "Picture-%ld", random());
}
strcat(fpname, "/");
len = 0;
np = 0; /* 1 = a new picture was found */
bt = NULL;
/* Get the picture and store it into a file */
for (i = 0; i < (int)frame->nfields; i++)
{
if ((field = id3_frame_field(frame, i)) != NULL)
{
id3_latin1_t const *lt1;
switch (field->type)
{
case ID3_FIELD_TYPE_LATIN1: // MIME type
if ((lt1 = id3_field_getlatin1(field)) != NULL)
strncpy (&hv0[0], (char *)lt1, sizeof(hv0)-1);
break;
case ID3_FIELD_TYPE_LATIN1FULL:
if ((lt1 = id3_field_getfulllatin1(field)) != NULL)
strncpy (&hv0[0], (char *)lt1, sizeof(hv0)-1);
break;
case ID3_FIELD_TYPE_BINARYDATA: // The picture
bt = id3_field_getbinarydata(field, &len);
np = 1;
break;
}
}
}
if (!strlen(id3_cover) && np)
{
if (strstr(hv0, "jpeg") != NULL)
strcat(uid, ".jpg");
else if (strstr(hv0, "png") != NULL)
strcat(uid, ".png");
else if (strstr(hv0, "gif") != NULL)
strcat(uid, ".gif");
else if (strstr(hv0, "tiff") != NULL)
strcat(uid, ".tiff");
else
{
char *pos;
if ((pos = strrchr(hv0, '/')) != NULL)
{
strcat(uid, ".");
strcat(uid, pos+1);
}
}
strcat(fpname, uid);
strcpy (id3_cover, uid);
}
else if (strlen(id3_cover) && !np)
{
strcat (fpname, id3_cover);
unlink(fpname);
}
else if (strlen(id3_cover))
strcat (fpname, id3_cover);
if (np)
{
if ((fd = open(fpname, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
syslog(LOG_WARNING, "Error opening file %s for saving a picture: %s", fpname, strerror(errno));
else
{
write(fd, bt, len);
close(fd);
}
}
}
if ((frame = id3_tag_findframe(id3, ID3_FRAME_GENRE, 0)) != NULL)
{
int i;
for (i = 0; i < (int)frame->nfields; i++)
{
field = id3_frame_field(frame, i);
if (field->type == ID3_FIELD_TYPE_TEXTENCODING)
enc = id3_field_gettextencoding(field);
if (field && checkID3FieldType(field->type))
break;
}
if (field != NULL)
getID3_Field(field, enc, &id3_genre[0], sizeof(id3_genre));
else
id3_genre[0] = 0;
if (strchr(id3_genre, '(') && strchr(id3_genre, ')') && isdigit(id3_genre[1]))
{
int gen;
gen = atoi(&id3_genre[1]);
if (gen >= 0 && gen < ID3_NR_OF_V1_GENRES)
strncpy (id3_genre, ID3_v1_genre_description[gen], sizeof(id3_genre));
else
strcpy (id3_genre, "Pop");
}
char_replace(id3_genre, '"', '`');
char_replace(id3_genre, '\\', ' ');
}
id3_tag_delete(id3);
id3_file_close(ifile);
if (!strlen(id3_title) && !strlen(id3_singer)) /* grab title and artist from file name */
{
char hv0[256], *p;
int x, flag;
memset(hv0, 0, sizeof(hv0));
p = strrchr(file, '/'); /* find the last directory separator */
if (p) /* If there is a seperator ... */
strncpy(hv0, p+1, sizeof(hv0)-1); /* Copy the part after it (this is the file name) */
else /* There seems to be no path ... */
strncpy(hv0, file, sizeof(hv0)-1); /* Copy the whole file name (this IS the file name) */
if ((p = strchr(hv0, '-')) != NULL) /* Do we have a dash as seperator? */
{ /* Yes, so look how many components the file name have */
char *p2;
*p = 0;
p++;
if ((p2 = strchr(p, '-')) != NULL)
{
*p2 = 0;
p2++;
strncpy(id3_album, hv0, sizeof(id3_album) - 1);
strncpy(id3_singer, p, sizeof(id3_singer) - 1);
strncpy(id3_title, p2, sizeof(id3_title) - 1);
trim(id3_album);
}
else
{
strncpy(id3_singer, hv0, sizeof(id3_singer) - 1);
strncpy(id3_title, p, sizeof(id3_title) - 1);
}
if ((p = strrchr(id3_title, '.')) != NULL)
*p = 0;
trim(id3_singer);
trim(id3_title);
/* Make upper / lower case */
flag = 1;
for (x = 0; x < (int)strlen(id3_singer); x++)
{
if (id3_singer[x] == ' ')
flag = 1;
if (flag && id3_singer[x] != ' ')
{
id3_singer[x] = (char)toupper((int)id3_singer[x]);
flag = 0;
}
else
id3_singer[x] = (char)tolower((int)id3_singer[x]);
}
flag = 1;
for (x = 0; x < (int)strlen(id3_title); x++)
{
if (id3_title[x] == ' ')
flag = 1;
if (flag && id3_title[x] != ' ')
{
id3_title[x] = (char)toupper((int)id3_title[x]);
flag = 0;
}
else
id3_title[x] = (char)tolower((int)id3_title[x]);
}
if (p2)
{
flag = 1;
for (x = 0; x < (int)strlen(id3_album); x++)
{
if (id3_album[x] == ' ')
flag = 1;
if (flag && id3_album[x] != ' ')
{
id3_album[x] = (char)toupper((int)id3_album[x]);
flag = 0;
}
else
id3_album[x] = (char)tolower((int)id3_album[x]);
}
}
}
else
{
strncpy(id3_title, hv0, sizeof(hv0) - 1);
if ((p = strrchr(id3_title, '.')) != NULL)
*p = 0;
trim(id3_title);
flag = 0;
for (x = 0; x < (int)strlen(id3_title); x++)
{
if (id3_title[x] == ' ')
flag = 1;
if (flag && id3_title[x] != ' ')
{
id3_title[x] = (char)toupper((int)id3_title[x]);
flag = 0;
}
else
id3_title[x] = (char)tolower((int)id3_title[x]);
}
}
}
if (!update)
{
strcpy (query, "insert into musicdb (\"path\", \"type\", \"title\", \"interpret\", \"album\", \"genre\", \"cover\") values ");
strcat (query, "(\"");
strcat (query, file);
strcat (query, "\", ");
sprintf(hv0, "%d", fType);
strcat (query, hv0);
strcat (query, ", \"");
strcat (query, id3_title);
strcat (query, "\", \"");
strcat (query, id3_singer);
strcat (query, "\", \"");
strcat (query, id3_album);
strcat (query, "\", \"");
strcat (query, id3_genre);
strcat (query, "\", \"");
strcat (query, id3_cover);
strcat (query, "\")");
}
else
{
strcpy(query, "update musicdb set \"title\" = \"");
strcat(query, id3_title);
strcat(query, "\", \"interpret\" = \"");
strcat(query, id3_singer);
strcat(query, "\", \"album\" = \"");
strcat(query, id3_album);
strcat(query, "\", \"genre\" = \"");
strcat(query, id3_genre);
strcat(query, "\", \"cover\" = \"");
strcat(query, id3_cover);
strcat(query, "\" where id = ");
sprintf(hv0, "%d", id);
strcat(query, hv0);
}
if ((rc = sqlite3_exec(db, query, NULL, NULL, &zErrMsg)) != SQLITE_OK)
{
syslog(LOG_DAEMON, "SQL Error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
return;
}
sqlite3_close(db);
grabFilesCount++;
}
int checkID3FieldType(enum id3_field_type ft)
{
switch (ft)
{
case ID3_FIELD_TYPE_LATIN1:
case ID3_FIELD_TYPE_LATIN1FULL:
case ID3_FIELD_TYPE_LATIN1LIST:
case ID3_FIELD_TYPE_STRING:
case ID3_FIELD_TYPE_STRINGFULL:
case ID3_FIELD_TYPE_STRINGLIST:
return 1;
case ID3_FIELD_TYPE_INT8:
case ID3_FIELD_TYPE_INT16:
case ID3_FIELD_TYPE_INT24:
case ID3_FIELD_TYPE_INT32:
case ID3_FIELD_TYPE_INT32PLUS:
return 1;
}
return 0;
}
char *getID3_Field(union id3_field *field, enum id3_field_textencoding te, char *ret, int len)
{
char *p, *pg;
char wc[256];
id3_utf8_t *uc;
id3_ucs4_t const *ucs;
id3_latin1_t const *lt1;
iconv_t ic;
int i;
size_t length;
long num;
if (field == NULL)
{
if (configs.debug)
syslog(LOG_DEBUG, "Missing valid field pointer!");
*ret = 0;
return NULL;
}
*ret = 0;
switch (te)
{
case ID3_FIELD_TEXTENCODING_ISO_8859_1:
if (field->type == ID3_FIELD_TYPE_LATIN1)
{
if ((lt1 = id3_field_getlatin1(field)) == NULL)
{
syslog(LOG_WARNING, "Couldn't get latin1 field!");
return NULL;
}
}
else if (field->type == ID3_FIELD_TYPE_LATIN1FULL)
{
if ((lt1 = id3_field_getfulllatin1(field)) == NULL)
{
syslog(LOG_WARNING, "Couldn't get full latin1 field!");
return NULL;
}
}
else if (field->type == ID3_FIELD_TYPE_LATIN1LIST)
{
int n;
n = id3_field_getnstrings(field);
for (i = 0; i < n; i++)
{
if ((ucs = id3_field_getstrings(field, i)) != NULL)
break;
}
if (ucs == NULL)
{
syslog(LOG_WARNING, "Couldn't get Latin1 field out of a list of %d", n);
return NULL;
}
uc = id3_ucs4_utf8duplicate(ucs);
if (uc)
{
strncpy(ret, (char *)uc, len);
free (uc);
return ret;
}
return NULL;
}
else
{
syslog(LOG_WARNING, "No parseable field type found!");
return NULL;
}
if ((ic = iconv_open("UTF-8", "ISO-8859-1")) == (iconv_t)-1)
{
syslog(LOG_WARNING, "Error initializing ICONV to convert ASCII into UTF-8: %s", strerror(errno));
return ret;
}
length = sizeof(wc);
p = wc;
pg = (char *)lt1;
i = strlen((char *)lt1);
iconv(ic, &pg, (size_t *)&i, &p, &length);
iconv_close(ic);
strncpy (ret, wc, len);
break;
case ID3_FIELD_TEXTENCODING_UTF_16:
case ID3_FIELD_TEXTENCODING_UTF_16BE:
case ID3_FIELD_TEXTENCODING_UTF_8:
if (field->type == ID3_FIELD_TYPE_STRINGFULL)
{
if ((ucs = id3_field_getfullstring(field)) == NULL)
{
syslog(LOG_WARNING, "Couldn't get full UTF-? field!");
return NULL;
}
}
else if (field->type == ID3_FIELD_TYPE_STRING)
{
if ((ucs = id3_field_getstring(field)) == NULL)
{
syslog(LOG_WARNING, "Couldn't get UTF-? field!");
return NULL;
}
}
else if (field->type == ID3_FIELD_TYPE_STRINGLIST)
{
int n;
n = id3_field_getnstrings(field);
for (i = 0; i < n; i++)
{
if ((ucs = id3_field_getstrings(field, i)) != NULL)
break;
}
if (ucs == NULL)
{
syslog(LOG_WARNING, "Couldn't get UTF-? field out of a list of %d", n);
return NULL;
}
}
else
{
syslog(LOG_WARNING, "No parseable field type found!");
return NULL;
}
uc = id3_ucs4_utf8duplicate(ucs);
if (uc)
{
strncpy(ret, (char *)uc, len);
free (uc);
}
break;
case ID3_FIELD_TYPE_INT8:
case ID3_FIELD_TYPE_INT16:
case ID3_FIELD_TYPE_INT24:
case ID3_FIELD_TYPE_INT32:
case ID3_FIELD_TYPE_INT32PLUS:
num = id3_field_getint(field);
sprintf(ret, "(%ld)", num);
break;
default:
if (configs.debug)
syslog(LOG_DEBUG, "Unkown field encoding: %d", te);
return NULL;
}
return ret;
}