Rev 40 | Rev 42 | 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 <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 <id3.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"
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);
/* 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;
/* char *p = strrchr(dir->d_name, '.');
if (p != NULL && (strcasestr(p, ".mp3") != NULL || strcasestr(p, ".flac") != NULL))
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[512];
strcpy(hv0, dir);
strcat(hv0, "/");
strcat(hv0, namelist[n]->d_name);
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;
}
/*static int callback(void *hint, int argc, char **argv, char **azColName)
{
if (hint != NULL && strcmp(hint, "EXIST"))
{
file_found = TRUE;
return 0;
}
return 0;
}
*/
void evaluateMusicFile(char *file)
{
sqlite3 *db;
sqlite3_stmt *res;
char *zErrMsg = 0;
int rc, fType;
char fname[256], hv0[64];
char query[8192];
ID3Tag *id3;
ID3Frame *frame;
ID3Field *field;
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;
if (access(file, R_OK))
return;
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 path 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);
if (rc == SQLITE_ROW) /* If we've a row, the path exists and we'll not add it again */
{
sqlite3_close(db);
return;
}
/* 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. */
id3 = ID3Tag_New();
ID3Tag_Link(id3, file);
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));
memset(&id3_cover, 0, sizeof(id3_cover));
/* Get the required frames */
if ((frame = ID3Tag_FindFrameWithID(id3, ID3FID_TITLE)) != NULL)
{
field = ID3Frame_GetField(frame, ID3FN_TEXT);
ID3Field_GetASCII(field, &id3_title[0], sizeof(id3_title));
char_replace(id3_title, '"', '`');
char_replace(id3_title, '\\', ' ');
ID3Frame_Clear(frame);
}
if ((frame = ID3Tag_FindFrameWithID(id3, ID3FID_LEADARTIST)) != NULL)
{
field = ID3Frame_GetField(frame, ID3FN_TEXT);
ID3Field_GetASCII(field, &id3_singer[0], sizeof(id3_singer));
char_replace(id3_singer, '"', '`');
char_replace(id3_singer, '\\', ' ');
ID3Frame_Clear(frame);
}
if ((frame = ID3Tag_FindFrameWithID(id3, ID3FID_ALBUM)) != NULL)
{
field = ID3Frame_GetField(frame, ID3FN_TEXT);
ID3Field_GetASCII(field, &id3_album[0], sizeof(id3_album));
char_replace(id3_album, '"', '`');
char_replace(id3_album, '\\', ' ');
ID3Frame_Clear(frame);
}
if ((frame = ID3Tag_FindFrameWithID(id3, ID3FID_PICTURE)) != NULL)
{
char fpname[512], uid[128], hv0[256];
ID3Frame *fpframe;
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 ((fpframe = ID3Tag_FindFrameWithID(id3, ID3FID_UNIQUEFILEID)) != NULL)
{
field = ID3Frame_GetField(fpframe, ID3FN_TEXT);
ID3Field_GetASCII(field, &uid[0], sizeof(uid));
ID3Frame_Clear(fpframe);
}
if (fpframe == NULL || !strlen(uid))
{
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, "/");
/* Get the picture and store it into a file */
field = ID3Frame_GetField(frame, ID3FN_MIMETYPE);
ID3Field_GetASCII(field, &hv0[0], sizeof(hv0));
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);
field = ID3Frame_GetField(frame, ID3FN_DATA);
ID3Field_ToFile(field, fpname);
ID3Frame_Clear(frame);
}
if ((frame = ID3Tag_FindFrameWithID(id3, ID3FID_CONTENTTYPE)) != NULL)
{
field = ID3Frame_GetField(frame, ID3FN_TEXT);
ID3Field_GetASCII(field, &id3_genre[0], sizeof(id3_genre));
if (strchr(id3_genre, '(') && strchr(id3_genre, ')') && isdigit(id3_genre[1]))
{
uint32 gen;
gen = ID3Field_GetINT(field);
if (gen < ID3_NR_OF_V1_GENRES)
strncpy (id3_genre, ID3_v1_genre_description[gen], sizeof(id3_genre));
else
strcpy (id3_genre, "Pop");
}
else
{
char *genre;
if ((genre = ASCIItoUTF8(id3_genre)) != NULL)
{
strncpy (id3_genre, genre, sizeof(id3_genre));
free(genre);
}
}
ID3Frame_Clear(frame);
char_replace(id3_genre, '"', '`');
char_replace(id3_genre, '\\', ' ');
}
ID3Tag_Clear(id3);
ID3Tag_Delete(id3);
if (!strlen(id3_title) && !strlen(id3_singer)) /* grab title and artist from file name */
{
char hv0[256], *p;
memset(hv0, 0, sizeof(hv0));
p = strrchr(file, '/');
if (p)
strncpy(hv0, p+1, sizeof(hv0)-1);
else
strncpy(hv0, file, sizeof(hv0)-1);
if ((p = strchr(hv0, '-')) != NULL)
{
*p = 0;
p++;
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);
}
else
{
strncpy(id3_title, hv0, sizeof(hv0) - 1);
if ((p = strrchr(id3_title, '.')) != NULL)
*p = 0;
trim(id3_title);
}
}
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, ", \"");
if ((title = ASCIItoUTF8(id3_title)) != NULL)
{
strcat (query, title);
free(title);
}
else
strcat (query, id3_title);
strcat (query, "\", \"");
if ((singer = ASCIItoUTF8(id3_singer)) != NULL)
{
strcat (query, singer);
free(singer);
}
else
strcat (query, id3_singer);
strcat (query, "\", \"");
if ((album = ASCIItoUTF8(id3_album)) != NULL)
{
strcat (query, album);
free(album);
}
else
strcat (query, id3_album);
strcat (query, "\", \"");
strcat (query, id3_genre);
strcat (query, "\", \"");
strcat (query, id3_cover);
strcat (query, "\")");
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++;
}