mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-25 06:35:57 -05:00
First pass of sqlite backend
This commit is contained in:
parent
cbc3ddf143
commit
847d10b361
61
configure.in
61
configure.in
@ -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])])
|
||||
|
@ -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');
|
||||
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
628
src/daap-proto.c
628
src/daap-proto.c
@ -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,">");
|
||||
d += 4;
|
||||
s++;
|
||||
break;
|
||||
case '<':
|
||||
strcat(d,"<");
|
||||
d += 4;
|
||||
s++;
|
||||
break;
|
||||
case '"':
|
||||
strcat(d,""");
|
||||
d += 6;
|
||||
s++;
|
||||
break;
|
||||
case '\'':
|
||||
strcat(d,"'");
|
||||
d += 6;
|
||||
s++;
|
||||
break;
|
||||
case '&':
|
||||
strcat(d,"&");
|
||||
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;
|
||||
}
|
||||
|
@ -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
1247
src/daap.c
File diff suppressed because it is too large
Load Diff
1700
src/db-gdbm.c
1700
src/db-gdbm.c
File diff suppressed because it is too large
Load Diff
712
src/db-generic.c
Normal file
712
src/db-generic.c
Normal 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
162
src/db-generic.h
Normal 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_ */
|
746
src/db-memory.c
746
src/db-memory.c
@ -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 ¤t->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;
|
||||
}
|
||||
|
||||
|
@ -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
1045
src/dbs-sqlite.c
Normal file
File diff suppressed because it is too large
Load Diff
42
src/dbs-sqlite.h
Normal file
42
src/dbs-sqlite.h
Normal 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
752
src/dispatch.c
Normal 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
11
src/dispatch.h
Normal 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
|
@ -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.
|
||||
|
149
src/lexer.l
149
src/lexer.l
@ -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);
|
||||
}
|
||||
|
||||
|
503
src/main.c
503
src/main.c
@ -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,30 +477,18 @@ 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));
|
||||
}
|
||||
|
||||
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 */
|
||||
ws_config.web_root=config.web_root;
|
||||
|
@ -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"))) {
|
||||
|
@ -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_ */
|
||||
|
246
src/parser.y
246
src/parser.y
@ -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;
|
||||
}
|
451
src/playlist.c
451
src/playlist.c
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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_ */
|
||||
|
||||
|
13
src/query.c
13
src/query.c
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
1143
src/redblack.c
1143
src/redblack.c
File diff suppressed because it is too large
Load Diff
191
src/redblack.h
191
src/redblack.h
@ -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
|
||||
*
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user