First pass of sqlite backend

This commit is contained in:
Ron Pedde 2005-03-11 06:39:40 +00:00
parent cbc3ddf143
commit 847d10b361
33 changed files with 2861 additions and 7335 deletions

View File

@ -15,17 +15,23 @@ AC_CANONICAL_HOST
AM_CONDITIONAL(COND_REND_OSX,false)
use_gdbm=true
use_sqlite=false
rend_posix=true
db_sqlite=true
STATIC_LIBS=no
CPPFLAGS="${CPPFLAGS} -g"
CPPFLAGS="${CPPFLAGS} -g -Wall"
dnl fix freebsd's broken (?) libpthread
AC_CHECK_LIB(c_r,pthread_creat,LDFLAGS="${LDFLAGS} -lc_r", [
AC_CHECK_LIB(pthread,pthread_create,LDFLAGS="${LDFLAGS} -lpthread") ])
AC_ARG_ENABLE(sqlite,[ --enable-sqlite Enable the sqlite db backend],
[ case "${enableval}" in
yes) db_sqlite=true;;
no) db_sqlite=false;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sqlite);;
esac ])
AC_ARG_ENABLE(debug,[ --enable-debug Enable debugging features],
CPPFLAGS="${CPPFLAGS} -Wall")
AC_ARG_ENABLE(debug-memory,[ --enable-debug-memory Enable mem leak debugging],
@ -58,23 +64,14 @@ AC_ARG_ENABLE(flac,[ --enable-flac Enable FLAC support],
use_flac=true;
CPPFLAGS="${CPPFLAGS} -DFLAC")
AC_ARG_ENABLE(sqlite,[ --enable-sqlite Enable SQLite backend],
use_sqlite=true;
LDFLAGS="${LDFLAGS} -lsqlite";
use_gdbm=false)
AM_CONDITIONAL(COND_REND_HOWL, test x$rend_howl = xtrue)
AM_CONDITIONAL(COND_REND_POSIX, test x$rend_posix = xtrue)
AM_CONDITIONAL(COND_OGGVORBIS, test x$use_oggvorbis = xtrue)
AM_CONDITIONAL(COND_FLAC, test x$use_flac = xtrue)
AM_CONDITIONAL(COND_SQLITE,test x$db_sqlite = xtrue)
AM_CONDITIONAL(COND_NEED_STRCASESTR,false)
AM_CONDITIONAL(COND_NEED_STRSEP,false)
AM_CONDITIONAL(COND_SQLITE,test x$use_sqlite = xtrue)
AM_CONDITIONAL(COND_GDBM,test x$use_gdbm = xtrue)
dnl Darwin's stupid cpp preprocessor....
echo Host type is $host
CPPFLAGS="$CPPFLAGS -DHOST='\"$host\"'"
@ -111,14 +108,6 @@ AC_ARG_WITH(static-libs,
fi
])
AC_ARG_WITH(gdbm-includes,
[--with-gdbm-includes[[=DIR]] use gdbm include files in DIR],[
if test "$withval" != "no" -a "$withval" != "yes"; then
Z_DIR=$withval
CPPFLAGS="${CPPFLAGS} -I$withval"
fi
])
AC_ARG_WITH(howl-includes,
[--with-howl-includes[[=DIR]] use howl include files in DIR],[
if test "$withval" != "no" -a "$withval" != "yes"; then
@ -127,14 +116,6 @@ AC_ARG_WITH(howl-includes,
fi
])
AC_ARG_WITH(gdbm-libs,
[--with-gdbm-libs[[=DIR]] use gdbm lib files in DIR],[
if test "$withval" != "no" -a "$withval" != "yes"; then
Z_DIR=$withval;
LDFLAGS="${LDFLAGS} -L$withval"
fi
])
AC_ARG_WITH(howl-libs,
[--with-howl-libs[[=DIR]] use howl lib files in DIR],[
if test "$withval" != "no" -a "$withval" != "yes"; then
@ -154,16 +135,6 @@ AC_ARG_WITH(id3tag,
AC_CHECK_HEADERS(getopt.h,,)
AC_CHECK_HEADERS(gdbm.h,, [
AC_MSG_ERROR([gdbm.h not found... try --with-gdbm-includes=dir])])
AC_CHECK_LIB(gdbm,gdbm_open,,echo "no libgdbm. Try --with-gdbm-lib=dir";exit)
if test "$STATIC_LIBS" != "no"; then
LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libgdbm.a"
else
LDFLAGS="${LDFLAGS} -lgdbm"
fi
AC_CHECK_HEADERS(id3tag.h,, [
AC_MSG_ERROR([id3tag.h not found... try --with-id3tag=dir])])
@ -179,6 +150,18 @@ else
fi
CFLAGS=$oldcflags
if test x$db_sqlite = xtrue; then
AC_CHECK_HEADERS(sqlite.h,, [
AC_MSG_ERROR([sqlite.h not found... Must have sqlite headers installed])])
AC_CHECK_LIB(sqlite,sqlite_open,,echo "Must have sqlite libraries installed";exit)
if test x"$STATIC_LIBS" != x"no"; then
LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libsqlite.a"
else
LDFLAGS="${LDFLAGS} -lsqlite"
fi
fi
if test x$use_oggvorbis = xtrue; then
AC_CHECK_HEADERS(ogg/ogg.h,, [
AC_MSG_ERROR([ogg/ogg.h not found... Must have libogg installed for Ogg/Vorbis support])])

View File

@ -13,23 +13,29 @@ CREATE TABLE songs (
conductor VARCHAR(1024) DEFAULT NULL,
grouping VARCHAR(1024) DEFAULT NULL,
url VARCHAR(1024) DEFAULT NULL,
bitrate INTEGER DEFAULT NULL,
samplerate INTEGER DEFAULT NULL,
song_length INTEGER DEFAULT NULL,
file_size INTEGER DEFAULT NULL,
year INTEGER DEFAULT NULL,
track INTEGER DEFAULT NULL,
total_tracks INTEGER DEFAULT NULL,
disc INTEGER DEFAULT NULL,
total_discs INTEGER DEFAULT NULL,
time_added INTEGER DEFAULT NULL,
time_modified INTEGER DEFAULT NULL,
time_played INTEGER DEFAULT NULL,
db_timestamp INTEGER DEFAULT NULL,
bpm INTEGER DEFAULT NULL,
compilation INTEGER DEFAULT NULL,
play_count INTEGER DEFAULT NULL,
rating INTEGER DEFAULT NULL
bitrate INTEGER DEFAULT 0,
samplerate INTEGER DEFAULT 0,
song_length INTEGER DEFAULT 0,
file_size INTEGER DEFAULT 0,
year INTEGER DEFAULT 0,
track INTEGER DEFAULT 0,
total_tracks INTEGER DEFAULT 0,
disc INTEGER DEFAULT 0,
total_discs INTEGER DEFAULT 0,
bpm INTEGER DEFAULT 0,
compilation INTEGER DEFAULT 0,
rating INTEGER DEFAULT 0,
play_count INTEGER DEFAULT 0,
data_kind INTEGER DEFAULT 0,
item_kind INTEGER DEFAULT 0,
description INTEGER DEFAULT 0,
time_added INTEGER DEFAULT 0,
time_modified INTEGER DEFAULT 0,
time_played INTEGER DEFAULT 0,
db_timestamp INTEGER DEFAULT 0,
disabled INTEGER DEFAULT 0,
updated INTEGER DEFAULT 0,
force_update INTEGER DEFAULT 0
);
CREATE TABLE config (
@ -41,6 +47,7 @@ CREATE TABLE playlists (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(255) NOT NULL,
smart INTEGER NOT NULL,
items INTEGER NOT NULL,
query VARCHAR(1024)
);
@ -58,7 +65,7 @@ CREATE TABLE users (
;CREATE INDEX idx_path on songs(path);
INSERT INTO config (term, value) VALUES ('version','1');
INSERT INTO users(id,name,password) VALUES (1,'admin','mt-daapd');
INSERT INTO playlists(id,name,smart,query) VALUES (1,'Library',1,'1');
INSERT INTO config VALUES ('version','1');
INSERT INTO users VALUES (1,1,'admin','mt-daapd');
INSERT INTO playlists VALUES (1,'Library',1,0,'1');

View File

@ -1,9 +1,6 @@
# $Id$
#
BUILT_SOURCES=parser.h
AM_YFLAGS=-d
sbin_PROGRAMS = mt-daapd
bin_PROGRAMS = wavstreamer
@ -27,28 +24,22 @@ if COND_FLAC
FLACSRC=flac.c
endif
if COND_GDBM
GDBMDB=db-gdbm.c
endif
if COND_SQLITE
SQLITEDB=db-sqlite.c
SQLITEDB=dbs-sqlite.c
endif
wavstreamer_SOURCES = wavstreamer.c
mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \
webserver.h configfile.c configfile.h err.c err.h restart.c restart.h \
daap-proto.c daap-proto.h daap.c daap.h db-memory.h \
mp3-scanner.h mp3-scanner.c playlist.c playlist.h \
rend-unix.h lexer.l parser.y strcasestr.c strcasestr.h strsep.c \
redblack.c redblack.h dynamic-art.c dynamic-art.h query.c query.h \
xml-rpc.h xml-rpc.c ssc.c ssc.h \
mp3-scanner.h mp3-scanner.c rend-unix.h strcasestr.c strcasestr.h strsep.c \
dynamic-art.c dynamic-art.h query.c query.h ssc.c ssc.h \
db-generic.c db-generic.h dispatch.c dispatch.h \
$(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(OGGVORBISSRC) $(FLACSRC) \
$(GDBMDB) $(SQLITEDB)
$(SQLITEDB)
EXTRA_DIST = mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c \
mDNSUNP.c mDNSPlatformFunctions.h mDNSPosix.h mDNSUNP.h \
rend-howl.c rend-posix.c rend-osx.c db-memory.c \
db-gdbm.c strcasestr.h redblack.c redblack.h db-memory.c \
parser.h ogg.c flac.c db-gdbm.c db-sqlite.c
parser.h ogg.c flac.c dbs-sqlite.c dbs-sqlite.h

View File

@ -48,8 +48,9 @@
#include <sys/wait.h>
#include "configfile.h"
#include "db-generic.h"
#include "err.h"
#include "xml-rpc.h"
//#include "xml-rpc.h"
#ifndef WITHOUT_MDNS
# include "rend.h"
@ -78,7 +79,7 @@ static int config_mutex_lock(void);
static int config_mutex_unlock(void);
static int config_existdir(char *path);
static int config_makedir(char *path);
static char *config_content_type(WS_CONNINFO *pwsc, char *path);
static void config_content_type(WS_CONNINFO *pwsc, char *path);
/*
* Defines
@ -168,7 +169,7 @@ int config_session=0; /**< session counter */
/**
* set the content type based on the file type
*/
static char *config_content_type(WS_CONNINFO *pwsc, char *path) {
void config_content_type(WS_CONNINFO *pwsc, char *path) {
char *extension;
CONTENTTYPES *pct=config_default_types;
@ -619,15 +620,16 @@ void config_handler(WS_CONNINFO *pwsc) {
pwsc->close=1;
ws_addresponseheader(pwsc,"Connection","close");
/*
if(strcasecmp(pwsc->uri,"/xml-rpc")==0) {
/* perhaps this should get a separate handler */
// perhaps this should get a separate handler
config_set_status(pwsc,0,"Serving xml-rpc method");
xml_handle(pwsc);
DPRINTF(E_DBG,L_CONF|L_XML,"Thread %d: xml-rpc served\n",pwsc->threadno);
config_set_status(pwsc,0,NULL);
return;
}
*/
snprintf(path,PATH_MAX,"%s/%s",config.web_root,pwsc->uri);
if(!realpath(path,resolved_path)) {
pwsc->error=errno;
@ -876,7 +878,7 @@ void config_emit_service_status(WS_CONNINFO *pwsc, void *value, char *arg) {
if(!scanning) {
ws_writefd(pwsc,"<tr>\n");
ws_writefd(pwsc," <th>DB Version</th>\n");
ws_writefd(pwsc," <td>%d</td>\n",db_version());
ws_writefd(pwsc," <td>%d</td>\n",db_revision());
ws_writefd(pwsc,"</tr>\n");
}

View File

@ -1,628 +0,0 @@
/*
* $Id$
* Helper functions for formatting a daap message
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "daap-proto.h"
#include "err.h"
#include "restart.h"
/* Forwards */
DAAP_BLOCK *daap_get_new(void);
DAAP_BLOCK *daap_add_formatted(DAAP_BLOCK *parent, char *tag,
int len, char *value);
GZIP_STREAM *gzip_alloc(void) {
GZIP_STREAM *gz = malloc(sizeof(GZIP_STREAM));
if(gz) {
memset(gz,0x00,sizeof(GZIP_STREAM));
gz->in_size = GZIP_CHUNK;
gz->in = malloc(gz->in_size);
gz->bytes_in = 0;
gz->out = NULL;
gz->bytes_out = 0;
}
return gz;
}
ssize_t gzip_write(GZIP_STREAM *gz, void *buf, size_t size) {
int next_size;
char *in2;
int new_size;
if (gz->in == NULL)
return -1;
/* make sure input buffer is big enough */
while (gz->in_size <= gz->bytes_in + size) {
new_size = 2*gz->in_size;
in2 = malloc(new_size);
if (in2 == NULL) {
DPRINTF(E_LOG,L_WS|L_DAAP,"out of memory for input buffer\n");
free(gz->in);
gz->in = NULL;
gz->bytes_in = gz->in_size = 0;
return -1;
}
memcpy(in2, gz->in, gz->in_size);
free(gz->in);
gz->in = in2;
gz->in_size = new_size;
}
memcpy(gz->in + gz->bytes_in, buf, size);
gz->bytes_in += size;
return size;
}
int gzip_compress(GZIP_STREAM *gz) {
int out_size;
int status;
z_stream strm;
if (gz->in == NULL)
return -1;
out_size = (int)(1.05*gz->in_size) + 40;
gz->out = malloc(out_size);
if (gz->out == NULL) {
DPRINTF(E_INF,L_WS|L_DAAP,"out of memory for output buffer\n");
gz->bytes_out = 0;
return -1;
}
memset((void*)&strm,0x00,sizeof(strm));
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.next_in = gz->in;
strm.avail_in = gz->bytes_in;
strm.next_out = gz->out;
strm.avail_out = out_size;
deflateInit2(&strm,GZIP_COMPRESSION_LEVEL,Z_DEFLATED,24,8,Z_DEFAULT_STRATEGY);
while ((status = deflate(&strm,Z_FINISH)) == Z_OK)
;
if (status != Z_STREAM_END) {
DPRINTF(E_LOG,L_WS|L_DAAP,"unable to compress data: %s (%d)\n",strm.msg,status);
gz->bytes_out = 0;
return -1;
}
gz->bytes_out = strm.total_out;
deflateEnd(&strm);
return gz->bytes_out;
}
int gzip_close(GZIP_STREAM *gz, int fd) {
int bytes_written = gz->bytes_out;
if (r_write(fd,gz->out,gz->bytes_out) != gz->bytes_out) {
DPRINTF(E_LOG,L_WS|L_DAAP,"unable to write gzipped data\n");
return -1;
}
if (gz->in != NULL)
free(gz->in);
if (gz->out != NULL)
free(gz->out);
free(gz);
return bytes_written;
}
/*
* daap_get_new
*
* Initialize a new daap struct
*/
DAAP_BLOCK *daap_get_new(void) {
DAAP_BLOCK *pnew;
pnew=(DAAP_BLOCK*)malloc(sizeof(DAAP_BLOCK));
if(!pnew) {
DPRINTF(E_WARN,L_DAAP,"Error mallocing a daap block\n");
return NULL;
}
pnew->free=0;
pnew->value=NULL;
pnew->parent=NULL;
pnew->children=NULL;
pnew->last_child=NULL;
pnew->next=NULL;
return pnew;
}
/*
* daap_add_formatted
*
* add a block exactly as formatted in value.
*
* Note that value WILL be freed later in daap_free, so
* the value paramater must have been malloced
*/
DAAP_BLOCK *daap_add_formatted(DAAP_BLOCK *parent, char *tag,
int size, char *value) {
DAAP_BLOCK *current,*last;
DAAP_BLOCK *pnew;
DPRINTF(E_SPAM,L_DAAP,"Adding daap tag %s\n",tag);
pnew = daap_get_new();
if(!pnew)
return NULL;
pnew->reported_size=size;
pnew->parent=parent;
pnew->size=size;
memcpy(pnew->tag,tag,4);
if((size <= 4) && (size >= 0)) { /* we can just put it in svalue */
memcpy(pnew->svalue,value,size);
pnew->free=0;
} else {
pnew->value=value;
pnew->free=1;
}
pnew->next=NULL;
/* walk the child list and put it at the end */
if(parent) {
last = parent->last_child;
if (last) {
last->next = pnew;
} else {
parent->children = pnew;
}
parent->last_child = pnew;
}
/* now, walk the chain and update sizes */
current=pnew->parent;
while(current) {
current->reported_size += (8 + pnew->reported_size);
current=current->parent;
}
return pnew;
}
/*
* daap_add_int
*
* Add an int block to a specific parent
*/
DAAP_BLOCK *daap_add_long(DAAP_BLOCK *parent, char *tag, int v1, int v2) {
char *ivalue;
ivalue=(char*)malloc(8);
if(!ivalue)
return NULL;
ivalue[0]=(v1 >> 24) & 0xFF;
ivalue[1]=(v1 >> 16) & 0xFF;
ivalue[2]=(v1 >> 8) & 0xFF;
ivalue[3]=v1 & 0xFF;
ivalue[4]=(v2 >> 24) & 0xFF;
ivalue[5]=(v2 >> 16) & 0xFF;
ivalue[6]=(v2 >> 8) & 0xFF;
ivalue[7]=v2 & 0xFF;
return daap_add_formatted(parent,tag,8,ivalue);
}
/*
* daap_add_int
*
* Add an int block to a specific parent
*/
DAAP_BLOCK *daap_add_int(DAAP_BLOCK *parent, char *tag, int value) {
char ivalue[4];
ivalue[0]=(value >> 24) & 0xFF;
ivalue[1]=(value >> 16) & 0xFF;
ivalue[2]=(value >> 8) & 0xFF;
ivalue[3]=value & 0xFF;
return daap_add_formatted(parent,tag,4,ivalue);
}
/*
* daap_add_short
*
* Add an int block to a specific parent
*/
DAAP_BLOCK *daap_add_short(DAAP_BLOCK *parent, char *tag, short int value) {
char ivalue[2];
ivalue[0]=(value >> 8) & 0xFF;
ivalue[1]=value & 0xFF;
return daap_add_formatted(parent,tag,2,ivalue);
}
/*
* daap_add_char
*
* Add a single char
*/
DAAP_BLOCK *daap_add_char(DAAP_BLOCK *parent, char *tag, char value) {
return daap_add_formatted(parent,tag,1,&value);
}
/*
* daap_add_data
*
* Add unstructured data to a specific parent
*/
DAAP_BLOCK *daap_add_data(DAAP_BLOCK *parent, char *tag,
int len, void *value) {
void *pvalue;
if(len > 4) {
pvalue=(void*)malloc(len);
if(!pvalue)
return NULL;
memcpy(pvalue,value,len);
return daap_add_formatted(parent,tag,len,pvalue);
}
return daap_add_formatted(parent,tag,len,value);
}
/*
* daap_add_string
*
* Add a string element to a specific parent
*/
DAAP_BLOCK *daap_add_string(DAAP_BLOCK *parent, char *tag, char *value) {
char *newvalue;
if(value) {
if(strlen(value) > 4) {
newvalue=strdup(value);
if(!newvalue)
return NULL;
return daap_add_formatted(parent,tag,strlen(newvalue),newvalue);
}
return daap_add_formatted(parent,tag,strlen(value),value);
}
return daap_add_formatted(parent,tag,0,"");
}
/*
* daap_add_empty
*
* add a tag whose only value is to act as an aggregator
*/
DAAP_BLOCK *daap_add_empty(DAAP_BLOCK *parent, char *tag) {
return daap_add_formatted(parent,tag,0,NULL);
}
DAAP_ITEMS *daap_lookup_tag(char *tag) {
DAAP_ITEMS *pitem;
pitem=taglist;
while((pitem->tag) && (strncmp(tag,pitem->tag,4))) {
pitem++;
}
if(!pitem->tag)
DPRINTF(E_FATAL,L_DAAP,"Unknown daap tag: %c%c%c%c\n",tag[0],tag[1],tag[2],tag[3]);
return pitem;
}
/**
* xml entity encoding, stupid style
*/
char *daap_xml_entity_encode(char *original, int len) {
char *new;
char *s, *d;
int destsize;
int truelen;
/* this is about stupid */
if(len) {
truelen=len;
} else {
truelen=strlen(original);
}
destsize = 6*truelen+1;
new=(char *)malloc(destsize);
if(!new) return NULL;
memset(new,0x00,destsize);
s=original;
d=new;
while(s < (original+truelen)) {
switch(*s) {
case '>':
strcat(d,"&gt;");
d += 4;
s++;
break;
case '<':
strcat(d,"&lt;");
d += 4;
s++;
break;
case '"':
strcat(d,"&quot;");
d += 6;
s++;
break;
case '\'':
strcat(d,"&apos;");
d += 6;
s++;
break;
case '&':
strcat(d,"&amp;");
d += 5;
s++;
break;
default:
*d++ = *s++;
}
}
return new;
}
/**
* serialize a dmap tree as xml
*/
int daap_serialize_xml(DAAP_BLOCK *root, int fd) {
DAAP_ITEMS *pitem;
unsigned char *data;
int ivalue;
long long lvalue;
char *encoded_string;
while(root) {
if(root->free)
data=(unsigned char *)root->value;
else
data=(unsigned char *)root->svalue;
pitem=daap_lookup_tag(root->tag);
r_fdprintf(fd,"<%s>",pitem->description);
switch(pitem->type) {
case 0x01: /* byte */
r_fdprintf(fd,"%d",*((char *)data));
break;
case 0x02: /* unsigned byte */
r_fdprintf(fd,"%ud",*((char *)data));
break;
case 0x03: /* short */
ivalue = data[0] << 8 | data[1];
r_fdprintf(fd,"%d",ivalue);
break;
case 0x05: /* int */
case 0x0A: /* epoch */
ivalue = data[0] << 24 |
data[1] << 16 |
data[2] << 8 |
data[3];
r_fdprintf(fd,"%d",ivalue);
break;
case 0x07: /* long long */
ivalue = data[0] << 24 |
data[1] << 16 |
data[2] << 8 |
data[3];
lvalue=ivalue;
ivalue = data[4] << 24 |
data[5] << 16 |
data[6] << 8 |
data[7];
lvalue = (lvalue << 32) | ivalue;
r_fdprintf(fd,"%ll",ivalue);
break;
case 0x09: /* string */
case 0x0C: /* container, but mlit is a string in browse */
if(root->size) {
encoded_string=daap_xml_entity_encode(data,root->size);
r_fdprintf(fd,"%s",encoded_string);
free(encoded_string);
}
break;
case 0x0B: /* version? */
ivalue=data[0] << 8 | data[1];
r_fdprintf(fd,"%d.%d.%d",ivalue,data[2],data[3]);
break;
default:
DPRINTF(E_FATAL,L_DAAP,"Bad dmap type: %d, %s\n",
pitem->type, pitem->description);
break;
}
if(root->children)
daap_serialize_xml(root->children,fd);
r_fdprintf(fd,"</%s>",pitem->description);
root=root->next;
}
}
/*
* daap_serialmem
*
* Serialize a daap tree to a fd;
*/
int daap_serialize(DAAP_BLOCK *root, int fd, GZIP_STREAM *gz) {
char size[4];
while(root) {
if (gz == NULL)
r_write(fd,root->tag,4);
else
gzip_write(gz,root->tag,4);
size[0] = (root->reported_size >> 24) & 0xFF;
size[1] = (root->reported_size >> 16) & 0xFF;
size[2] = (root->reported_size >> 8 ) & 0xFF;
size[3] = (root->reported_size) & 0xFF;
if (gz == NULL)
r_write(fd,&size,4);
else
gzip_write(gz,&size,4);
if(root->size) {
if(root->free) {
if (gz == NULL) {
r_write(fd,root->value,root->size);
}
else {
gzip_write(gz,root->value,root->size);
}
}
else {
if (gz == NULL) {
r_write(fd,root->svalue,root->size);
}
else {
gzip_write(gz,root->svalue,root->size);
}
}
}
if(root->children) {
if(daap_serialize(root->children,fd,gz))
return -1;
}
root=root->next;
}
return 0;
}
/*
* daap_remove
*
* remove a node from it's parent node and release it
*/
void daap_remove(DAAP_BLOCK* node)
{
DAAP_BLOCK* parent = node->parent;
if(0 != parent)
{
DAAP_BLOCK** ptr = &parent->children;
while(*ptr && *ptr != node)
ptr = &(**ptr).next;
assert(0 != *ptr);
// remove us from the chain
*ptr = node->next;
// update sizes in parent chain
for(parent = node->parent ; parent ; parent = parent->parent)
parent->reported_size -= (8 + node->reported_size);
// clear parent and next pointers so daap_free doesn't get ambitious
node->parent = 0;
node->next = 0;
}
daap_free(node);
}
/*
* find a child block of the parent node
*/
DAAP_BLOCK *daap_find(DAAP_BLOCK *parent, char* tag)
{
for(parent = parent->children ; parent ; parent = parent->next)
if(!strncmp(parent->tag, tag, 4))
break;
return parent;
}
/*
* daap_free
*
* Free an entire daap formatted block
*/
void daap_free(DAAP_BLOCK *root) {
DAAP_BLOCK *pnext;
while(root) {
DPRINTF(E_SPAM,L_DAAP,"Freeing %c%c%c%c\n",root->tag[0],root->tag[1],
root->tag[2],root->tag[3]);
if((root->size) && (root->free))
free(root->value); /* otherwise, static value */
daap_free(root->children);
pnext=root->next;
free(root);
root=pnext;
}
return;
}
// search a block's children and change an integer value
int daap_set_int(DAAP_BLOCK* parent, char* tag, int value)
{
DAAP_BLOCK* child = daap_find(parent, tag);
if(0 == child || child->size != sizeof(int))
return 0;
child->svalue[0]=(value >> 24) & 0xFF;
child->svalue[1]=(value >> 16) & 0xFF;
child->svalue[2]=(value >> 8) & 0xFF;
child->svalue[3]=value & 0xFF;
return 1;
}

View File

@ -1,87 +0,0 @@
/*
* $Id$
* Helper functions for formatting a daap message
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _DAAP_PROTO_H_
#define _DAAP_PROTO_H_
#include <sys/types.h>
#include "zlib.h"
#define GZIP_CHUNK 16384
#define GZIP_COMPRESSION_LEVEL Z_DEFAULT_COMPRESSION
typedef struct gzip_stream_tag {
int bytes_in;
int bytes_out;
int in_size;
char *in;
char *out;
} GZIP_STREAM;
GZIP_STREAM *gzip_alloc(void);
ssize_t gzip_write(GZIP_STREAM *gz, void *buf, size_t size);
int gzip_compress(GZIP_STREAM *gz);
int gzip_close(GZIP_STREAM *gz, int fd);
typedef struct daap_block_tag {
char tag[4];
int reported_size;
int size;
int free;
char *value;
char svalue[4]; /* for statics up to 4 bytes */
struct daap_block_tag *parent;
struct daap_block_tag *children;
struct daap_block_tag *last_child;
struct daap_block_tag *next;
} DAAP_BLOCK;
DAAP_BLOCK *daap_add_int(DAAP_BLOCK *parent, char *tag, int value);
DAAP_BLOCK *daap_add_data(DAAP_BLOCK *parent, char *tag, int len, void *value);
DAAP_BLOCK *daap_add_string(DAAP_BLOCK *parent, char *tag, char *value);
DAAP_BLOCK *daap_add_empty(DAAP_BLOCK *parent, char *tag);
DAAP_BLOCK *daap_add_char(DAAP_BLOCK *parent, char *tag, char value);
DAAP_BLOCK *daap_add_short(DAAP_BLOCK *parent, char *tag, short int value);
DAAP_BLOCK *daap_add_long(DAAP_BLOCK *parent, char *tag, int v1, int v2);
int daap_serialize(DAAP_BLOCK *root, int fd, GZIP_STREAM *gz);
void daap_free(DAAP_BLOCK *root);
// remove a block from it's parent (and free it)
void daap_remove(DAAP_BLOCK* root);
// search a block's direct children for a block with a given tag
DAAP_BLOCK *daap_find(DAAP_BLOCK *parent, char* tag);
// search a block's children and change an integer value
int daap_set_int(DAAP_BLOCK* parent, char* tag, int value);
typedef struct tag_daap_items {
int type;
char *tag;
char *description;
} DAAP_ITEMS;
extern DAAP_ITEMS taglist[];
#endif

1247
src/daap.c

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

712
src/db-generic.c Normal file
View File

@ -0,0 +1,712 @@
/*
* $Id$
* Generic db implementation for specific sql backend
*
* Copyright (C) 2005 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define _XOPEN_SOURCE 600 /** I forgot why I needed this? */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "db-generic.h"
#include "err.h"
#include "mp3-scanner.h"
#include "dbs-sqlite.h"
#define DB_VERSION 1
#define MAYBEFREE(a) { if((a)) free((a)); };
/** pointers to database-specific functions */
typedef struct tag_db_functions {
char *name;
int(*dbs_open)(char *);
int(*dbs_init)(int);
int(*dbs_deinit)(void);
int(*dbs_add)(MP3FILE*);
int(*dbs_update)(MP3FILE*);
int(*dbs_enum_start)(DBQUERYINFO *);
int(*dbs_enum_size)(DBQUERYINFO *, int *);
int(*dbs_enum_fetch)(DBQUERYINFO *, unsigned char **);
int(*dbs_enum_reset)(DBQUERYINFO *);
int(*dbs_enum_end)(void);
int(*dbs_get_id)(char *);
int(*dbs_start_scan)(void);
int(*dbs_end_scan)(void);
int(*dbs_get_count)(CountType_t);
MP3FILE*(*dbs_fetch_item)(int);
void(*dbs_dispose_item)(MP3FILE*);
}DB_FUNCTIONS;
/** All supported backend databases, and pointers to the db specific implementations */
DB_FUNCTIONS db_functions[] = {
#ifdef HAVE_LIBSQLITE
{
"sqlite",
db_sqlite_open,
db_sqlite_init,
db_sqlite_deinit,
db_sqlite_add,
db_sqlite_update,
db_sqlite_enum_start,
db_sqlite_enum_size,
db_sqlite_enum_fetch,
db_sqlite_enum_reset,
db_sqlite_enum_end,
db_sqlite_get_id,
db_sqlite_start_scan,
db_sqlite_end_scan,
db_sqlite_get_count,
db_sqlite_fetch_item,
db_sqlite_dispose_item
},
#endif
{ NULL,NULL }
};
DAAP_ITEMS taglist[] = {
{ 0x05, "miid", "dmap.itemid" },
{ 0x09, "minm", "dmap.itemname" },
{ 0x01, "mikd", "dmap.itemkind" },
{ 0x07, "mper", "dmap.persistentid" },
{ 0x0C, "mcon", "dmap.container" },
{ 0x05, "mcti", "dmap.containeritemid" },
{ 0x05, "mpco", "dmap.parentcontainerid" },
{ 0x05, "mstt", "dmap.status" },
{ 0x09, "msts", "dmap.statusstring" },
{ 0x05, "mimc", "dmap.itemcount" },
{ 0x05, "mctc", "dmap.containercount" },
{ 0x05, "mrco", "dmap.returnedcount" },
{ 0x05, "mtco", "dmap.specifiedtotalcount" },
{ 0x0C, "mlcl", "dmap.listing" },
{ 0x0C, "mlit", "dmap.listingitem" },
{ 0x0C, "mbcl", "dmap.bag" },
{ 0x0C, "mdcl", "dmap.dictionary" },
{ 0x0C, "msrv", "dmap.serverinforesponse" },
{ 0x01, "msau", "dmap.authenticationmethod" },
{ 0x01, "mslr", "dmap.loginrequired" },
{ 0x0B, "mpro", "dmap.protocolversion" },
{ 0x01, "msal", "dmap.supportsautologout" },
{ 0x01, "msup", "dmap.supportsupdate" },
{ 0x01, "mspi", "dmap.supportspersistentids" },
{ 0x01, "msex", "dmap.supportsextensions" },
{ 0x01, "msbr", "dmap.supportsbrowse" },
{ 0x01, "msqy", "dmap.supportsquery" },
{ 0x01, "msix", "dmap.supportsindex" },
{ 0x01, "msrs", "dmap.supportsresolve" },
{ 0x05, "mstm", "dmap.timeoutinterval" },
{ 0x05, "msdc", "dmap.databasescount" },
{ 0x0C, "mlog", "dmap.loginresponse" },
{ 0x05, "mlid", "dmap.sessionid" },
{ 0x0C, "mupd", "dmap.updateresponse" },
{ 0x05, "musr", "dmap.serverrevision" },
{ 0x01, "muty", "dmap.updatetype" },
{ 0x0C, "mudl", "dmap.deletedidlisting" },
{ 0x0C, "mccr", "dmap.contentcodesresponse" },
{ 0x05, "mcnm", "dmap.contentcodesnumber" },
{ 0x09, "mcna", "dmap.contentcodesname" },
{ 0x03, "mcty", "dmap.contentcodestype" },
{ 0x0B, "apro", "daap.protocolversion" },
{ 0x0C, "avdb", "daap.serverdatabases" },
{ 0x0C, "abro", "daap.databasebrowse" },
{ 0x0C, "abal", "daap.browsealbumlisting" },
{ 0x0C, "abar", "daap.browseartistlisting" },
{ 0x0C, "abcp", "daap.browsecomposerlisting" },
{ 0x0C, "abgn", "daap.browsegenrelisting" },
{ 0x0C, "adbs", "daap.databasesongs" },
{ 0x09, "asal", "daap.songalbum" },
{ 0x09, "asar", "daap.songartist" },
{ 0x03, "asbt", "daap.songbeatsperminute" },
{ 0x03, "asbr", "daap.songbitrate" },
{ 0x09, "ascm", "daap.songcomment" },
{ 0x01, "asco", "daap.songcompilation" },
{ 0x09, "ascp", "daap.songcomposer" },
{ 0x0A, "asda", "daap.songdateadded" },
{ 0x0A, "asdm", "daap.songdatemodified" },
{ 0x03, "asdc", "daap.songdisccount" },
{ 0x03, "asdn", "daap.songdiscnumber" },
{ 0x01, "asdb", "daap.songdisabled" },
{ 0x09, "aseq", "daap.songeqpreset" },
{ 0x09, "asfm", "daap.songformat" },
{ 0x09, "asgn", "daap.songgenre" },
{ 0x09, "asdt", "daap.songdescription" },
{ 0x02, "asrv", "daap.songrelativevolume" },
{ 0x05, "assr", "daap.songsamplerate" },
{ 0x05, "assz", "daap.songsize" },
{ 0x05, "asst", "daap.songstarttime" },
{ 0x05, "assp", "daap.songstoptime" },
{ 0x05, "astm", "daap.songtime" },
{ 0x03, "astc", "daap.songtrackcount" },
{ 0x03, "astn", "daap.songtracknumber" },
{ 0x01, "asur", "daap.songuserrating" },
{ 0x03, "asyr", "daap.songyear" },
{ 0x01, "asdk", "daap.songdatakind" },
{ 0x09, "asul", "daap.songdataurl" },
{ 0x0C, "aply", "daap.databaseplaylists" },
{ 0x01, "abpl", "daap.baseplaylist" },
{ 0x0C, "apso", "daap.playlistsongs" },
{ 0x0C, "arsv", "daap.resolve" },
{ 0x0C, "arif", "daap.resolveinfo" },
{ 0x05, "aeNV", "com.apple.itunes.norm-volume" },
{ 0x01, "aeSP", "com.apple.itunes.smart-playlist" },
/* iTunes 4.5+ */
{ 0x01, "msas", "dmap.authenticationschemes" },
{ 0x05, "ascd", "daap.songcodectype" },
{ 0x09, "agrp", "daap.songgrouping" },
{ 0x05, "aeSV", "com.apple.itunes.music-sharing-version" },
{ 0x05, "aePI", "com.apple.itunes.itms-playlistid" },
{ 0x05, "aeCI", "com.apple.iTunes.itms-composerid" },
{ 0x05, "aeGI", "com.apple.iTunes.itms-genreid" },
{ 0x05, "aeAI", "com.apple.iTunes.itms-artistid" },
{ 0x05, "aeSI", "com.apple.iTunes.itms-songid" },
{ 0x00, NULL, NULL }
};
/** map the string names specified in the meta= tag to bit numbers */
static METAMAP db_metamap[] = {
{ "dmap.itemid", metaItemId },
{ "dmap.itemname", metaItemName },
{ "dmap.itemkind", metaItemKind },
{ "dmap.persistentid", metaPersistentId },
{ "dmap.containeritemid", metaContainerItemId },
{ "dmap.parentcontainerid", metaParentContainerId },
/* end generics */
{ "daap.songalbum", metaSongAlbum },
{ "daap.songartist", metaSongArtist },
{ "daap.songbitrate", metaSongBitRate },
{ "daap.songbeatsperminute",metaSongBPM },
{ "daap.songcomment", metaSongComment },
{ "daap.songcompilation", metaSongCompilation },
{ "daap.songcomposer", metaSongComposer },
{ "daap.songdatakind", metaSongDataKind },
{ "daap.songdataurl", metaSongDataURL },
{ "daap.songdateadded", metaSongDateAdded },
{ "daap.songdatemodified", metaSongDateModified },
{ "daap.songdescription", metaSongDescription },
{ "daap.songdisabled", metaSongDisabled },
{ "daap.songdisccount", metaSongDiscCount },
{ "daap.songdiscnumber", metaSongDiscNumber },
{ "daap.songeqpreset", metaSongEqPreset },
{ "daap.songformat", metaSongFormat },
{ "daap.songgenre", metaSongGenre },
{ "daap.songgrouping", metaSongGrouping },
{ "daap.songrelativevolume",metaSongRelativeVolume },
{ "daap.songsamplerate", metaSongSampleRate },
{ "daap.songsize", metaSongSize },
{ "daap.songstarttime", metaSongStartTime },
{ "daap.songstoptime", metaSongStopTime },
{ "daap.songtime", metaSongTime },
{ "daap.songtrackcount", metaSongTrackCount },
{ "daap.songtracknumber", metaSongTrackNumber },
{ "daap.songuserrating", metaSongUserRating },
{ "daap.songyear", metaSongYear },
{ 0, 0 }
};
/* Globals */
static DB_FUNCTIONS *db_current=&db_functions[0]; /**< current database backend */
static int db_revision_no=2; /**< current revision of the db */
static pthread_once_t db_initlock=PTHREAD_ONCE_INIT; /**< to initialize the rwlock */
static int db_is_scanning=0;
static pthread_rwlock_t db_rwlock; /**< pthread r/w sync for the database */
/* Forwards */
static void db_writelock(void);
static void db_readlock(void);
static int db_unlock(void);
static void db_init_once(void);
/**
* encode a string meta request into a MetaField_t
*
* \param meta meta string variable from GET request
*/
MetaField_t db_encode_meta(char *meta) {
MetaField_t bits = 0;
char *start;
char *end;
METAMAP *m;
for(start = meta ; *start ; start = end) {
int len;
if(0 == (end = strchr(start, ',')))
end = start + strlen(start);
len = end - start;
if(*end != 0)
end++;
for(m = db_metamap ; m->tag ; ++m)
if(!strncmp(m->tag, start, len))
break;
if(m->tag)
bits |= (((MetaField_t) 1) << m->bit);
else
DPRINTF(E_WARN,L_DAAP,"Unknown meta code: %.*s\n", len, start);
}
DPRINTF(E_DBG, L_DAAP, "meta codes: %llu\n", bits);
return bits;
}
/**
* see if a specific metafield was requested
*
* \param meta encoded list of requested metafields
* \param fieldNo field to test for
*/
int db_wantsmeta(MetaField_t meta, MetaFieldName_t fieldNo) {
return 0 != (meta & (((MetaField_t) 1) << fieldNo));
}
/*
* db_readlock
*
* If this fails, something is so amazingly hosed, we might just as well
* terminate.
*/
void db_readlock(void) {
int err;
if((err=pthread_rwlock_rdlock(&db_rwlock))) {
DPRINTF(E_FATAL,L_DB,"cannot lock rdlock: %s\n",strerror(err));
}
}
/*
* db_writelock
*
* same as above
*/
void db_writelock(void) {
int err;
if((err=pthread_rwlock_wrlock(&db_rwlock))) {
DPRINTF(E_FATAL,L_DB,"cannot lock rwlock: %s\n",strerror(err));
}
}
/*
* db_unlock
*
* useless, but symmetrical
*/
int db_unlock(void) {
return pthread_rwlock_unlock(&db_rwlock);
}
/**
* Must dynamically initialize the rwlock, as Mac OSX 10.3 (at least)
* doesn't have a static initializer for rwlocks
*/
void db_init_once(void) {
pthread_rwlock_init(&db_rwlock,NULL);
}
/**
* Set the database backend. This should show a friendly error about
* what databse types are supported.
*
* \param type what database backend to use (by friendly name)
*/
extern int db_set_backend(char *type) {
DPRINTF(E_DBG,L_DB,"Setting backend database to %s\n",type);
if(!db_functions[0].name) {
DPRINTF(E_FATAL,L_DB,"No database backends are available. Install sqlite!\n");
}
db_current=&db_functions[0];
while((db_current->name) && (strcasecmp(db_current->name,type))) {
db_current++;
}
if(!db_current->name) {
DPRINTF(E_WARN,L_DB,"Could not find db backend %s. Aborting.\n",type);
return -1;
}
DPRINTF(E_DBG,L_DB,"Backend database set\n");
return 0;
}
/**
* Open the database. This is done before we drop privs, that
* way if the database only has root perms, then it can still
* be opened.
*
* \param parameters This is backend-specific (mysql, sqlite, etc)
*/
int db_open(char *parameters) {
int result;
DPRINTF(E_DBG,L_DB,"Opening database\n");
if(pthread_once(&db_initlock,db_init_once))
return -1;
result=db_current->dbs_open(parameters);
DPRINTF(E_DBG,L_DB,"Results: %d\n",result);
return result;
}
/**
* Initialize the database, including marking it for full reload if necessary.
*
* \param reload whether or not to do a full reload of the database
*/
int db_init(int reload) {
return db_current->dbs_init(reload);
}
/**
* Close the database.
*/
int db_deinit(void) {
return db_current->dbs_deinit();
}
/**
* return the current db revision. this is mostly to determine
* when its time to send an updated version to the client
*/
int db_revision(void) {
int revision;
db_readlock();
revision=db_revision_no;
db_unlock();
return revision;
}
/**
* is the db currently in scanning mode?
*/
int db_scanning(void) {
return db_is_scanning;
}
/**
* add (or update) a file
*/
int db_add(MP3FILE *pmp3) {
int retval;
int id;
id=db_get_id(pmp3->path);
db_writelock();
if(id) {
retval=db_current->dbs_update(pmp3);
} else {
retval=db_current->dbs_add(pmp3);
db_revision_no++;
}
db_unlock();
return retval;
}
/**
* start a db enumeration, based info in the DBQUERYINFO struct
*
* \param pinfo pointer to DBQUERYINFO struction
* \returns 0 on success, -1 on failure
*/
int db_enum_start(DBQUERYINFO *pinfo) {
int retval;
db_writelock();
retval=db_current->dbs_enum_start(pinfo);
if(retval) {
db_unlock();
return retval;
}
return 0;
}
/**
* get size info about the returned query. This implicitly calls
* db_<dbase>_enum_reset, so it should be positioned at the head
* of the list of returned items.
*/
int db_enum_size(DBQUERYINFO *pinfo, int *count) {
return db_current->dbs_enum_size(pinfo,count);
}
/**
* fetch the next item in the result set started by the db enum. this item
* will the the appropriate dmap item. It is the application's duty to free
* the dmap item.
*
* \param plen length of the dmap item returned
* \returns dmap item
*/
int db_enum_fetch(DBQUERYINFO *pinfo, unsigned char **pdmap) {
return db_current->dbs_enum_fetch(pinfo,pdmap);
}
/**
* reset the enum, without coming out the the db_writelock
*/
int db_enum_reset(DBQUERYINFO *pinfo) {
return db_current->dbs_enum_reset(pinfo);
}
/**
* finish the enumeration
*/
int db_enum_end(void) {
int retval;
retval=db_current->dbs_enum_end();
db_unlock();
return retval;
}
/**
* fetch a MP3FILE struct given an id. This will be done
* mostly only by the web interface, and when streaming a song
*
* \param id id of the item to get details for
*/
MP3FILE *db_fetch_item(int id) {
MP3FILE *retval;
db_readlock();
retval=db_current->dbs_fetch_item(id);
db_unlock();
return retval;
}
int db_start_scan(void) {
int retval;
db_writelock();
retval=db_current->dbs_start_scan();
db_is_scanning=1;
db_unlock();
return retval;
}
int db_end_scan(void) {
int retval;
db_writelock();
retval=db_current->dbs_end_scan();
db_is_scanning=0;
db_unlock();
return retval;
}
int db_last_modified(char *path) {
int id;
MP3FILE *pmp3;
int retval=0;
id=db_get_id(path);
pmp3=db_fetch_item(id);
if(pmp3) {
retval=pmp3->db_timestamp;
db_dispose_item(pmp3);
}
return retval;
}
int db_get_id(char *path) {
int retval;
db_readlock();
retval=db_current->dbs_get_id(path);
db_unlock();
return retval;
}
void db_dispose_item(MP3FILE *pmp3) {
return db_current->dbs_dispose_item(pmp3);
}
int db_get_count(CountType_t type) {
int retval;
db_readlock();
retval=db_current->dbs_get_count(type);
db_unlock();
return retval;
}
/*
* FIXME: clearly a stub
*/
int db_get_song_count() {
return db_get_count(countSongs);
}
int db_get_playlist_count() {
return db_get_count(countPlaylists);
}
/* These dmap functions arguably don't belong here, but with
* the database delivering dmap objects by preference over MP3FILE
* objects, it does make some amount of sense to be here
*/
/**
* add a character type to a dmap block (type 0x01)
*
* \param where where to serialize the dmap info
* \tag what four byte tag
* \value what character value
*/
int db_dmap_add_char(char *where, char *tag, char value) {
/* tag */
memcpy(where,tag,4);
/* len */
where[4]=where[5]=where[6]=0;
where[7]=1;
/* value */
where[8] = value;
return 9;
}
/**
* add a short type to a dmap block (type 0x03)
*
* \param where where to serialize the dmap info
* \tag what four byte tag
* \value what character value
*/
int db_dmap_add_short(char *where, char *tag, short value) {
/* tag */
memcpy(where,tag,4);
/* len */
where[4]=where[5]=where[6]=0;
where[7]=2;
/* value */
where[8] = (value >> 8) & 0xFF;
where[9] = value & 0xFF;
return 10;
}
/**
* add an int type to a dmap block (type 0x05)
*
* \param where where to serialize the dmap info
* \tag what four byte tag
* \value what character value
*/
int db_dmap_add_int(char *where, char *tag, int value) {
/* tag */
memcpy(where,tag,4);
/* len */
where[4]=where[5]=where[6]=0;
where[7]=4;
/* value */
where[8] = (value >> 24) & 0xFF;
where[9] = (value >> 16) & 0xFF;
where[10] = (value >> 8) & 0xFF;
where[11] = value & 0xFF;
return 12;
}
/**
* add a string type to a dmap block (type 0x09)
*
* \param where where to serialize the dmap info
* \tag what four byte tag
* \value what character value
*/
int db_dmap_add_string(char *where, char *tag, char *value) {
int len=strlen(value);
/* tag */
memcpy(where,tag,4);
/* length */
where[4]=(len >> 24) & 0xFF;
where[5]=(len >> 16) & 0xFF;
where[6]=(len >> 8) & 0xFF;
where[7]=len & 0xFF;
strncpy(where+8,value,strlen(value));
return 8 + strlen(value);
}
/**
* add a container type to a dmap block (type 0x0C)
*
* \param where where to serialize the dmap info
* \tag what four byte tag
* \value what character value
*/
int db_dmap_add_container(char *where, char *tag, int size) {
int len=size;
/* tag */
memcpy(where,tag,4);
/* length */
where[4]=(len >> 24) & 0xFF;
where[5]=(len >> 16) & 0xFF;
where[6]=(len >> 8) & 0xFF;
where[7]=len & 0xFF;
return 8;
}

162
src/db-generic.h Normal file
View File

@ -0,0 +1,162 @@
/*
* $Id$
* Header info for generic database
*
* Copyright (C) 2005 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _DB_GENERIC_H_
#define _DB_GENERIC_H_
#include "mp3-scanner.h" /** for MP3FILE */
typedef enum {
// generic meta data
metaItemId,
metaItemName,
metaItemKind,
metaPersistentId,
metaContainerItemId,
metaParentContainerId,
firstTypeSpecificMetaId,
// song meta data
metaSongAlbum = firstTypeSpecificMetaId,
metaSongArtist,
metaSongBPM, /* beats per minute */
metaSongBitRate,
metaSongComment,
metaSongCompilation,
metaSongComposer,
metaSongDataKind,
metaSongDataURL,
metaSongDateAdded,
metaSongDateModified,
metaSongDescription,
metaSongDisabled,
metaSongDiscCount,
metaSongDiscNumber,
metaSongEqPreset,
metaSongFormat,
metaSongGenre,
metaSongGrouping,
metaSongRelativeVolume,
metaSongSampleRate,
metaSongSize,
metaSongStartTime,
metaSongStopTime,
metaSongTime,
metaSongTrackCount,
metaSongTrackNumber,
metaSongUserRating,
metaSongYear
} MetaFieldName_t;
typedef enum {
queryTypeItems,
queryTypePlaylists,
queryTypePlaylistItems,
queryTypeBrowseArtists,
queryTypeBrowseAlbums,
queryTypeBrowseGenres,
queryTypeBrowseComposers
} QueryType_t;
typedef enum {
indexTypeNone,
indexTypeFirst,
indexTypeLast,
indexTypeSub
} IndexType_t;
typedef enum {
countSongs,
countPlaylists
} CountType_t;
typedef unsigned long long MetaField_t;
typedef struct tag_dbqueryinfo {
QueryType_t query_type;
IndexType_t index_type;
MetaField_t meta;
int index_low;
int index_high;
int playlist_id;
int db_id;
int session_id;
int uri_count;
char *uri_sections[10];
char *whereclause;
} DBQUERYINFO;
typedef struct
{
const char* tag;
MetaFieldName_t bit;
} METAMAP;
typedef struct tag_daap_items {
int type;
char *tag;
char *description;
} DAAP_ITEMS;
extern DAAP_ITEMS taglist[];
extern int db_set_backend(char *type);
extern int db_open(char *parameters);
extern int db_init(int reload);
extern int db_deinit(void);
extern int db_revision(void);
extern int db_add(MP3FILE *pmp3);
extern int db_enum_start(DBQUERYINFO *pinfo);
extern int db_enum_size(DBQUERYINFO *pinfo, int *count);
extern int db_enum_fetch(DBQUERYINFO *pinfo, unsigned char **pdmap);
extern int db_enum_reset(DBQUERYINFO *pinfo);
extern int db_enum_end(void);
extern int db_start_scan(void);
extern int db_end_scan(void);
extern int db_exists(char *path);
extern int db_scanning(void);
extern int db_last_modified(char *path);
extern MetaField_t db_encode_meta(char *meta);
extern int db_wantsmeta(MetaField_t meta, MetaFieldName_t fieldNo);
/* dmap helper functions */
extern int db_dmap_add_char(char *where, char *tag, char value);
extern int db_dmap_add_short(char *where, char *tag, short value);
extern int db_dmap_add_int(char *where, char *tag, int value);
extern int db_dmap_add_string(char *where, char *tag, char *value);
extern int db_dmap_add_container(char *where, char *tag, int size);
/* Holdover functions from old db interface...
* should these be removed? Refactored?
*/
extern int db_get_song_count(void);
extern int db_get_playlist_count(void);
extern MP3FILE *db_fetch_item(int id);
extern int db_get_id(char *path);
extern void db_dispose_item(MP3FILE *pmp3);
#endif /* _DB_GENERIC_H_ */

View File

@ -1,746 +0,0 @@
/*
* $Id$
* Implementation for simple in-memory linked list db
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define _XOPEN_SOURCE 600
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include "err.h"
#include "mp3-scanner.h"
/*
* Typedefs
*/
typedef struct tag_mp3record {
MP3FILE mp3file;
struct tag_mp3record *next;
} MP3RECORD;
typedef struct tag_playlistentry {
unsigned int id;
struct tag_playlistentry *next;
} DB_PLAYLISTENTRY;
typedef struct tag_playlist {
unsigned int id;
int songs;
char *name;
int is_smart;
struct tag_playlistentry *nodes;
struct tag_playlist *next;
} DB_PLAYLIST;
#define MAYBEFREE(a) { if((a)) free((a)); };
/*
* Globals
*/
MP3RECORD db_root;
DB_PLAYLIST db_playlists;
int db_version_no;
int db_update_mode=0;
int db_song_count;
int db_playlist_count=0;
pthread_rwlock_t db_rwlock; /* OSX doesn't have PTHREAD_RWLOCK_INITIALIZER */
pthread_once_t db_initlock=PTHREAD_ONCE_INIT;
/*
* Forwards
*/
int db_start_initial_update(void);
int db_end_initial_update(void);
int db_is_empty(void);
int db_open(char *parameters);
int db_init();
int db_deinit(void);
int db_version(void);
int db_add(MP3FILE *mp3file);
int db_add_playlist(unsigned int playlistid, char *name, int is_smart);
int db_add_playlist_song(unsigned int playlistid, unsigned int itemid);
MP3RECORD *db_enum_begin(void);
MP3FILE *db_enum(MP3RECORD **current);
int db_enum_end(void);
DB_PLAYLIST *db_playlist_enum_begin(void);
int db_playlist_enum(DB_PLAYLIST **current);
int db_playlist_enum_end(void);
int db_scanning(void);
DB_PLAYLISTENTRY *db_playlist_items_enum_begin(int playlistid);
int db_playlist_items_enum(DB_PLAYLISTENTRY **current);
int db_playlist_items_enum_end(void);
int db_get_song_count(void);
int db_get_playlist_count(void);
int db_get_playlist_is_smart(int playlistid);
int db_get_playlist_entry_count(int playlistid);
char *db_get_playlist_name(int playlistid);
MP3FILE *db_find(int id);
void db_freerecord(MP3RECORD *mp3record);
/*
* db_init_once
*
* Must dynamically initialize the rwlock, as Mac OSX 10.3 (at least)
* doesn't have a static initializer for rwlocks
*/
void db_init_once(void) {
pthread_rwlock_init(&db_rwlock,NULL);
}
/*
* db_open
*
* We'll wait for db_init
*/
void db_open(char *parameters) {
return 0;
}
/*
* db_init
*
* Initialize the database. For the in-memory db
* the parameters are insignificant
*/
int db_init(void) {
db_root.next=NULL;
db_version_no=1;
db_song_count=0;
db_playlists.next=NULL;
if(pthread_once(&db_initlock,db_init_once))
return -1;
pl_register();
return 0;
}
/*
* db_deinit
*
* Close the db, in this case freeing memory
*/
int db_deinit(void) {
MP3RECORD *current;
DB_PLAYLIST *plist;
DB_PLAYLISTENTRY *pentry;
while(db_root.next) {
current=db_root.next;
db_root.next=current->next;
db_freerecord(current);
}
while(db_playlists.next) {
plist=db_playlists.next;
db_playlists.next=plist->next;
free(plist->name);
/* free all the nodes */
while(plist->nodes) {
pentry=plist->nodes;
plist->nodes = pentry->next;
free(pentry);
}
free(plist);
}
return 0;
}
/*
* db_version
*
* return the db version
*/
int db_version(void) {
return db_version_no;
}
/*
* db_scanning
*
* is the db doing a background scan?
*/
int db_scanning(void) {
return 0;
}
/*
* db_start_initial_update
*
* Set the db to bulk import mode
*/
int db_start_initial_update(void) {
db_update_mode=1;
return 0;
}
/*
* db_end_initial_update
*
* Take the db out of bulk import mode
*/
int db_end_initial_update(void) {
db_update_mode=0;
return 0;
}
/*
* db_is_empty
*
* See if the db is empty or not -- that is, should
* the scanner start up in bulk update mode or in
* background update mode
*/
int db_is_empty(void) {
return !db_root.next;
}
/*
* db_add_playlist
*
* Add a new playlist
*/
int db_add_playlist(unsigned int playlistid, char *name, int is_smart) {
int err;
DB_PLAYLIST *pnew;
pnew=(DB_PLAYLIST*)malloc(sizeof(DB_PLAYLIST));
if(!pnew)
return -1;
pnew->name=strdup(name);
pnew->id=playlistid;
pnew->is_smart=is_smart;
pnew->nodes=NULL;
pnew->songs=0;
if(!pnew->name) {
free(pnew);
return -1;
}
DPRINTF(E_DBG,L_DB,"Adding new playlist %s\n",name);
if((err=pthread_rwlock_wrlock(&db_rwlock))) {
DPRINTF(E_WARN,L_DB,"cannot lock wrlock in db_add\n");
free(pnew->name);
free(pnew);
errno=err;
return -1;
}
/* we'll update playlist count when we add a song! */
// db_playlist_count++;
pnew->next=db_playlists.next;
db_playlists.next=pnew;
if(!db_update_mode) {
db_version_no++;
}
pthread_rwlock_unlock(&db_rwlock);
DPRINTF(E_DBG,L_DB,"Added playlist\n");
return 0;
}
/*
* db_add_playlist_song
*
* Add a song to a particular playlist
*/
int db_add_playlist_song(unsigned int playlistid, unsigned int itemid) {
DB_PLAYLIST *current;
DB_PLAYLISTENTRY *pnew;
int err;
pnew=(DB_PLAYLISTENTRY*)malloc(sizeof(DB_PLAYLISTENTRY));
if(!pnew)
return -1;
pnew->id=itemid;
pnew->next=NULL;
DPRINTF(E_DBG,L_DB,"Adding new playlist item\n");
if((err=pthread_rwlock_wrlock(&db_rwlock))) {
DPRINTF(E_WARN,L_DB,"cannot lock wrlock in db_add\n");
free(pnew);
errno=err;
return -1;
}
current=db_playlists.next;
while(current && (current->id != playlistid))
current=current->next;
if(!current) {
DPRINTF(E_WARN,L_DB,"Could not find playlist attempting to add to\n");
pthread_rwlock_unlock(&db_rwlock);
free(pnew);
return -1;
}
if(!current->songs)
db_playlist_count++;
current->songs++;
pnew->next = current->nodes;
current->nodes = pnew;
if(!db_update_mode) {
db_version_no++;
}
pthread_rwlock_unlock(&db_rwlock);
DPRINTF(E_DBG,L_DB,"Added playlist item\n");
return 0;
}
/*
* db_add
*
* add an MP3 file to the database.
*/
int db_add(MP3FILE *mp3file) {
int err;
int g;
MP3RECORD *pnew;
DPRINTF(E_DBG,L_DB,"Adding %s\n",mp3file->path);
if((pnew=(MP3RECORD*)malloc(sizeof(MP3RECORD))) == NULL) {
free(pnew);
errno=ENOMEM;
return -1;
}
memset(pnew,0,sizeof(MP3RECORD));
memcpy(&pnew->mp3file,mp3file,sizeof(MP3FILE));
g=(int) pnew->mp3file.path=strdup(mp3file->path);
g = g && (pnew->mp3file.fname=strdup(mp3file->fname));
if(mp3file->title)
g = g && (pnew->mp3file.title=strdup(mp3file->title));
if(mp3file->artist)
g = g && (pnew->mp3file.artist=strdup(mp3file->artist));
if(mp3file->album)
g = g && (pnew->mp3file.album=strdup(mp3file->album));
if(mp3file->genre)
g = g && (pnew->mp3file.genre=strdup(mp3file->genre));
if(mp3file->comment)
g = g && (pnew->mp3file.comment=strdup(mp3file->comment));
if(mp3file->type)
g = g && (pnew->mp3file.type=strdup(mp3file->type));
if(mp3file->composer)
g = g && (pnew->mp3file.composer=strdup(mp3file->composer));
if(mp3file->orchestra)
g = g && (pnew->mp3file.orchestra=strdup(mp3file->orchestra));
if(mp3file->conductor)
g = g && (pnew->mp3file.conductor=strdup(mp3file->conductor));
if(mp3file->grouping)
g = g && (pnew->mp3file.grouping=strdup(mp3file->grouping));
if(!g) {
DPRINTF(E_WARN,L_DB,"Malloc error in db_add\n");
db_freerecord(pnew);
errno=ENOMEM;
return -1;
}
if((err=pthread_rwlock_wrlock(&db_rwlock))) {
DPRINTF(E_WARN,L_DB,"cannot lock wrlock in db_add\n");
db_freerecord(pnew);
errno=err;
return -1;
}
make_composite_tags(&pnew->mp3file);
pnew->next=db_root.next;
db_root.next=pnew;
if(!db_update_mode) {
db_version_no++;
}
db_song_count++;
pthread_rwlock_unlock(&db_rwlock);
DPRINTF(E_DBG,L_DB,"Added file\n");
return 0;
}
/*
* db_freerecord
*
* free a complete mp3record
*/
void db_freerecord(MP3RECORD *mp3record) {
MAYBEFREE(mp3record->mp3file.path);
MAYBEFREE(mp3record->mp3file.fname);
MAYBEFREE(mp3record->mp3file.title);
MAYBEFREE(mp3record->mp3file.artist);
MAYBEFREE(mp3record->mp3file.album);
MAYBEFREE(mp3record->mp3file.genre);
MAYBEFREE(mp3record->mp3file.comment);
MAYBEFREE(mp3record->mp3file.type);
MAYBEFREE(mp3record->mp3file.composer);
MAYBEFREE(mp3record->mp3file.orchestra);
MAYBEFREE(mp3record->mp3file.conductor);
MAYBEFREE(mp3record->mp3file.grouping);
MAYBEFREE(mp3record->mp3file.description);
free(mp3record);
}
/*
* db_enum_begin
*
* Begin to walk through an enum of
* the database.
*
* this should be done quickly, as we'll be holding
* a reader lock on the db
*/
MP3RECORD *db_enum_begin(void) {
int err;
if((err=pthread_rwlock_rdlock(&db_rwlock))) {
DPRINTF(E_FATAL,L_DB,"Cannot lock rwlock\n");
errno=err;
return NULL;
}
return db_root.next;
}
/*
* db_playlist_enum_begin
*
* Start enumerating playlists
*/
DB_PLAYLIST *db_playlist_enum_begin(void) {
int err;
DB_PLAYLIST *current;
if((err=pthread_rwlock_rdlock(&db_rwlock))) {
DPRINTF(E_FATAL,L_DB,"Cannot lock rwlock\n");
errno=err;
return NULL;
}
/* find first playlist with a song in it! */
current=db_playlists.next;
while(current && (!current->songs))
current=current->next;
return current;
}
/*
* db_playlist_items_enum_begin
*
* Start enumerating playlist items
*/
DB_PLAYLISTENTRY *db_playlist_items_enum_begin(int playlistid) {
DB_PLAYLIST *current;
int err;
if((err=pthread_rwlock_rdlock(&db_rwlock))) {
DPRINTF(E_FATAL,L_DB,"Cannot lock rwlock\n");
errno=err;
return NULL;
}
current=db_playlists.next;
while(current && (current->id != playlistid))
current=current->next;
if(!current)
return NULL;
return current->nodes;
}
/*
* db_enum
*
* Walk to the next entry
*/
MP3FILE *db_enum(MP3RECORD **current) {
MP3FILE *retval;
if(*current) {
retval=&((*current)->mp3file);
*current=(*current)->next;
return retval;
}
return NULL;
}
/*
* db_playlist_enum
*
* walk to the next entry
*/
int db_playlist_enum(DB_PLAYLIST **current) {
int retval;
DB_PLAYLIST *p;
if(*current) {
retval = (*current)->id;
p=*current;
p=p->next;
while(p && (!p->songs))
p=p->next;
*current=p;
return retval;
}
return -1;
}
/*
* db_playlist_items_enum
*
* walk to the next entry
*/
int db_playlist_items_enum(DB_PLAYLISTENTRY **current) {
int retval;
if(*current) {
retval = (*current)->id;
*current=(*current)->next;
return retval;
}
return -1;
}
/*
* db_enum_end
*
* quit walking the database (and give up reader lock)
*/
int db_enum_end(void) {
return pthread_rwlock_unlock(&db_rwlock);
}
/*
* db_playlist_enum_end
*
* quit walking the database
*/
int db_playlist_enum_end(void) {
return pthread_rwlock_unlock(&db_rwlock);
}
/*
* db_playlist_items_enum_end
*
* Quit walking the database
*/
int db_playlist_items_enum_end(void) {
return pthread_rwlock_unlock(&db_rwlock);
}
/*
* db_find
*
* Find a MP3FILE entry based on file id
*/
MP3FILE *db_find(int id) {
MP3RECORD *current=db_root.next;
while((current) && (current->mp3file.id != id)) {
current=current->next;
}
if(!current)
return NULL;
return &current->mp3file;
}
/*
* db_get_playlist_count
*
* return the number of playlists
*/
int db_get_playlist_count(void) {
return db_playlist_count;
}
/*
* db_get_song_count
*
* return the number of songs in the database. Used for the /database
* request
*/
int db_get_song_count(void) {
return db_song_count;
}
/*
* db_get_playlist_is_smart
*
* return whether or not the playlist is a "smart" playlist
*/
int db_get_playlist_is_smart(int playlistid) {
DB_PLAYLIST *current;
int err;
int result;
if((err=pthread_rwlock_rdlock(&db_rwlock))) {
DPRINTF(E_FATAL,L_DB,"Cannot lock rwlock\n");
errno=err;
return -1;
}
current=db_playlists.next;
while(current && (current->id != playlistid))
current=current->next;
if(!current) {
result=0;
} else {
result=current->is_smart;
}
pthread_rwlock_unlock(&db_rwlock);
return result;
}
/*
* db_get_playlist_entry_count
*
* return the number of songs in a particular playlist
*/
int db_get_playlist_entry_count(int playlistid) {
int count;
DB_PLAYLIST *current;
int err;
if((err=pthread_rwlock_rdlock(&db_rwlock))) {
DPRINTF(E_FATAL,L_DB,"Cannot lock rwlock\n");
errno=err;
return -1;
}
current=db_playlists.next;
while(current && (current->id != playlistid))
current=current->next;
if(!current) {
count = -1;
} else {
count = current->songs;
}
pthread_rwlock_unlock(&db_rwlock);
return count;
}
/*
* db_get_playlist_name
*
* return the name of a playlist
*
* FIXME: Small race here
*/
char *db_get_playlist_name(int playlistid) {
char *name;
DB_PLAYLIST *current;
int err;
if((err=pthread_rwlock_rdlock(&db_rwlock))) {
DPRINTF(E_FATAL,L_DB,"Cannot lock rwlock\n");
errno=err;
return NULL;
}
current=db_playlists.next;
while(current && (current->id != playlistid))
current=current->next;
if(!current) {
name = NULL;
} else {
name = current->name;
}
pthread_rwlock_unlock(&db_rwlock);
return name;
}
/*
* db_exists
*
* Check if a particular id is in the database
*/
int db_exists(int id) {
MP3FILE *pmp3;
pmp3=db_find(id);
return pmp3 ? 1 : 0;
}
/*
* db_last_modified
*
* See when the file was modified (according to the database)
*
* This is merely a stub for the in-memory db
*/
int db_last_modified(int id) {
return 0;
}

View File

@ -1,69 +0,0 @@
/*
* $Id$
* Header info for in-memory db
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _DB_MEMORY_H_
#define _DB_MEMORY_H_
#include "mp3-scanner.h"
typedef void* ENUMHANDLE;
extern int db_start_initial_update(void);
extern int db_end_initial_update(void);
extern int db_is_empty(void);
extern int db_open(char *parameters, int reload);
extern int db_init(void);
extern int db_deinit(void);
extern int db_version(void);
extern int db_add(MP3FILE *mp3file);
extern int db_delete(unsigned long int id);
extern int db_add_playlist(unsigned long int playlistid, char *name, int file_time, int is_smart);
extern int db_add_playlist_song(unsigned long int playlistid, unsigned long int itemid);
extern int db_delete_playlist(unsigned long int playlistid);
extern ENUMHANDLE db_enum_begin(void);
extern MP3FILE *db_enum(ENUMHANDLE *handle);
extern int db_enum_end(ENUMHANDLE handle);
extern MP3FILE *db_find(unsigned long int id);
extern void db_dispose(MP3FILE *pmp3); /* must be called after a db_find */
extern int db_get_song_count(void);
extern int db_get_playlist_count(void);
extern int db_get_playlist_entry_count(unsigned long int playlistid);
extern int db_get_playlist_is_smart(unsigned long int playlistid);
extern ENUMHANDLE db_playlist_enum_begin(void);
extern int db_playlist_enum(ENUMHANDLE *current);
extern int db_playlist_enum_end(ENUMHANDLE handle);
extern ENUMHANDLE db_playlist_items_enum_begin(unsigned long int playlistid);
extern int db_playlist_items_enum(ENUMHANDLE *current);
extern int db_playlist_items_enum_end(ENUMHANDLE handle);
extern char *db_get_playlist_name(unsigned long int playlistid);
extern int db_playlist_last_modified(unsigned long int playlistid);
extern int db_scanning(void);
/* For persistant databases only */
extern int db_exists(unsigned long int id);
extern int db_last_modified(unsigned long int id);
#endif /* _DB_MEMORY_H_ */

1045
src/dbs-sqlite.c Normal file

File diff suppressed because it is too large Load Diff

42
src/dbs-sqlite.h Normal file
View File

@ -0,0 +1,42 @@
/*
* $Id$
* sqlite-specific db implementation
*
* Copyright (C) 2005 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _DBS_SQLITE_H_
#define _DBS_SQLITE_H_
extern int db_sqlite_open(char *parameters);
extern int db_sqlite_init(int reload);
extern int db_sqlite_deinit(void);
extern int db_sqlite_add(MP3FILE *pmp3);
extern int db_sqlite_update(MP3FILE *pmp3);
extern int db_sqlite_enum_start(DBQUERYINFO *pinfo);
extern int db_sqlite_enum_size(DBQUERYINFO *pinfo, int *count);
extern int db_sqlite_enum_fetch(DBQUERYINFO *pinfo, unsigned char **pdmap);
extern int db_sqlite_enum_reset(DBQUERYINFO *pinfo);
extern int db_sqlite_enum_end(void);
extern int db_sqlite_get_id(char *path);
extern int db_sqlite_start_scan(void);
extern int db_sqlite_end_scan(void);
extern int db_sqlite_get_count(CountType_t type);
extern MP3FILE *db_sqlite_fetch_item(int id);
extern void db_sqlite_dispose_item(MP3FILE *pmp3);
#endif /* _DBS_SQLITE_H_ */

752
src/dispatch.c Normal file
View File

@ -0,0 +1,752 @@
/*
* $Id$
* daap handler functions and dispatch code
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "db-generic.h"
#include "configfile.h"
#include "err.h"
#include "mp3-scanner.h"
#include "webserver.h"
#include "ssc.h"
#include "dynamic-art.h"
#include "restart.h"
#include "daapd.h"
/* Forwards */
static void dispatch_server_info(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_login(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_content_codes(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_update(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_dbinfo(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_playlistitems(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_browse(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_playlists(WS_CONNINFO *pqsc, DBQUERYINFO *pqi);
static void dispatch_items(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
static void dispatch_logout(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
/**
* Handles authentication for the daap server. This isn't the
* authenticator for the web admin page, but rather the iTunes
* authentication when trying to connect to the server. Note that most
* of this is actually handled in the web server registration, which
* decides when to apply the authentication or not. If you mess with
* when and where the webserver applies auth or not, you'll likely
* break something. It seems that some requests must be authed, and others
* not. If you apply authentication somewhere that iTunes doesn't expect
* it, it happily disconnects.
*
* \param username The username passed by iTunes
* \param password The password passed by iTunes
* \returns 1 if auth successful, 0 otherwise
*/
int daap_auth(char *username, char *password) {
if((password == NULL) &&
((config.readpassword == NULL) || (strlen(config.readpassword)==0)))
return 1;
if(password == NULL)
return 0;
return !strcasecmp(password,config.readpassword);
}
void daap_handler(WS_CONNINFO *pwsc) {
DBQUERYINFO *pqi;
char *token, *string, *save;
pqi=(DBQUERYINFO*)malloc(sizeof(DBQUERYINFO));
if(!pqi) {
ws_returnerror(pwsc,500,"Internal server error: out of memory!");
return;
}
memset(pqi,0x00,sizeof(DBQUERYINFO));
/* Add some default headers */
ws_addresponseheader(pwsc,"Accept-Ranges","bytes");
ws_addresponseheader(pwsc,"DAAP-Server","mt-daapd/" VERSION);
ws_addresponseheader(pwsc,"Content-Type","application/x-dmap-tagged");
if(ws_getvar(pwsc,"session-id"))
pqi->session_id = atoi(ws_getvar(pwsc,"session-id"));
/* tokenize the uri for easier decoding */
string=(pwsc->uri)+1;
while((token=strtok_r(string,"/",&save))) {
string=NULL;
pqi->uri_sections[pqi->uri_count++] = token;
}
/* Start dispatching */
if(!strcasecmp(pqi->uri_sections[0],"server-info"))
return dispatch_server_info(pwsc,pqi);
if(!strcasecmp(pqi->uri_sections[0],"content-codes"))
return dispatch_content_codes(pwsc,pqi);
if(!strcasecmp(pqi->uri_sections[0],"login"))
return dispatch_login(pwsc,pqi);
if(!strcasecmp(pqi->uri_sections[0],"update"))
return dispatch_update(pwsc,pqi);
if(!strcasecmp(pqi->uri_sections[0],"logout"))
return dispatch_logout(pwsc,pqi);
/*
* /databases/id/items
* /databases/id/containers
* /databases/id/containers/id/items
* /databases/id/browse/category
* /databases/id/items/id.mp3
*/
if(!strcasecmp(pqi->uri_sections[0],"databases")) {
if(pqi->uri_count == 1) {
return dispatch_dbinfo(pwsc,pqi);
}
pqi->db_id=atoi(pqi->uri_sections[1]);
if(pqi->uri_count == 3) {
if(!strcasecmp(pqi->uri_sections[2],"items"))
return dispatch_items(pwsc,pqi);
if(!strcasecmp(pqi->uri_sections[2],"containers"))
return dispatch_playlists(pwsc,pqi);
pwsc->close=1;
free(pqi);
ws_returnerror(pwsc,404,"Page not found");
return;
}
if(pqi->uri_count == 4) {
if(!strcasecmp(pqi->uri_sections[2],"browse"))
return dispatch_browse(pwsc,pqi);
if(!strcasecmp(pqi->uri_sections[2],"items"))
return dispatch_stream(pwsc,pqi);
pwsc->close=1;
free(pqi);
ws_returnerror(pwsc,404,"Page not found");
return;
}
if(pqi->uri_count == 5) {
if((!strcasecmp(pqi->uri_sections[2],"containers")) &&
(!strcasecmp(pqi->uri_sections[4],"items"))) {
pqi->playlist_id=atoi(pqi->uri_sections[3]);
return dispatch_playlistitems(pwsc,pqi);
}
}
}
pwsc->close=1;
free(pqi);
ws_returnerror(pwsc,404,"Page not found");
return;
}
void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
MP3FILE *pmp3;
FILE *file_ptr;
int file_fd;
char *real_path;
int bytes_copied;
off_t real_len;
off_t file_len;
off_t offset=0;
long img_size;
struct stat sb;
int img_fd;
int item;
/* stream out the song */
pwsc->close=1;
item=atoi(pqi->uri_sections[3]);
if(ws_getrequestheader(pwsc,"range")) {
offset=(off_t)atol(ws_getrequestheader(pwsc,"range") + 6);
}
pmp3=db_fetch_item(item);
if(!pmp3) {
DPRINTF(E_LOG,L_DAAP|L_WS|L_DB,"Could not find requested item %lu\n",item);
ws_returnerror(pwsc,404,"File Not Found");
} else if ((real_path=server_side_convert_path(pmp3->path)) != NULL) {
/************************
* Server side conversion
************************/
DPRINTF(E_WARN,L_WS,"Thread %d: Autoconvert file %s for client\n",
pwsc->threadno,real_path);
file_ptr = server_side_convert_open(real_path,
offset,
pmp3->song_length);
if (file_ptr) {
file_fd = fileno(file_ptr);
} else {
file_fd = -1;
}
if(file_fd == -1) {
if (file_ptr) {
server_side_convert_close(file_ptr);
}
pwsc->error=errno;
DPRINTF(E_WARN,L_WS,
"Thread %d: Error opening %s for conversion\n",
pwsc->threadno,real_path);
ws_returnerror(pwsc,404,"Not found");
config_set_status(pwsc,pqi->session_id,NULL);
db_dispose_item(pmp3);
free(pmp3);
free(real_path);
} else {
if(pmp3->type)
ws_addresponseheader(pwsc,"Content-Type","audio/%s",
pmp3->type);
// Also content-length -heade would be nice, but since
// we don't really know it here, so let's leave it out.
ws_addresponseheader(pwsc,"Connection","Close");
if(!offset)
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
else {
// This is actually against the protocol, since
// range MUST be explicit according to HTTP-standard
// Seems to work at least with iTunes.
ws_addresponseheader(pwsc,
"Content-Range","bytes %ld-*/*",
(long)offset);
ws_writefd(pwsc,"HTTP/1.1 206 Partial Content\r\n");
}
ws_emitheaders(pwsc);
config_set_status(pwsc,pqi->session_id,
"Streaming file via convert filter '%s'",
pmp3->fname);
DPRINTF(E_LOG,L_WS,
"Session %d: Streaming file '%s' to %s (offset %ld)\n",
pqi->session_id,pmp3->fname, pwsc->hostname,(long)offset);
if(!offset)
config.stats.songs_served++; /* FIXME: remove stat races */
if((bytes_copied=copyfile(file_fd,pwsc->fd)) == -1) {
DPRINTF(E_INF,L_WS,
"Error copying converted file to remote... %s\n",
strerror(errno));
} else {
DPRINTF(E_INF,L_WS,
"Finished streaming converted file to remote\n");
}
server_side_convert_close(file_ptr);
config_set_status(pwsc,pqi->session_id,NULL);
db_dispose_item(pmp3);
free(pmp3);
free(real_path);
}
} else {
/**********************
* stream file normally
**********************/
file_fd=r_open2(pmp3->path,O_RDONLY);
if(file_fd == -1) {
pwsc->error=errno;
DPRINTF(E_WARN,L_WS,"Thread %d: Error opening %s: %s\n",
pwsc->threadno,pmp3->path,strerror(errno));
ws_returnerror(pwsc,404,"Not found");
config_set_status(pwsc,pqi->session_id,NULL);
db_dispose_item(pmp3);
free(pmp3);
} else {
real_len=lseek(file_fd,0,SEEK_END);
lseek(file_fd,0,SEEK_SET);
/* Re-adjust content length for cover art */
if((config.artfilename) &&
((img_fd=da_get_image_fd(pmp3->path)) != -1)) {
fstat(img_fd, &sb);
img_size = sb.st_size;
if (strncasecmp(pmp3->type,"mp3",4) ==0) {
/*PENDING*/
} else if (strncasecmp(pmp3->type, "m4a", 4) == 0) {
real_len += img_size + 24;
if (offset > img_size + 24) {
offset -= img_size + 24;
}
}
}
file_len = real_len - offset;
DPRINTF(E_DBG,L_WS,"Thread %d: Length of file (remaining) is %ld\n",
pwsc->threadno,(long)file_len);
// DWB: fix content-type to correctly reflect data
// content type (dmap tagged) should only be used on
// dmap protocol requests, not the actually song data
if(pmp3->type)
ws_addresponseheader(pwsc,"Content-Type","audio/%s",pmp3->type);
ws_addresponseheader(pwsc,"Content-Length","%ld",(long)file_len);
ws_addresponseheader(pwsc,"Connection","Close");
if(!offset)
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
else {
ws_addresponseheader(pwsc,"Content-Range","bytes %ld-%ld/%ld",
(long)offset,(long)real_len,
(long)real_len+1);
ws_writefd(pwsc,"HTTP/1.1 206 Partial Content\r\n");
}
ws_emitheaders(pwsc);
config_set_status(pwsc,pqi->session_id,"Streaming file '%s'",pmp3->fname);
DPRINTF(E_LOG,L_WS,"Session %d: Streaming file '%s' to %s (offset %d)\n",
pqi->session_id,pmp3->fname, pwsc->hostname,(long)offset);
if(!offset)
config.stats.songs_served++; /* FIXME: remove stat races */
if((config.artfilename) &&
(!offset) &&
((img_fd=da_get_image_fd(pmp3->path)) != -1)) {
if (strncasecmp(pmp3->type,"mp3",4) ==0) {
DPRINTF(E_INF,L_WS|L_ART,"Dynamic add artwork to %s (fd %d)\n",
pmp3->fname, img_fd);
da_attach_image(img_fd, pwsc->fd, file_fd, offset);
} else if (strncasecmp(pmp3->type, "m4a", 4) == 0) {
DPRINTF(E_INF,L_WS|L_ART,"Dynamic add artwork to %s (fd %d)\n",
pmp3->fname, img_fd);
da_aac_attach_image(img_fd, pwsc->fd, file_fd, offset);
}
} else if(offset) {
DPRINTF(E_INF,L_WS,"Seeking to offset %ld\n",(long)offset);
lseek(file_fd,offset,SEEK_SET);
}
if((bytes_copied=copyfile(file_fd,pwsc->fd)) == -1) {
DPRINTF(E_INF,L_WS,"Error copying file to remote... %s\n",
strerror(errno));
} else {
DPRINTF(E_INF,L_WS,"Finished streaming file to remote: %d bytes\n",
bytes_copied);
}
config_set_status(pwsc,pqi->session_id,NULL);
r_close(file_fd);
db_dispose_item(pmp3);
free(pmp3);
}
}
free(pqi);
}
void dispatch_playlistitems(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
char items_response[61];
char *current=items_response;
int song_count;
int list_length;
unsigned char *block;
if(ws_getvar(pwsc,"meta")) {
pqi->meta = db_encode_meta(ws_getvar(pwsc,"meta"));
} else {
pqi->meta = ((1ll << metaItemId) |
(1ll << metaItemName) |
(1ll << metaItemKind) |
(1ll << metaContainerItemId) |
(1ll << metaParentContainerId));
}
/* should build the query string here, too */
pqi->whereclause = NULL;
pqi->query_type = queryTypePlaylistItems;
pqi->index_type=indexTypeNone;
if(db_enum_start(pqi)) {
DPRINTF(E_LOG,L_DAAP,"Could not start enum\n");
ws_returnerror(pwsc,500,"Internal server error: out of memory!");
return;
}
list_length=db_enum_size(pqi,&song_count);
DPRINTF(E_DBG,L_DAAP,"Item enum: got %d songs, dmap size: %d\n",song_count,list_length);
current += db_dmap_add_container(current,"apso",list_length + 53);
current += db_dmap_add_int(current,"mstt",200); /* 12 */
current += db_dmap_add_char(current,"muty",0); /* 9 */
current += db_dmap_add_int(current,"mtco",song_count); /* 12 */
current += db_dmap_add_int(current,"mrco",song_count); /* 12 */
current += db_dmap_add_container(current,"mlcl",list_length);
ws_addresponseheader(pwsc,"Content-Length","%d",61+list_length);
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_emitheaders(pwsc);
r_write(pwsc->fd,items_response,61);
while((list_length=db_enum_fetch(pqi,&block)) > 0) {
DPRINTF(E_DBG,L_DAAP,"Got block of size %d\n",list_length);
r_write(pwsc->fd,block,list_length);
free(block);
}
DPRINTF(E_DBG,L_DAAP,"Done enumerating.\n");
db_enum_end();
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
return;
}
void dispatch_browse(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
return;
}
void dispatch_playlists(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
char playlist_response[61];
char *current=playlist_response;
int pl_count;
int list_length;
unsigned char *block;
/* currently, this is ignored for playlist queries */
if(ws_getvar(pwsc,"meta")) {
pqi->meta = db_encode_meta(ws_getvar(pwsc,"meta"));
} else {
pqi->meta = (MetaField_t) -1ll;
}
/* should build the query string here, too */
pqi->whereclause = NULL;
pqi->query_type = queryTypePlaylists;
pqi->index_type=indexTypeNone;
if(db_enum_start(pqi)) {
DPRINTF(E_LOG,L_DAAP,"Could not start enum\n");
ws_returnerror(pwsc,500,"Internal server error: out of memory!");
return;
}
list_length=db_enum_size(pqi,&pl_count);
DPRINTF(E_DBG,L_DAAP,"Item enum: got %d playlists, dmap size: %d\n",pl_count,list_length);
current += db_dmap_add_container(current,"aply",list_length + 53);
current += db_dmap_add_int(current,"mstt",200); /* 12 */
current += db_dmap_add_char(current,"muty",0); /* 9 */
current += db_dmap_add_int(current,"mtco",pl_count); /* 12 */
current += db_dmap_add_int(current,"mrco",pl_count); /* 12 */
current += db_dmap_add_container(current,"mlcl",list_length);
ws_addresponseheader(pwsc,"Content-Length","%d",61+list_length);
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_emitheaders(pwsc);
r_write(pwsc->fd,playlist_response,61);
while((list_length=db_enum_fetch(pqi,&block)) > 0) {
DPRINTF(E_DBG,L_DAAP,"Got block of size %d\n",list_length);
r_write(pwsc->fd,block,list_length);
free(block);
}
DPRINTF(E_DBG,L_DAAP,"Done enumerating.\n");
db_enum_end();
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
return;
}
void dispatch_items(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
char items_response[61];
char *current=items_response;
int song_count;
int list_length;
unsigned char *block;
if(ws_getvar(pwsc,"meta")) {
pqi->meta = db_encode_meta(ws_getvar(pwsc,"meta"));
} else {
pqi->meta = (MetaField_t) -1ll;
}
/* should build the query string here, too */
pqi->whereclause = NULL;
pqi->query_type = queryTypeItems;
pqi->index_type=indexTypeNone;
if(db_enum_start(pqi)) {
DPRINTF(E_LOG,L_DAAP,"Could not start enum\n");
ws_returnerror(pwsc,500,"Internal server error: out of memory!");
return;
}
list_length=db_enum_size(pqi,&song_count);
DPRINTF(E_DBG,L_DAAP,"Item enum: got %d songs, dmap size: %d\n",song_count,list_length);
current += db_dmap_add_container(current,"adbs",list_length + 53);
current += db_dmap_add_int(current,"mstt",200); /* 12 */
current += db_dmap_add_char(current,"muty",0); /* 9 */
current += db_dmap_add_int(current,"mtco",song_count); /* 12 */
current += db_dmap_add_int(current,"mrco",song_count); /* 12 */
current += db_dmap_add_container(current,"mlcl",list_length);
ws_addresponseheader(pwsc,"Content-Length","%d",61+list_length);
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_emitheaders(pwsc);
r_write(pwsc->fd,items_response,61);
while((list_length=db_enum_fetch(pqi,&block)) > 0) {
DPRINTF(E_DBG,L_DAAP,"Got block of size %d\n",list_length);
r_write(pwsc->fd,block,list_length);
free(block);
}
DPRINTF(E_DBG,L_DAAP,"Done enumerating.\n");
db_enum_end();
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
return;
}
void dispatch_update(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
char update_response[32];
int clientver=1;
fd_set rset;
struct timeval tv;
int result;
char *current=update_response;
DPRINTF(E_DBG,L_DAAP,"Preparing to send update response\n");
if(ws_getvar(pwsc,"revision-number")) {
clientver=atoi(ws_getvar(pwsc,"revision-number"));
}
while(clientver == db_revision()) {
FD_ZERO(&rset);
FD_SET(pwsc->fd,&rset);
tv.tv_sec=30;
tv.tv_usec=0;
result=select(pwsc->fd+1,&rset,NULL,NULL,&tv);
if(FD_ISSET(pwsc->fd,&rset)) {
/* can't be ready for read, must be error */
DPRINTF(E_DBG,L_DAAP,"Socket closed?\n");
return;
}
}
/* otherwise, send the info about this version */
current += db_dmap_add_container(current,"mupd",24);
current += db_dmap_add_int(current,"mstt",200); /* 12 */
current += db_dmap_add_int(current,"musr",db_revision()); /* 12 */
ws_addresponseheader(pwsc,"Content-Length","32");
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_emitheaders(pwsc);
r_write(pwsc->fd,update_response,32);
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
}
void dispatch_dbinfo(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
char dbinfo_response[255]; /* FIXME */
char *current = dbinfo_response;
int namelen;
namelen=strlen(config.servername);
current += db_dmap_add_container(current,"avdb",105 + namelen);
current += db_dmap_add_int(current,"mstt",200); /* 12 */
current += db_dmap_add_char(current,"muty",0); /* 9 */
current += db_dmap_add_int(current,"mtco",1); /* 12 */
current += db_dmap_add_int(current,"mrco",1); /* 12 */
current += db_dmap_add_container(current,"mlcl",52 + namelen);
current += db_dmap_add_container(current,"mlit",44 + namelen);
current += db_dmap_add_int(current,"miid",1); /* 12 */
current += db_dmap_add_string(current,"minm",config.servername); /* 8 + namelen */
current += db_dmap_add_int(current,"mimc",db_get_song_count()); /* 12 */
current += db_dmap_add_int(current,"mctc",db_get_playlist_count()); /* 12 */
ws_addresponseheader(pwsc,"Content-Length","%d",113 + namelen);
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_emitheaders(pwsc);
r_write(pwsc->fd,dbinfo_response,113 + namelen);
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
return;
}
void dispatch_logout(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
ws_returnerror(pwsc,204,"Logout Successful");
}
void dispatch_login(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
char login_response[32];
char *current = login_response;
int session;
session = config_get_next_session();
current += db_dmap_add_container(current,"mlog",24);
current += db_dmap_add_int(current,"mstt",200); /* 12 */
current += db_dmap_add_int(current,"mlid",session); /* 12 */
ws_addresponseheader(pwsc,"Content-Length","32");
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_emitheaders(pwsc);
r_write(pwsc->fd,login_response,32);
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
return;
}
void dispatch_content_codes(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
char content_codes[20];
char mdcl[256]; /* FIXME: Don't make this static */
int len;
DAAP_ITEMS *dicurrent;
char *current=content_codes;
dicurrent=taglist;
len=0;
while(dicurrent->type) {
len += (8 + 12 + 10 + 8 + strlen(dicurrent->description));
dicurrent++;
}
current += db_dmap_add_container(current,"mccr",len + 12);
current += db_dmap_add_int(current,"mstt",200);
ws_addresponseheader(pwsc,"Content-Length","%d",len+20);
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_emitheaders(pwsc);
r_write(pwsc->fd,content_codes,20);
dicurrent=taglist;
while(dicurrent->type) {
current=mdcl;
len = 12 + 10 + 8 + strlen(dicurrent->description);
current += db_dmap_add_container(current,"mdcl",len);
current += db_dmap_add_string(current,"mcnm",dicurrent->tag); /* 12 */
current += db_dmap_add_string(current,"mcna",dicurrent->description); /* 8 + descr */
current += db_dmap_add_short(current,"mcty",dicurrent->type); /* 10 */
r_write(pwsc->fd,mdcl,len+8);
dicurrent++;
}
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
}
void dispatch_server_info(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) {
char server_info[256]; /* FIXME: Don't make this static */
char *current = server_info;
char *client_version;
int mpro = 2 << 16;
int apro = 3 << 16;
int actual_length=130 + strlen(config.servername);
if(actual_length > sizeof(server_info)) {
DPRINTF(E_FATAL,L_DAAP,"Server name too long.\n");
}
client_version=ws_getrequestheader(pwsc,"Client-DAAP-Version");
current += db_dmap_add_container(current,"msrv",actual_length - 8);
current += db_dmap_add_int(current,"mstt",200); /* 12 */
if((client_version) && (!strcmp(client_version,"1.0"))) {
mpro = 1 << 16;
apro = 1 << 16;
}
if((client_version) && (!strcmp(client_version,"2.0"))) {
mpro = 1 << 16;
apro = 2 << 16;
}
current += db_dmap_add_int(current,"mpro",mpro); /* 12 */
current += db_dmap_add_int(current,"apro",apro); /* 12 */
current += db_dmap_add_int(current,"mstm",1800); /* 12 */
current += db_dmap_add_string(current,"minm",config.servername); /* 8 + strlen(name) */
current += db_dmap_add_char(current,"msau", /* 9 */
config.readpassword != NULL ? 2 : 0);
current += db_dmap_add_char(current,"msex",0); /* 9 */
current += db_dmap_add_char(current,"msix",0); /* 9 */
current += db_dmap_add_char(current,"msbr",0); /* 9 */
current += db_dmap_add_char(current,"msqy",0); /* 9 */
current += db_dmap_add_char(current,"msup",0); /* 9 */
current += db_dmap_add_int(current,"msdc",1); /* 12 */
ws_addresponseheader(pwsc,"Content-Length","%d",actual_length);
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_emitheaders(pwsc);
r_write(pwsc->fd,server_info,actual_length);
config_set_status(pwsc,pqi->session_id,NULL);
free(pqi);
}

11
src/dispatch.h Normal file
View File

@ -0,0 +1,11 @@
/*
* $Id$
*/
#ifndef _DISPATCH_H_
#define _DISPATCH_H_
extern void daap_handler(WS_CONNINFO *pwsc);
extern int daap_auth(char *username, char *password);
#endif

View File

@ -33,12 +33,15 @@
#include "configfile.h"
#include "err.h"
#include "playlist.h"
#include "restart.h"
#include "mp3-scanner.h"
#define BLKSIZE PIPE_BUF
/* Forwards */
int *da_get_current_tag_info(int file_fd);
int fcopyblock(FILE *fromfp, int tofd, size_t size);
/* For some reason, we need to lose 2 bytes from this image size
This size is everything after the APIC text in this frame.

View File

@ -1,149 +0,0 @@
%{
/* $Id$
* Simple playlist lexer
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "err.h"
#include "playlist.h"
#include "parser.h"
extern int yydebug;
time_t l_converttime(int day, int month, int year);
time_t l_convertyyyymmdd(char *date);
%}
%option yylineno
%option case-insensitive
qstring \"[^\"\n]*[\"\n]
yyyymmdd [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
%%
[\n\t ]+
\#.*\n
artist { yylval.ival=ARTIST; return(ARTIST); }
album { yylval.ival=ALBUM; return(ALBUM); }
genre { yylval.ival=GENRE; return(GENRE); }
path { yylval.ival=PATH; return(PATH); }
composer { yylval.ival=COMPOSER; return(COMPOSER); }
orchestra { yylval.ival=ORCHESTRA; return(ORCHESTRA); }
conductor { yylval.ival=CONDUCTOR; return(CONDUCTOR); }
grouping { yylval.ival=GROUPING; return(GROUPING); }
type { yylval.ival=TYPE; return(TYPE); }
comment { yylval.ival=COMMENT; return(COMMENT); }
year { yylval.ival=YEAR; return(YEAR); }
bpm { yylval.ival=BPM; return(BPM); }
bitrate { yylval.ival=BITRATE; return(BITRATE); }
date { yylval.ival=DATEADDED; return(DATEADDED); }
{yyyymmdd} { yylval.ival=l_convertyyyymmdd(yytext); return(DATE); }
today { yylval.ival=time(NULL); return(DATE); }
yesterday { yylval.ival=time(NULL) - 24*3600; return(DATE); }
last\ week { yylval.ival=time(NULL) - 24*3600*7; return(DATE); }
last\ month { yylval.ival=time(NULL) - 24*3600*30; return(DATE); }
last\ year { yylval.ival=time(NULL) - 24*3600*365; return(DATE); }
days? { yylval.ival=24*3600; return(INTERVAL); }
weeks? { yylval.ival=24*3600*7; return(INTERVAL); }
months? { yylval.ival=24*3600*30; return(INTERVAL); }
years? { yylval.ival=24*3600*365; return(INTERVAL); }
ago { yylval.ival=AGO; return(AGO); }
before { yylval.ival=BEFORE; return(BEFORE); }
after { yylval.ival=AFTER; return(AFTER); }
is { yylval.ival=IS; return(IS); }
includes { yylval.ival=INCLUDES; return(INCLUDES); }
matches { yylval.ival=INCLUDES; return(MATCHES); }
= { yylval.ival=EQUALS; return(EQUALS); }
or |
\|\| { yylval.ival=OR; return(OR); }
and |
&& { yylval.ival=AND; return(AND); }
not |
! { yylval.ival=1; return(NOT); }
\<= { yylval.ival=LESSEQUAL; return(LESSEQUAL); }
\< { yylval.ival=LESS; return(LESS); }
\>= { yylval.ival=GREATEREQUAL; return(GREATEREQUAL); }
\> { yylval.ival=GREATER; return(GREATER); }
{qstring} { yylval.cval=strdup(yytext+1);
if(yylval.cval[strlen(yylval.cval)-1] == '"')
yylval.cval[strlen(yylval.cval)-1] = '\0';
return(ID); }
[0-9]+ { yylval.ival=atoi(yytext); return(NUM); }
. { return yytext[0]; }
%%
time_t l_convertyyyymmdd(char *date) {
char year[5];
char month[3];
char day[3];
memset(year,0,sizeof(year));
memset(month,0,sizeof(month));
memset(day,0,sizeof(day));
strncpy(year,date,4);
strncpy(month,date+5,2);
strncpy(day,date+8,2);
DPRINTF(E_INF,L_PL,"Converting %d-%d-%d\n",atoi(year),atoi(month),atoi(day));
return l_converttime(atoi(day), atoi(month), atoi(year));
}
time_t l_converttime(int day, int month, int year) {
struct tm tm;
memset((void*)&tm,0,sizeof(tm));
tm.tm_year = year - 1900;
tm.tm_mon = month-1;
tm.tm_mday = day;
return mktime(&tm);
}
int yywrap(void) {
return 1;
}
void yyerror(char *msg) {
pl_error=1;
DPRINTF(E_LOG,L_PL,"Playlist error, line %d: %s\n",yylineno, msg);
}

View File

@ -71,15 +71,13 @@
#include <sys/wait.h>
#include "configfile.h"
#include "db-memory.h"
#include "daap.h"
#include "daap-proto.h"
#include "dispatch.h"
#include "err.h"
#include "mp3-scanner.h"
#include "webserver.h"
#include "playlist.h"
#include "ssc.h"
#include "dynamic-art.h"
#include "db-generic.h"
#ifndef WITHOUT_MDNS
# include "rend.h"
@ -123,464 +121,9 @@ CONFIG config; /**< Main configuration structure, as read from configfile */
* Forwards
*/
static int daemon_start(void);
static void write_pid_file(void);
static void usage(char *program);
static void *signal_handler(void *arg);
static int start_signal_handler(pthread_t *handler_tid);
static void daap_handler(WS_CONNINFO *pwsc);
static int daap_auth(char *username, char *password);
/**
* Handles authentication for the daap server. This isn't the
* authenticator for the web admin page, but rather the iTunes
* authentication when trying to connect to the server. Note that most
* of this is actually handled in the web server registration, which
* decides when to apply the authentication or not. If you mess with
* when and where the webserver applies auth or not, you'll likely
* break something. It seems that some requests must be authed, and others
* not. If you apply authentication somewhere that iTunes doesn't expect
* it, it happily disconnects.
*
* \param username The username passed by iTunes
* \param password The password passed by iTunes
* \returns 1 if auth successful, 0 otherwise
*/
int daap_auth(char *username, char *password) {
if((password == NULL) &&
((config.readpassword == NULL) || (strlen(config.readpassword)==0)))
return 1;
if(password == NULL)
return 0;
return !strcasecmp(password,config.readpassword);
}
/**
* This handles requests that are daap-related. For example,
* /server-info, /login, etc. This should really be split up
* into multiple functions, and perhaps moved into daap.c
*
* \param pwsc Webserver connection info, passed from the webserver
*
* \todo Decomplexify this!
*/
void daap_handler(WS_CONNINFO *pwsc) {
int close;
DAAP_BLOCK *root;
int clientrev;
/* for the /databases URI */
char *uri;
unsigned long int db_index;
unsigned long int playlist_index;
unsigned long int item=0;
char *first, *last;
char* index = 0;
int streaming=0;
int compress =0;
int start_time;
int end_time;
int bytes_written;
int serialize_as_xml;
MP3FILE *pmp3;
int file_fd;
FILE *file_ptr; /* for possible conv filter */
int session_id=0;
int img_fd;
struct stat sb;
long img_size;
off_t offset=0;
off_t real_len;
off_t file_len;
char *real_path;
int bytes_copied=0;
GZIP_STREAM *gz;
close=pwsc->close;
pwsc->close=1; /* in case we have any errors */
root=NULL;
ws_addresponseheader(pwsc,"Accept-Ranges","bytes");
ws_addresponseheader(pwsc,"DAAP-Server","mt-daapd/%s",VERSION);
ws_addresponseheader(pwsc,"Content-Type","application/x-dmap-tagged");
if(ws_getvar(pwsc,"session-id")) {
session_id=atoi(ws_getvar(pwsc,"session-id"));
}
if(!strcasecmp(pwsc->uri,"/server-info")) {
config_set_status(pwsc,session_id,"Sending server info");
root=daap_response_server_info(config.servername,
ws_getrequestheader(pwsc,"Client-DAAP-Version"));
} else if (!strcasecmp(pwsc->uri,"/content-codes")) {
config_set_status(pwsc,session_id,"Sending content codes");
root=daap_response_content_codes();
} else if (!strcasecmp(pwsc->uri,"/login")) {
config_set_status(pwsc,session_id,"Logging in");
root=daap_response_login(pwsc->hostname);
} else if (!strcasecmp(pwsc->uri,"/update")) {
if(!ws_getvar(pwsc,"delta")) { /* first check */
clientrev=db_version() - 1;
config_set_status(pwsc,session_id,"Sending database");
} else {
clientrev=atoi(ws_getvar(pwsc,"delta"));
config_set_status(pwsc,session_id,"Waiting for DB updates");
}
root=daap_response_update(pwsc->fd,clientrev);
if((ws_getvar(pwsc,"delta")) && (root==NULL)) {
DPRINTF(E_LOG,L_WS,"Client %s disconnected\n",pwsc->hostname);
config_set_status(pwsc,session_id,NULL);
pwsc->close=1;
return;
}
} else if (!strcasecmp(pwsc->uri,"/logout")) {
config_set_status(pwsc,session_id,NULL);
ws_returnerror(pwsc,204,"Logout Successful");
return;
} else if(strcmp(pwsc->uri,"/databases")==0) {
config_set_status(pwsc,session_id,"Sending database info");
root=daap_response_dbinfo(config.servername);
if(0 != (index = ws_getvar(pwsc, "index")))
daap_handle_index(root, index);
} else if(strncmp(pwsc->uri,"/databases/",11) == 0) {
/* the /databases/ uri will either be:
*
* /databases/id/items, which returns items in a db
* /databases/id/containers, which returns a container
* /databases/id/containers/id/items, which returns playlist elements
* /databases/id/items/id.mp3, to spool an mp3
* /databases/id/browse/category
*/
uri = strdup(pwsc->uri);
first=(char*)&uri[11];
last=first;
while((*last) && (*last != '/')) {
last++;
}
if(*last) {
*last='\0';
db_index=atoll(first);
last++;
if(strncasecmp(last,"items/",6)==0) {
/* streaming */
first=last+6;
while((*last) && (*last != '.'))
last++;
if(*last == '.') {
*last='\0';
item=atoll(first);
streaming=1;
DPRINTF(E_DBG,L_DAAP|L_WS,"Streaming request for id %lu\n",item);
}
free(uri);
} else if (strncasecmp(last,"items",5)==0) {
/* songlist */
free(uri);
// pass the meta field request for processing
// pass the query request for processing
root=daap_response_songlist(ws_getvar(pwsc,"meta"),
ws_getvar(pwsc,"query"));
config_set_status(pwsc,session_id,"Sending songlist");
} else if (strncasecmp(last,"containers/",11)==0) {
/* playlist elements */
first=last + 11;
last=first;
while((*last) && (*last != '/')) {
last++;
}
if(*last) {
*last='\0';
playlist_index=atoll(first);
// pass the meta list info for processing
root=daap_response_playlist_items(playlist_index,
ws_getvar(pwsc,"meta"),
ws_getvar(pwsc,"query"));
}
free(uri);
config_set_status(pwsc,session_id,"Sending playlist info");
} else if (strncasecmp(last,"containers",10)==0) {
/* list of playlists */
free(uri);
root=daap_response_playlists(config.servername);
config_set_status(pwsc,session_id,"Sending playlist info");
} else if (strncasecmp(last,"browse/",7)==0) {
config_set_status(pwsc,session_id,"Compiling browse info");
root = daap_response_browse(last + 7,
ws_getvar(pwsc, "filter"));
config_set_status(pwsc,session_id,"Sending browse info");
free(uri);
}
}
// prune the full list if an index range was specified
if(0 != (index = ws_getvar(pwsc, "index")))
daap_handle_index(root, index);
}
if((!root)&&(!streaming)) {
DPRINTF(E_DBG,L_WS|L_DAAP,"Bad request -- root=%x, streaming=%d\n",root,streaming);
ws_returnerror(pwsc,400,"Invalid Request");
config_set_status(pwsc,session_id,NULL);
return;
}
pwsc->close=close;
if(!streaming) {
DPRINTF(E_DBG,L_WS,"Satisfying request\n");
serialize_as_xml=0;
if(ws_getvar(pwsc,"output"))
serialize_as_xml=1;
if((config.compress) && ws_testrequestheader(pwsc,"Accept-Encoding","gzip") &&
(root->reported_size >= 1000) && (!serialize_as_xml)) {
compress=1;
}
DPRINTF(E_DBG,L_WS|L_DAAP,"Serializing\n");
start_time = time(NULL);
if (compress) {
DPRINTF(E_DBG,L_WS|L_DAAP,"Using compression: %s\n", pwsc->uri);
gz = gzip_alloc();
daap_serialize(root,pwsc->fd,gz);
gzip_compress(gz);
bytes_written = gz->bytes_out;
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
ws_addresponseheader(pwsc,"Content-Length","%d",bytes_written);
ws_addresponseheader(pwsc,"Content-Encoding","gzip");
DPRINTF(E_DBG,L_WS,"Emitting headers\n");
ws_emitheaders(pwsc);
if (gzip_close(gz,pwsc->fd) != bytes_written) {
DPRINTF(E_LOG,L_WS|L_DAAP,"Error compressing data\n");
}
DPRINTF(E_DBG,L_WS|L_DAAP,"Compression ratio: %f\n",((double) bytes_written)/(8.0 + root->reported_size))
}
else {
bytes_written = root->reported_size + 8;
if(!serialize_as_xml) {
ws_addresponseheader(pwsc,"Content-Length","%d",bytes_written);
} else {
ws_addresponseheader(pwsc,"Connection","close");
ws_addresponseheader(pwsc,"Content-type","text/xml");
pwsc->close=1;
}
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
DPRINTF(E_DBG,L_WS,"Emitting headers\n");
ws_emitheaders(pwsc);
if(!serialize_as_xml) {
daap_serialize(root,pwsc->fd,NULL);
} else {
ws_writefd(pwsc,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
daap_serialize_xml(root,pwsc->fd);
}
}
end_time = time(NULL);
DPRINTF(E_DBG,L_WS|L_DAAP,"Sent %d bytes in %d seconds\n",bytes_written,end_time-start_time);
DPRINTF(E_DBG,L_WS|L_DAAP,"Done, freeing\n");
daap_free(root);
} else {
/* stream out the song */
pwsc->close=1;
if(ws_getrequestheader(pwsc,"range")) {
offset=(off_t)atol(ws_getrequestheader(pwsc,"range") + 6);
}
pmp3=db_find(item);
if(!pmp3) {
DPRINTF(E_LOG,L_DAAP|L_WS|L_DB,"Could not find requested item %lu\n",item);
ws_returnerror(pwsc,404,"File Not Found");
} else if ((real_path=server_side_convert_path(pmp3->path)) != NULL) {
// The file should be converted in the server side.
DPRINTF(E_WARN,L_WS,"Thread %d: Autoconvert file %s for client\n",
pwsc->threadno,real_path);
file_ptr = server_side_convert_open(real_path,
offset,
pmp3->song_length);
if (file_ptr) {
file_fd = fileno(file_ptr);
} else {
file_fd = -1;
}
if(file_fd == -1) {
if (file_ptr) {
server_side_convert_close(file_ptr);
}
pwsc->error=errno;
DPRINTF(E_WARN,L_WS,
"Thread %d: Error opening %s for conversion\n",
pwsc->threadno,real_path);
ws_returnerror(pwsc,404,"Not found");
config_set_status(pwsc,session_id,NULL);
db_dispose(pmp3);
free(pmp3);
free(real_path);
} else {
// DWB: fix content-type to correctly reflect data
// content type (dmap tagged) should only be used on
// dmap protocol requests, not the actually song data
if(pmp3->type)
ws_addresponseheader(pwsc,"Content-Type","audio/%s",
pmp3->type);
// Also content-length -heade would be nice, but since
// we don't really know it here, so let's leave it out.
ws_addresponseheader(pwsc,"Connection","Close");
if(!offset)
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
else {
// This is actually against the protocol, since
// range MUST be explicit according to HTTP-standard
// Seems to work at least with iTunes.
ws_addresponseheader(pwsc,
"Content-Range","bytes %ld-*/*",
(long)offset);
ws_writefd(pwsc,"HTTP/1.1 206 Partial Content\r\n");
}
ws_emitheaders(pwsc);
config_set_status(pwsc,session_id,
"Streaming file via convert filter '%s'",
pmp3->fname);
DPRINTF(E_LOG,L_WS,
"Session %d: Streaming file '%s' to %s (offset %ld)\n",
session_id,pmp3->fname, pwsc->hostname,(long)offset);
if(!offset)
config.stats.songs_served++; /* FIXME: remove stat races */
if((bytes_copied=copyfile(file_fd,pwsc->fd)) == -1) {
DPRINTF(E_INF,L_WS,
"Error copying converted file to remote... %s\n",
strerror(errno));
} else {
DPRINTF(E_INF,L_WS,
"Finished streaming converted file to remote\n");
}
server_side_convert_close(file_ptr);
config_set_status(pwsc,session_id,NULL);
db_dispose(pmp3);
free(pmp3);
free(real_path);
}
} else {
/* got the file, let's open and serve it */
file_fd=r_open2(pmp3->path,O_RDONLY);
if(file_fd == -1) {
pwsc->error=errno;
DPRINTF(E_WARN,L_WS,"Thread %d: Error opening %s: %s\n",
pwsc->threadno,pmp3->path,strerror(errno));
ws_returnerror(pwsc,404,"Not found");
config_set_status(pwsc,session_id,NULL);
db_dispose(pmp3);
free(pmp3);
} else {
real_len=lseek(file_fd,0,SEEK_END);
lseek(file_fd,0,SEEK_SET);
/* Re-adjust content length for cover art */
if((config.artfilename) &&
((img_fd=da_get_image_fd(pmp3->path)) != -1)) {
fstat(img_fd, &sb);
img_size = sb.st_size;
if (strncasecmp(pmp3->type,"mp3",4) ==0) {
/*PENDING*/
} else if (strncasecmp(pmp3->type, "m4a", 4) == 0) {
real_len += img_size + 24;
if (offset > img_size + 24) {
offset -= img_size + 24;
}
}
}
file_len = real_len - offset;
DPRINTF(E_DBG,L_WS,"Thread %d: Length of file (remaining) is %ld\n",
pwsc->threadno,(long)file_len);
// DWB: fix content-type to correctly reflect data
// content type (dmap tagged) should only be used on
// dmap protocol requests, not the actually song data
if(pmp3->type)
ws_addresponseheader(pwsc,"Content-Type","audio/%s",pmp3->type);
ws_addresponseheader(pwsc,"Content-Length","%ld",(long)file_len);
ws_addresponseheader(pwsc,"Connection","Close");
if(!offset)
ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
else {
ws_addresponseheader(pwsc,"Content-Range","bytes %ld-%ld/%ld",
(long)offset,(long)real_len,
(long)real_len+1);
ws_writefd(pwsc,"HTTP/1.1 206 Partial Content\r\n");
}
ws_emitheaders(pwsc);
config_set_status(pwsc,session_id,"Streaming file '%s'",pmp3->fname);
DPRINTF(E_LOG,L_WS,"Session %d: Streaming file '%s' to %s (offset %d)\n",
session_id,pmp3->fname, pwsc->hostname,(long)offset);
if(!offset)
config.stats.songs_served++; /* FIXME: remove stat races */
if((config.artfilename) &&
(!offset) &&
((img_fd=da_get_image_fd(pmp3->path)) != -1)) {
if (strncasecmp(pmp3->type,"mp3",4) ==0) {
DPRINTF(E_INF,L_WS|L_ART,"Dynamic add artwork to %s (fd %d)\n",
pmp3->fname, img_fd);
da_attach_image(img_fd, pwsc->fd, file_fd, offset);
} else if (strncasecmp(pmp3->type, "m4a", 4) == 0) {
DPRINTF(E_INF,L_WS|L_ART,"Dynamic add artwork to %s (fd %d)\n",
pmp3->fname, img_fd);
da_aac_attach_image(img_fd, pwsc->fd, file_fd, offset);
}
} else if(offset) {
DPRINTF(E_INF,L_WS,"Seeking to offset %ld\n",(long)offset);
lseek(file_fd,offset,SEEK_SET);
}
if((bytes_copied=copyfile(file_fd,pwsc->fd)) == -1) {
DPRINTF(E_INF,L_WS,"Error copying file to remote... %s\n",
strerror(errno));
} else {
DPRINTF(E_INF,L_WS,"Finished streaming file to remote: %d bytes\n",
bytes_copied);
}
config_set_status(pwsc,session_id,NULL);
r_close(file_fd);
db_dispose(pmp3);
free(pmp3);
}
}
}
DPRINTF(E_DBG,L_WS|L_DAAP,"Finished serving DAAP response\n");
return;
}
/**
* Fork and exit. Stolen pretty much straight from Stevens.
@ -645,7 +188,6 @@ void usage(char *program) {
printf(" -D <mod,mod..> Debug modules\n");
printf(" -m Disable mDNS\n");
printf(" -c <file> Use configfile specified\n");
printf(" -p Parse playlist file\n");
printf(" -f Run in foreground\n");
printf(" -y Yes, go ahead and run as non-root user\n");
printf("\n\n");
@ -771,7 +313,7 @@ int start_signal_handler(pthread_t *handler_tid) {
return -1;
}
if(error=pthread_create(handler_tid, NULL, signal_handler, NULL)) {
if((error=pthread_create(handler_tid, NULL, signal_handler, NULL))) {
errno=error;
DPRINTF(E_LOG,L_MAIN,"Error creating signal_handler thread\n");
return -1;
@ -805,7 +347,6 @@ int main(int argc, char *argv[]) {
char *configfile=DEFAULT_CONFIGFILE;
WSCONFIG ws_config;
WSHANDLE server;
int parseonly=0;
int foreground=0;
int reload=0;
int start_time;
@ -813,6 +354,7 @@ int main(int argc, char *argv[]) {
int rescan_counter=0;
int old_song_count;
int force_non_root=0;
int skip_initial=0;
pthread_t signal_tid;
int pid_fd;
@ -821,7 +363,7 @@ int main(int argc, char *argv[]) {
config.use_mdns=1;
err_debuglevel=1;
while((option=getopt(argc,argv,"D:d:c:mpfry")) != -1) {
while((option=getopt(argc,argv,"D:d:c:mfrys")) != -1) {
switch(option) {
case 'd':
err_debuglevel=atoi(optarg);
@ -844,15 +386,14 @@ int main(int argc, char *argv[]) {
config.use_mdns=0;
break;
case 'p':
parseonly=1;
foreground=1;
break;
case 'r':
reload=1;
break;
case 's':
skip_initial=1;
break;
case 'y':
force_non_root=1;
break;
@ -888,7 +429,7 @@ int main(int argc, char *argv[]) {
}
#ifndef WITHOUT_MDNS
if((config.use_mdns) && (!parseonly)) {
if(config.use_mdns) {
DPRINTF(E_LOG,L_MAIN,"Starting rendezvous daemon\n");
if(rend_init(config.runas)) {
DPRINTF(E_FATAL,L_MAIN|L_REND,"Error in rend_init: %s\n",strerror(errno));
@ -904,14 +445,14 @@ int main(int argc, char *argv[]) {
if(0 == (pid_fp = fdopen(pid_fd, "w")))
DPRINTF(E_FATAL,L_MAIN,"fdopen: %s\n",strerror(errno));
daemon_start();
/* just to be on the safe side... */
config.pid=0;
daemon_start();
}
/* DWB: shouldn't this be done after dropping privs? */
if(db_open(config.dbdir, reload))
if(db_open(config.dbdir))
DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_open: %s\n",strerror(errno));
@ -936,29 +477,17 @@ int main(int argc, char *argv[]) {
fprintf(pid_fp,"%d\n",config.pid);
fclose(pid_fp);
}
DPRINTF(E_LOG,L_MAIN|L_PL,"Loading playlists\n");
if(config.playlist)
pl_load(config.playlist);
if(parseonly) {
if(!pl_error) {
fprintf(stderr,"Parsed successfully.\n");
pl_dump();
}
exit(EXIT_SUCCESS);
}
/* Initialize the database before starting */
DPRINTF(E_LOG,L_MAIN|L_DB,"Initializing database\n");
if(db_init()) {
if(db_init(reload)) {
DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_init: %s\n",strerror(errno));
}
DPRINTF(E_LOG,L_MAIN|L_SCAN,"Starting mp3 scan\n");
if(scan_init(config.mp3dir)) {
DPRINTF(E_FATAL,L_MAIN|L_SCAN,"Error scanning MP3 files: %s\n",strerror(errno));
if(!skip_initial) {
DPRINTF(E_LOG,L_MAIN|L_SCAN,"Starting mp3 scan\n");
if(scan_init(config.mp3dir)) {
DPRINTF(E_FATAL,L_MAIN|L_SCAN,"Error scanning MP3 files: %s\n",strerror(errno));
}
}
/* start up the web server */

View File

@ -46,10 +46,9 @@
#include <dirent.h> /* why here? For osx 10.2, of course! */
#include "daapd.h"
#include "db-memory.h"
#include "db-generic.h"
#include "err.h"
#include "mp3-scanner.h"
#include "playlist.h"
#include "ssc.h"
#ifndef HAVE_STRCASESTR
@ -111,10 +110,6 @@ int scan_sample_table[3][4] = {
{ 11025, 12000, 8000, 0 } /* MPEG 2.5 */
};
int scan_mode_foreground=1;
char *scan_winamp_genre[] = {
"Blues", // 0
"Classic Rock",
@ -282,11 +277,11 @@ static int scan_get_fileinfo(char *file, MP3FILE *pmp3);
static int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3);
static int scan_get_aacfileinfo(char *file, MP3FILE *pmp3);
static int scan_get_wavfileinfo(char *file, MP3FILE *pmp3);
static int scan_get_nulfileinfo(char *file, MP3FILE *pmp3) { return 0; };
//static int scan_get_nulfileinfo(char *file, MP3FILE *pmp3) { return 0; };
static int scan_get_urlfileinfo(char *file, MP3FILE *pmp3);
static int scan_freetags(MP3FILE *pmp3);
static void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb);
//static void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb);
static void scan_music_file(char *path, struct dirent *pde, struct stat *psb);
static int scan_decode_mp3_frame(unsigned char *frame, SCAN_FRAMEINFO *pfi);
@ -361,25 +356,16 @@ time_t mac_to_unix_time(int t) {
int scan_init(char *path) {
int err=0;
scan_mode_foreground=0;
if(db_is_empty()) {
scan_mode_foreground=1;
}
if(db_start_initial_update())
if(db_start_scan())
return -1;
DPRINTF(E_DBG,L_SCAN,"%s scanning for MP3s in %s\n",
scan_mode_foreground ? "Foreground" : "Background",
path);
DPRINTF(E_DBG,L_SCAN,"Scanning for MP3s in %s\n",path);
err=scan_path(path);
if(db_end_initial_update())
if(db_end_scan())
return -1;
scan_mode_foreground=0;
return err;
}
@ -441,16 +427,17 @@ int scan_path(char *path) {
if((strcasecmp(".m3u",(char*)&pde->d_name[strlen(pde->d_name) - 4]) == 0) &&
config.process_m3u){
/* we found an m3u file */
scan_static_playlist(path, pde, &sb);
DPRINTF(E_LOG,L_SCAN,"Oops... we aren't doing playlists.. Sorry: %s\n",
mp3_path);
// scan_static_playlist(path, pde, &sb);
} else if (((ext = strrchr(pde->d_name, '.')) != NULL) &&
(strcasestr(config.extensions, ext))) {
/* only scan if it's been changed, or empty db */
modified_time=sb.st_mtime;
DPRINTF(E_DBG,L_SCAN,"FS Mod time: %d\n",modified_time);
DPRINTF(E_DBG,L_SCAN,"DB Mod time: %d\n",db_last_modified(sb.st_ino));
if((scan_mode_foreground) ||
!db_exists(sb.st_ino) ||
db_last_modified(sb.st_ino) < modified_time) {
DPRINTF(E_DBG,L_SCAN,"DB Mod time: %d\n",db_last_modified(mp3_path));
if(!db_get_id(mp3_path) ||
db_last_modified(mp3_path) < modified_time) {
scan_music_file(path,pde,&sb);
} else {
DPRINTF(E_DBG,L_SCAN,"Skipping file... not modified\n");
@ -470,6 +457,7 @@ int scan_path(char *path) {
*
* Scan a file as a static playlist
*/
/*
void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb) {
char playlist_path[PATH_MAX];
char m3u_path[PATH_MAX];
@ -480,7 +468,7 @@ void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb) {
DPRINTF(E_WARN,L_SCAN|L_PL,"Processing static playlist: %s\n",pde->d_name);
/* see if we should update it */
if(db_playlist_last_modified(psb->st_ino) == psb->st_mtime)
return;
@ -498,15 +486,15 @@ void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb) {
memset(linebuffer,0x00,sizeof(linebuffer));
while(readline(fd,linebuffer,sizeof(linebuffer)) > 0) {
while((linebuffer[strlen(linebuffer)-1] == '\n') ||
(linebuffer[strlen(linebuffer)-1] == '\r')) /* windows? */
(linebuffer[strlen(linebuffer)-1] == '\r'))
linebuffer[strlen(linebuffer)-1] = '\0';
if((linebuffer[0] == ';') || (linebuffer[0] == '#'))
continue;
/* FIXME - should chomp trailing comments */
// FIXME - should chomp trailing comments
/* otherwise, assume it is a path */
// otherwise, assume it is a path
if(linebuffer[0] == '/') {
strcpy(m3u_path,linebuffer);
} else {
@ -515,9 +503,9 @@ void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb) {
DPRINTF(E_DBG,L_SCAN|L_PL,"Checking %s\n",m3u_path);
/* might be valid, might not... */
// might be valid, might not...
if(!stat(m3u_path,&sb)) {
/* FIXME: check to see if valid inode! */
// FIXME: check to see if valid inode!
db_add_playlist_song(playlistid,sb.st_ino);
} else {
DPRINTF(E_WARN,L_SCAN|L_PL,"Playlist entry %s bad: %s\n",
@ -529,7 +517,7 @@ void scan_static_playlist(char *path, struct dirent *pde, struct stat *psb) {
DPRINTF(E_WARN,L_SCAN|L_PL,"Done processing playlist\n");
}
*/
/*
* scan_music_file
*
@ -574,7 +562,7 @@ void scan_music_file(char *path, struct dirent *pde, struct stat *psb) {
DPRINTF(E_DBG,L_SCAN," Date Added: %d\n",mp3file.time_added);
db_add(&mp3file);
pl_eval(&mp3file); /* FIXME: move to db_add? */
// pl_eval(&mp3file); /* FIXME: move to db_add? */
} else {
DPRINTF(E_WARN,L_SCAN,"Skipping %s - scan_gettags failed\n",pde->d_name);
}
@ -1622,7 +1610,7 @@ int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3) {
int xing_flags;
int found;
int first_check;
int first_check=0;
char frame_buffer[4];
if(!(infile=fopen(file,"rb"))) {

View File

@ -54,14 +54,15 @@ typedef struct tag_mp3file {
int time_added;
int time_modified;
int time_played;
int play_count;
int rating;
int db_timestamp;
int disabled;
int bpm; /* TBPM */
int got_id3;
// unsigned int id;
unsigned long int id;
/* generated fields */
unsigned int id;
char* description; /* long file type */
int item_kind; /* song or movie */
int data_kind; /* dmap.datakind (asdk) */
@ -72,7 +73,7 @@ typedef struct tag_mp3file {
extern int scan_init(char *path);
extern void make_composite_tags(MP3FILE *song);
/* this should be refactored out of here... */
/* FIXME: this should be refactored out of here... */
extern off_t aac_drilltoatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length);
extern long scan_aac_findatom(FILE *fin, long max_offset, char *which_atom, int *atom_size);
#endif /* _MP3_SCANNER_H_ */

View File

@ -1,246 +0,0 @@
%{
/* $Id$
* Simple playlist parser
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "playlist.h"
#define YYERROR_VERBOSE 1
extern int yyerror(char *msg);
/* Forwards */
extern PL_NODE *pl_newcharpredicate(int tag, int op, char *value);
extern PL_NODE *pl_newintpredicate(int tag, int op, int value);
extern PL_NODE *pl_newdatepredicate(int tag, int op, int value);
extern PL_NODE *pl_newexpr(PL_NODE *arg1, int op, PL_NODE *arg2);
extern int pl_addplaylist(char *name, PL_NODE *root);
/* Globals */
int pl_number=2;
%}
%left OR AND
%union {
unsigned int ival;
char *cval;
PL_NODE *plval;
}
%token <ival> ARTIST
%token <ival> ALBUM
%token <ival> GENRE
%token <ival> PATH
%token <ival> COMPOSER
%token <ival> ORCHESTRA
%token <ival> CONDUCTOR
%token <ival> GROUPING
%token <ival> TYPE
%token <ival> COMMENT
%token <ival> EQUALS
%token <ival> LESS
%token <ival> LESSEQUAL
%token <ival> GREATER
%token <ival> GREATEREQUAL
%token <ival> IS
%token <ival> INCLUDES
%token <ival> MATCHES
%token <ival> OR
%token <ival> AND
%token <ival> NOT
%token <cval> ID
%token <ival> NUM
%token <ival> DATE
%token <ival> YEAR
%token <ival> BPM
%token <ival> BITRATE
%token <ival> DATEADDED
%token <ival> BEFORE
%token <ival> AFTER
%token <ival> AGO
%token <ival> INTERVAL
%type <plval> expression
%type <plval> predicate
%type <ival> strtag
%type <ival> inttag
%type <ival> datetag
%type <ival> dateval
%type <ival> interval
%type <ival> strbool
%type <ival> intbool
%type <ival> datebool
%type <ival> playlist
%%
playlistlist: playlist {}
| playlistlist playlist {}
;
playlist: ID '{' expression '}' { $$ = pl_addplaylist($1, $3); }
;
expression: expression AND expression { $$=pl_newexpr($1,$2,$3); }
| expression OR expression { $$=pl_newexpr($1,$2,$3); }
| '(' expression ')' { $$=$2; }
| predicate
;
predicate: strtag strbool ID { $$=pl_newcharpredicate($1, $2, $3); }
| inttag intbool NUM { $$=pl_newintpredicate($1, $2, $3); }
| datetag datebool dateval { $$=pl_newdatepredicate($1, $2, $3); }
;
datetag: DATEADDED { $$ = $1; }
;
inttag: YEAR
| BPM
| BITRATE
;
intbool: EQUALS { $$ = $1; }
| LESS { $$ = $1; }
| LESSEQUAL { $$ = $1; }
| GREATER { $$ = $1; }
| GREATEREQUAL { $$ = $1; }
| NOT intbool { $$ = $2 | 0x80000000; }
;
datebool: BEFORE { $$ = $1; }
| AFTER { $$ = $1; }
| NOT datebool { $$=$2 | 0x80000000; }
;
interval: INTERVAL { $$ = $1; }
| NUM INTERVAL { $$ = $1 * $2; }
;
dateval: DATE { $$ = $1; }
| interval BEFORE dateval { $$ = $3 - $1; }
| interval AFTER dateval { $$ = $3 + $1; }
| interval AGO { $$ = time(NULL) - $1; }
;
strtag: ARTIST
| ALBUM
| GENRE
| PATH
| COMPOSER
| ORCHESTRA
| CONDUCTOR
| GROUPING
| TYPE
| COMMENT
;
strbool: IS { $$=$1; }
| INCLUDES { $$=$1; }
| MATCHES { $$=$1; }
| NOT strbool { $$=$2 | 0x80000000; }
;
%%
PL_NODE *pl_newintpredicate(int tag, int op, int value) {
PL_NODE *pnew;
pnew=(PL_NODE*)malloc(sizeof(PL_NODE));
if(!pnew)
return NULL;
pnew->op=op;
pnew->type=T_INT;
pnew->arg1.ival=tag;
pnew->arg2.ival=value;
return pnew;
}
PL_NODE *pl_newdatepredicate(int tag, int op, int value) {
PL_NODE *pnew;
pnew=(PL_NODE*)malloc(sizeof(PL_NODE));
if(!pnew)
return NULL;
pnew->op=op;
pnew->type=T_DATE;
pnew->arg1.ival=tag;
pnew->arg2.ival=value;
return pnew;
}
PL_NODE *pl_newcharpredicate(int tag, int op, char *value) {
PL_NODE *pnew;
pnew=(PL_NODE*)malloc(sizeof(PL_NODE));
if(!pnew)
return NULL;
pnew->op=op;
pnew->type=T_STR;
pnew->arg1.ival=tag;
pnew->arg2.cval=value;
return pnew;
}
PL_NODE *pl_newexpr(PL_NODE *arg1, int op, PL_NODE *arg2) {
PL_NODE *pnew;
pnew=(PL_NODE*)malloc(sizeof(PL_NODE));
if(!pnew)
return NULL;
pnew->op=op;
pnew->arg1.plval=arg1;
pnew->arg2.plval=arg2;
return pnew;
}
int pl_addplaylist(char *name, PL_NODE *root) {
SMART_PLAYLIST *pnew;
pnew=(SMART_PLAYLIST *)malloc(sizeof(SMART_PLAYLIST));
if(!pnew)
return -1;
pnew->next=pl_smart.next;
pnew->name=name;
pnew->root=root;
pnew->id=pl_number++;
pl_smart.next=pnew;
return 0;
}

View File

@ -1,451 +0,0 @@
/*
* $Id$
* iTunes-style smart playlists
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "db-memory.h"
#include "err.h"
#include "mp3-scanner.h"
#include "playlist.h"
#include "parser.h"
/* Externs */
extern int yyparse(void *);
/* Globals */
SMART_PLAYLIST pl_smart = { NULL, 0, NULL, NULL };
int pl_error=0;
/* Forwards */
void pl_dump(void);
void pl_dump_node(PL_NODE *pnode, int indent);
int pl_load(char *file);
int pl_eval_node(MP3FILE *pmp3, PL_NODE *pnode);
extern FILE *yyin;
#define MATCH_WILD_CHR '?'
#define MATCH_WILD_STR '*'
#define MATCH_CHAR_EQ(m, c, case_sensitive) \
(((m) == (c)) || \
((! case_sensitive) && \
((isalpha((m)) ? toupper((m)) : (m)) == \
(isalpha((c)) ? toupper((c)) : (c)))))
/*
* match
*
* Trivial pattern matcher.
* - '*' matches to any (sub)string
* - '?' matches to any character
* Returs non-zero on match.
*/
static int match(char *mask, char *str, int case_sensitive)
{
unsigned char *s, *m;
m = (unsigned char *)mask;
s = (unsigned char *)str;
while (*m != '\0') {
if ((! MATCH_CHAR_EQ(*m, *s, case_sensitive)) &&
(*m != MATCH_WILD_CHR) && (*m != MATCH_WILD_STR)) {
return 0;
} else if (*m == MATCH_WILD_STR) {
m++;
while (*s) {
if (match((char *)m, (char *)s, case_sensitive))
return 1;
s++;
}
return ((!(*m != '\0')) ? 1 : 0);
} else {
s++;
m++;
}
}
return ((!(*s != '\0')) ? 1 : 0);
}
/*
* pl_dump
*
* Dump the playlist list for debugging
*/
void pl_dump(void) {
SMART_PLAYLIST *pcurrent=pl_smart.next;
while(pcurrent) {
printf("Playlist %s:\n",pcurrent->name);
pl_dump_node(pcurrent->root,1);
pcurrent=pcurrent->next;
}
}
/*
* pl_dump_node
*
* recursively dump a node
*/
void pl_dump_node(PL_NODE *pnode, int indent) {
int index;
int not=0;
unsigned int boolarg;
char datebuffer[40];
struct tm *ptm;
for(index=0;index<indent;index++) {
printf(" ");
}
if(pnode->op == AND) {
printf("AND\n");
} else if (pnode->op == OR) {
printf("OR\n");
}
if((pnode->op == AND) || (pnode->op == OR)) {
pl_dump_node(pnode->arg1.plval,indent+1);
pl_dump_node(pnode->arg2.plval,indent+1);
return;
}
switch(pnode->arg1.ival) {
case ARTIST:
printf("ARTIST ");
break;
case ALBUM:
printf("ALBUM ");
break;
case GENRE:
printf("GENRE ");
break;
case PATH:
printf("PATH ");
break;
case COMPOSER:
printf("COMPOSER ");
break;
case ORCHESTRA:
printf("ORCHESTRA ");
break;
case CONDUCTOR:
printf("CONDUCTOR ");
break;
case GROUPING:
printf("GROUPING ");
break;
case TYPE:
printf("TYPE ");
break;
case COMMENT:
printf("COMMENT ");
break;
case YEAR:
printf("YEAR ");
break;
case BPM:
printf("BPM ");
break;
case BITRATE:
printf("BITRATE ");
break;
case DATEADDED:
printf("DATE ");
break;
default:
printf ("<unknown tag> ");
break;
}
boolarg=(pnode->op) & 0x7FFFFFFF;
if(pnode->op & 0x80000000)
not=1;
switch(boolarg) {
case IS:
printf("%s",not? "IS NOT " : "IS ");
break;
case INCLUDES:
printf("%s",not? "DOES NOT INCLUDE " : "INCLUDES ");
break;
case MATCHES:
printf("%s",not? "DOES NOT MATCH " : "MATCHES ");
break;
case EQUALS:
printf("EQUALS ");
break;
case LESS:
printf("< ");
break;
case LESSEQUAL:
printf("<= ");
break;
case GREATER:
printf("> ");
break;
case GREATEREQUAL:
printf(">= ");
break;
case BEFORE:
printf("BEFORE ");
break;
case AFTER:
printf("AFTER ");
break;
default:
printf("<unknown boolop> ");
break;
}
switch(pnode->type) {
case T_STR:
printf("%s\n",pnode->arg2.cval);
break;
case T_INT:
printf("%d\n",pnode->arg2.ival);
break;
case T_DATE:
ptm=localtime((time_t*)&pnode->arg2.ival);
strftime(datebuffer,sizeof(datebuffer),"%Y-%m-%d",ptm);
printf("%s\n",datebuffer);
break;
default:
printf("<unknown type>\n");
break;
}
return;
}
/*
* pl_load
*
* Load a smart playlist
*/
int pl_load(char *file) {
FILE *fin;
int result;
fin=fopen(file,"r");
if(!fin) {
return -1;
}
yyin=fin;
result=yyparse(NULL);
fclose(fin);
if(pl_error) {
return -1;
}
return 0;
}
/*
* pl_register
*
* Register the playlists
*/
void pl_register(void) {
SMART_PLAYLIST *pcurrent;
/* register the playlists */
DPRINTF(E_INF,L_PL,"Finished loading smart playlists\n");
pcurrent=pl_smart.next;
while(pcurrent) {
DPRINTF(E_INF,L_PL,"Adding smart playlist %s as %d\n",pcurrent->name,pcurrent->id)
db_add_playlist(pcurrent->id, pcurrent->name,0,1);
pcurrent=pcurrent->next;
}
}
/*
* pl_eval
*
* Run each MP3 file through the smart playlists
*/
void pl_eval(MP3FILE *pmp3) {
SMART_PLAYLIST *pcurrent;
pcurrent=pl_smart.next;
while(pcurrent) {
if(pl_eval_node(pmp3,pcurrent->root)) {
DPRINTF(E_DBG,L_PL,"Match song to playlist %s (%d)\n",pcurrent->name,pcurrent->id);
db_add_playlist_song(pcurrent->id, pmp3->id);
}
pcurrent=pcurrent->next;
}
}
/*
* pl_eval_node
*
* Test node status
*/
int pl_eval_node(MP3FILE *pmp3, PL_NODE *pnode) {
int r_arg,r_arg2;
char *cval=NULL;
int ival=0;
int boolarg;
int not=0;
int retval=0;
r_arg=r_arg2=0;
if((pnode->op == AND) || (pnode->op == OR)) {
r_arg=pl_eval_node(pmp3,pnode->arg1.plval);
if((pnode->op == AND) && !r_arg)
return 0;
if((pnode->op == OR) && r_arg)
return 1;
r_arg2=pl_eval_node(pmp3,pnode->arg2.plval);
if(pnode->op == AND)
return r_arg && r_arg2;
return r_arg || r_arg2;
}
/* Not an AND/OR node, so let's eval */
switch(pnode->arg1.ival) {
case ALBUM:
cval=pmp3->album;
break;
case ARTIST:
cval=pmp3->artist;
break;
case GENRE:
cval=pmp3->genre;
break;
case PATH:
cval=pmp3->path;
break;
case COMPOSER:
cval=pmp3->composer;
break;
case ORCHESTRA:
cval=pmp3->orchestra;
break;
case CONDUCTOR:
cval=pmp3->conductor;
break;
case GROUPING:
cval=pmp3->grouping;
break;
case TYPE:
cval=pmp3->description;
break;
case COMMENT:
cval=pmp3->comment;
break;
case YEAR:
ival=pmp3->year;
break;
case BPM:
ival=pmp3->bpm;
break;
case BITRATE:
ival=pmp3->bitrate / 1024; // bitrate in Kbps
break;
case DATEADDED:
ival=pmp3->time_added;
break;
default:
DPRINTF(E_FATAL,L_PL,"Unknown token in playlist. This can't happen!\n\n");
break;
}
boolarg=(pnode->op) & 0x7FFFFFFF;
if(pnode->op & 0x80000000)
not=1;
if(pnode->type==T_STR) {
if(!cval)
cval = "";
DPRINTF(E_DBG,L_PL,"Matching %s to %s\n",cval,pnode->arg2.cval);
switch(boolarg) {
case IS:
r_arg=strcasecmp(cval,pnode->arg2.cval);
retval = not ? r_arg : !r_arg;
break;
case INCLUDES:
r_arg=(int)strcasestr(cval,pnode->arg2.cval);
retval = not ? !r_arg : r_arg;
break;
case MATCHES:
r_arg = match(pnode->arg2.cval, cval, 0);
retval = not ? r_arg : !r_arg;
break;
}
}
if(pnode->type==T_DATE) {
DPRINTF(E_DBG,L_PL,"Comparing (datewise) %d to %d\n",ival,pnode->arg2.ival);
switch(boolarg) {
case BEFORE:
r_arg=(ival < pnode->arg2.ival);
break;
case AFTER:
r_arg=(ival > pnode->arg2.ival);
break;
}
retval=r_arg;
}
if(pnode->type==T_INT) {
DPRINTF(E_DBG,L_PL,"Comparing %d to %d\n",ival,pnode->arg2.ival);
switch(boolarg) {
case EQUALS:
r_arg=(ival == pnode->arg2.ival);
break;
case GREATER:
r_arg=(ival > pnode->arg2.ival);
break;
case GREATEREQUAL:
r_arg=(ival >= pnode->arg2.ival);
break;
case LESS:
r_arg=(ival < pnode->arg2.ival);
break;
case LESSEQUAL:
r_arg=(ival <= pnode->arg2.ival);
break;
}
retval = not? !r_arg : r_arg;
}
DPRINTF(E_DBG,L_PL,"Returning %d\n",retval);
return retval;
}

View File

@ -1,62 +0,0 @@
/*
* $Id$
* iTunes-style smart playlists
*
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _PL_H_
#define _PL_H_
#include "mp3-scanner.h"
#define T_INT 0
#define T_STR 1
#define T_DATE 2
typedef struct tag_pl_node {
int op;
int type;
union {
int ival;
struct tag_pl_node *plval;
} arg1;
union {
char *cval;
int ival;
struct tag_pl_node *plval;
} arg2;
} PL_NODE;
typedef struct tag_smart_playlist {
char *name;
unsigned int id;
PL_NODE *root;
struct tag_smart_playlist *next;
} SMART_PLAYLIST;
extern SMART_PLAYLIST pl_smart;
extern int pl_error;
extern void pl_dump(void);
extern int pl_load(char *file);
extern void pl_eval(MP3FILE *pmp3);
extern void pl_register(void);
#endif /* _PL_H_ */

View File

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "err.h"
#include "query.h"
@ -27,10 +28,12 @@ static int get_field_name(const char** pcursor,
const char* query,
char* name,
int len);
/*
static int get_opcode(const char** pcursor,
const char* query,
char* name,
int len);
*/
static query_node_t* match_number(const query_field_t* field,
char not, char opcode,
const char** pcursor,
@ -300,6 +303,9 @@ static query_node_t* match_number(const query_field_t* field,
case qft_i64:
node->right.i64 = strtoll(*pcursor, (char**) pcursor, 10);
break;
default:
DPRINTF(E_LOG,L_QRY,"Bad field type -- invalid query\n");
break;
}
if(**pcursor != '\'')
@ -421,6 +427,9 @@ int query_test(query_node_t* query, void* target)
/* constants */
case qot_const:
return query->left.constant;
default:
return 0;
}
/* should not happen */
return 0;
@ -641,7 +650,7 @@ void query_dump(FILE* fp, query_node_t* query, int depth)
depth, "", labels[query->type],
query->left.field->name, query->right.i32);
else
fprintf(fp, "%*s(%s %s %q)\n",
fprintf(fp, "%*s(%s %s %ll)\n",
depth, "", labels[query->type],
query->left.field->name, query->right.i64);
break;
@ -660,6 +669,8 @@ void query_dump(FILE* fp, query_node_t* query, int depth)
case qot_const:
fprintf(fp, "%*s(%s)\n", depth, "", query->left.constant ? "true" : "false");
break;
default:
break;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,191 +0,0 @@
/*
* RCS $Id$
*/
/*
Redblack balanced tree algorithm
Copyright (C) Damian Ivereigh 2000
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version. See the file COPYING for details.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Header file for redblack.c, should be included by any code that
** uses redblack.c since it defines the functions
*/
/* Stop multiple includes */
#ifndef _REDBLACK_H
#ifndef RB_CUSTOMIZE
/*
* Without customization, the data member in the tree nodes is a void
* pointer, and you need to pass in a comparison function to be
* applied at runtime. With customization, you specify the data type
* as the macro RB_ENTRY(data_t) (has to be a macro because compilers
* gag on typdef void) and the name of the compare function as the
* value of the macro RB_CMP. Because the comparison function is
* compiled in, RB_CMP only needs to take two arguments. If your
* content type is not a pointer, define INLINE to get direct access.
*/
#define rbdata_t void
#define RB_CMP(s, t, e) (*rbinfo->rb_cmp)(s, t, e)
#undef RB_INLINE
#define RB_ENTRY(name) rb##name
#endif /* RB_CUSTOMIZE */
#ifndef RB_STATIC
#define RB_STATIC
#endif
/* Modes for rblookup */
#define RB_NONE -1 /* None of those below */
#define RB_LUEQUAL 0 /* Only exact match */
#define RB_LUGTEQ 1 /* Exact match or greater */
#define RB_LULTEQ 2 /* Exact match or less */
#define RB_LULESS 3 /* Less than key (not equal to) */
#define RB_LUGREAT 4 /* Greater than key (not equal to) */
#define RB_LUNEXT 5 /* Next key after current */
#define RB_LUPREV 6 /* Prev key before current */
#define RB_LUFIRST 7 /* First key in index */
#define RB_LULAST 8 /* Last key in index */
/* For rbwalk - pinched from search.h */
typedef enum
{
preorder,
postorder,
endorder,
leaf
}
VISIT;
struct RB_ENTRY(lists) {
const struct RB_ENTRY(node) *rootp;
const struct RB_ENTRY(node) *nextp;
};
#define RBLIST struct RB_ENTRY(lists)
struct RB_ENTRY(tree) {
#ifndef RB_CUSTOMIZE
/* comparison routine */
int (*rb_cmp)(const void *, const void *, const void *);
/* config data to be passed to rb_cmp */
const void *rb_config;
/* root of tree */
#endif /* RB_CUSTOMIZE */
struct RB_ENTRY(node) *rb_root;
};
#ifndef RB_CUSTOMIZE
RB_STATIC struct RB_ENTRY(tree) *rbinit(int (*)(const void *, const void *, const void *),
const void *);
#else
RB_STATIC struct RB_ENTRY(tree) *RB_ENTRY(init)(void);
#endif /* RB_CUSTOMIZE */
#ifndef no_delete
RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(delete)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *);
#endif
#ifndef no_find
RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(find)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *);
#endif
#ifndef no_lookup
RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(lookup)(int, const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *);
#endif
#ifndef no_search
RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(search)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *);
#endif
#ifndef no_destroy
RB_STATIC void RB_ENTRY(destroy)(struct RB_ENTRY(tree) *);
#endif
#ifndef no_walk
RB_STATIC void RB_ENTRY(walk)(const struct RB_ENTRY(tree) *,
void (*)(const RB_ENTRY(data_t) *, const VISIT, const int, void *),
void *);
#endif
#ifndef no_readlist
RB_STATIC RBLIST *RB_ENTRY(openlist)(const struct RB_ENTRY(tree) *);
RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(readlist)(RBLIST *);
RB_STATIC void RB_ENTRY(closelist)(RBLIST *);
#endif
/* Some useful macros */
#define rbmin(rbinfo) RB_ENTRY(lookup)(RB_LUFIRST, NULL, (rbinfo))
#define rbmax(rbinfo) RB_ENTRY(lookup)(RB_LULAST, NULL, (rbinfo))
#define _REDBLACK_H
#endif /* _REDBLACK_H */
/*
*
* $Log$
* Revision 1.1 2004/03/13 23:43:02 rpedde
* Add Damian Ivereigh's redblack tree implementation to speed lookups
*
* Revision 1.9 2003/10/24 01:31:21 damo
* Patches from Eric Raymond: %prefix is implemented.  Various other small
* changes avoid stepping on global namespaces and improve the documentation.
*
* Revision 1.8 2003/10/23 04:18:47 damo
* Fixed up the rbgen stuff ready for the 1.3 release
*
* Revision 1.7 2002/08/26 03:11:40 damo
* Fixed up a bunch of compiler warnings when compiling example4
*
* Tidies up the Makefile.am & Specfile.
*
* Renamed redblack to rbgen
*
* Revision 1.6 2002/08/26 01:03:35 damo
* Patch from Eric Raymond to change the way the library is used:-
*
* Eric's idea is to convert libredblack into a piece of in-line code
* generated by another program. This should be faster, smaller and easier
* to use.
*
* This is the first check-in of his code before I start futzing with it!
*
* Revision 1.5 2002/01/30 07:54:53 damo
* Fixed up the libtool versioning stuff (finally)
* Fixed bug 500600 (not detecting a NULL return from malloc)
* Fixed bug 509485 (no longer needs search.h)
* Cleaned up debugging section
* Allow multiple inclusions of redblack.h
* Thanks to Matthias Andree for reporting (and fixing) these
*
* Revision 1.4 2000/06/06 14:43:43 damo
* Added all the rbwalk & rbopenlist stuff. Fixed up malloc instead of sbrk.
* Added two new examples
*
* Revision 1.3 2000/05/24 06:45:27 damo
* Converted everything over to using const
* Added a new example1.c file to demonstrate the worst case scenario
* Minor fixups of the spec file
*
* Revision 1.2 2000/05/24 06:17:10 damo
* Fixed up the License (now the LGPL)
*
* Revision 1.1 2000/05/24 04:15:53 damo
* Initial import of files. Versions are now all over the place. Oh well
*
*/

View File

@ -54,10 +54,12 @@ static void rend_stoprunloop(void) {
/*
* rend_sigint
*/
/*
static void rend_sigint(int sigraised) {
DPRINTF(E_INF,L_REND,"SIGINT\n");
rend_stoprunloop();
}
*/
/*
* rend_handler

View File

@ -30,6 +30,8 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "daapd.h"
#include "err.h"

View File

@ -35,6 +35,7 @@
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <sys/select.h>

View File

@ -39,7 +39,7 @@
#include <dirent.h> /* why here? For osx 10.2, of course! */
#include "daapd.h"
#include "db-memory.h"
#include "db-generic.h"
#include "err.h"
#include "mp3-scanner.h"
#include "ssc.h"

View File

@ -246,9 +246,8 @@ int main(int argc, char **argv)
int c;
unsigned long sec = 0, us = 0, samples = 0;
unsigned long offset = 0;
unsigned long skip = 0;
char *end;
FILE *f;
FILE *f=NULL;
unsigned char *hdr;
size_t hdr_len;
size_t data_len;

View File

@ -464,6 +464,9 @@ void ws_close(WS_CONNINFO *pwsc) {
/* DWB: update the status so it doesn't fill up with no longer
relevant entries */
/* FIXME: status handling should be done with a callback or something */
config_set_status(pwsc, 0, NULL);
DPRINTF(E_DBG,L_WS,"Thread %d: Terminating\n",pwsc->threadno);
@ -677,7 +680,6 @@ int ws_encoding_hack(WS_CONNINFO *pwsc) {
*
*/
int ws_getgetvars(WS_CONNINFO *pwsc, char *string) {
char *new_string;
char *first, *last, *middle;
char *key, *value;
int done;