Subversion Repositories mdb

Rev

Rev 4 | Rev 6 | 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"

static pthread_mutex_t fastmutex_proc = PTHREAD_MUTEX_INITIALIZER;
int musicFilter(const struct dirent *dir);
int grabMusicFiles(char *dir);
static int callback(void *hint, int argc, char **argv, char **azColName);
void evaluateMusicFile(char *file);

/* Global variables */
int file_found = 0;
char cmd_error[512];
char cmd_message[512];

/*
 * 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;
        memset(&buf[0], 0, sizeof(buf));
        memset(cmd_message, 0, sizeof(cmd_message));
        i = 0;

        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));
                                i = 0;
                                pthread_mutex_unlock(&fastmutex_proc);
                                continue;
                        }
                }

                i++;
        }

        close(s1);
}

/*
 * 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,j;

        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;
syslog(LOG_DEBUG, "bef=%s, par=%s", bef, par);
        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 file before we start to scan.
                 * This makes shure, that there is no double entry.
                 */
                strcpy(fname, configs.home);
                strcat(fname, "/music.db");

                if (!access(fname, R_OK | W_OK))
                {
                        if (unlink(fname) != 0)
                                syslog(LOG_WARNING, "Error deleting file %s: %s", fname, strerror(errno));
                }

                /* Scan all directories in the config file recursievly. */
                while (readLine(fd, &hv0[0], sizeof(hv0)) != NULL)
                {
                        grabMusicFiles(hv0);
                        i++;
                }

                sprintf(cmd_message, "DIRECTORIES:%d;", i);
                close(fd);
        }

        /*
         * Syntax: LIST:<type>:<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>;
         *         <id>     The unique ID of the file. Needed to play the file!
         *         <line>   The line number counting from 1
         */
        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: 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);
        }

        return TRUE;            /* cmd was OK */
}

/*
 * 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 = strrchr(dir->d_name, '.');

                if (p != NULL && strcasestr(p, ".mp3") != 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(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(hv0);
syslog(LOG_DEBUG, "Directory: %s", namelist[n]->d_name);
                        }
                        else
                                evaluateMusicFile(hv0);

                        free(namelist[n]); 
                } 

                free(namelist); 
        } 
}

static int callback(void *hint, int argc, char **argv, char **azColName)
{
/*      int i; */

        if (hint != NULL && strcmp(hint, "EXIST"))
        {
                file_found = TRUE;
syslog(LOG_DEBUG, "File exist");
                return 0;
        }

/*      for(i = 0; i < argc; i++)
        {
                printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
        }

        printf("\n");
*/
        return 0;
}

void evaluateMusicFile(char *file)
{
        sqlite3 *db;
        char *zErrMsg = 0;
        int rc;
        char fname[256];
        char query[1024];
        ID3Tag *id3;
        ID3Frame *frame;
        ID3Field *field;
        char id3_title[256];
        char id3_singer[256];
        char id3_album[256];
        char id3_genre[256];
        char *title, *singer, *album, *hp;

        if (access(file, R_OK))
                return;

        strcpy(fname, configs.home);
        strcat(fname, "/music.db");

        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)
        {
                strcpy(query, "create table \"main\".\"musicdb\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,");
                strcat(query, "\"path\" TEXT NOT NULL, \"title\" TEXT, \"interpret\" TEXT,");
                strcat(query, "\"album\" TEXT, \"genre\" TEXT, \"cover\" TEXT)");
                rc = sqlite3_exec(db, query, callback, (void *)"CREATE", &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\" (%s) was successfully created.", fname);
        }

        file_found = FALSE;
        /* Make sure, the file is not already in the database */
        sprintf(query, "select path from musicdb where path = \"%s\"", file);

        if ((rc = sqlite3_exec(db, query, callback, (void *)"EXIST", &zErrMsg)) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "SQL Error [%s]: %s", query, zErrMsg);
                sqlite3_free(zErrMsg);
                sqlite3_close(db);
                return;
        }

        if (file_found)                         /* This is true, if the file is already in the database. */
        {
                file_found = FALSE;
                sqlite3_close(db);
                return;
        }

        /* 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));
        /* 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, '\\', ' ');
        }

        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, '\\', ' ');
        }

        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, '\\', ' ');
        }

        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);
                        }
                }

                char_replace(id3_genre, '"', '`');
                char_replace(id3_genre, '\\', ' ');
        }

        ID3Tag_Delete(id3);

        strcpy (query, "insert into musicdb (\"path\", \"title\", \"interpret\", \"album\", \"genre\") values ");
        strcat (query, "(\"");
        strcat (query, file);
        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, "\")");

        if ((rc = sqlite3_exec(db, query, callback, (void *)"INSERT", &zErrMsg)) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "SQL Error  [%s]: %s", query, zErrMsg);
                sqlite3_free(zErrMsg);
                sqlite3_close(db);
                return;
        }

        sqlite3_close(db);
}