Rev 59 | 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;
int _s1[MAX_HANDLES];
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));
handleAdd(s1);
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++;
}
handleDelete(s1);
close(s1);
pthread_exit(NULL);
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.
* The type DIRECT means to play a particular file without change the current
* queue. If something is already playing, it's stopped and the file the
* parameter <what> points to is played. With DIRECT the parameter <what>
* has to contain a valid ID number.
*
* Syntax: PLAY:<type>:<what>;
* <type> ID | TITLE | ARTIST | ALBUM | GENRE | PLAYLIST | QUEUE | DIRECT
* <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") && strcasecmp(p_type, "DIRECT")) /* If the type is not "ID" and not "DIRECT" 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 && strcasecmp(p_type, "DIRECT"))
nextCommand = PLAY_PLAY;
else if (playerActive && strcasecmp(p_type, "DIRECT")) /* Only if type is not DIRECT */
appendToQueue(s1, p_type, what);
else
{
_playPars.s1 = s1;
strcpy(_playPars.type, p_type);
strcpy(_playPars.what, what);
if (!strcasecmp(p_type, "DIRECT") && playerActive)
{
qstackAdd(atoi(what));
nextCommand = PLAY_STACK;
}
else if (!playerActive)
{
int e;
/* start a new thread to play the file(s) */
if ((e = pthread_create(&pthr_play, &pattr, pthr_playfile, (void *)&_playPars)) != 0)
{
switch (e)
{
case EAGAIN: strcpy (hv0, "Insufficient resources to create another thread."); break;
case EINVAL: strcpy (hv0, "Invalid settings in \"attr\"."); break;
case EPERM: strcpy (hv0, "No permission to set the scheduling policy and parameters specified in \"attr\"."); break;
default: sprintf (hv0, "Unknown error %d.", e);
}
syslog (LOG_DAEMON,"Creation of thread \"pthr_play\" failed: %s", hv0);
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, &pattr, 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;
}
/*
* This are functions to handle all the handles for connected clients.
*/
void handleInit()
{
int i;
for (i = 0; i < MAX_HANDLES; i++)
_s1[i] = -1;
}
void handleAdd(int fd)
{
int i;
for (i = 0; i < MAX_HANDLES; i++)
{
if (_s1[i] == fd)
return;
}
for (i = 0; i < MAX_HANDLES; i++)
{
if (_s1[i] < 0)
{
_s1[i] = fd;
return;
}
}
}
void handleDelete(int fd)
{
int i;
for (i = 0; i < MAX_HANDLES; i++)
{
if (_s1[i] == fd)
{
_s1[i] = -1;
return;
}
}
}
void handleWrite(char *txt)
{
int i;
for (i = 0; i < MAX_HANDLES; i++)
{
if (_s1[i] >= 0)
write(_s1[i], txt, strlen(txt));
}
}