Lots of changes, but notably:

* MiniDLNA can now pass the DLNA Conformance Test!
 * Dependence on libdlna has been removed, and the ffmpeg libs are used directly.
 * Lots of unused code has been cleaned up.
 * File transfers will now be forked off into a new process, so as not to tie up the server when sending data.
This commit is contained in:
Justin Maggard 2009-01-22 00:25:20 +00:00
parent 9867def383
commit 74d73037d0
25 changed files with 769 additions and 608 deletions

View File

@ -12,7 +12,10 @@
# #
#CFLAGS = -Wall -O -D_GNU_SOURCE -g -DDEBUG #CFLAGS = -Wall -O -D_GNU_SOURCE -g -DDEBUG
#CFLAGS = -Wall -g -Os -D_GNU_SOURCE #CFLAGS = -Wall -g -Os -D_GNU_SOURCE
CFLAGS = -Wall -g -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 CFLAGS = -Wall -g -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 \
-I/usr/include/ffmpeg \
-I/usr/include/libavutil -I/usr/include/libavcodec -I/usr/include/libavformat \
-I/usr/include/ffmpeg/libavutil -I/usr/include/ffmpeg/libavcodec -I/usr/include/ffmpeg/libavformat
CC = gcc CC = gcc
RM = rm -f RM = rm -f
INSTALL = install INSTALL = install
@ -30,7 +33,7 @@ BASEOBJS = minidlna.o upnphttp.o upnpdescgen.o upnpsoap.o \
ALLOBJS = $(BASEOBJS) $(LNXOBJS) ALLOBJS = $(BASEOBJS) $(LNXOBJS)
#LIBS = -liptc #LIBS = -liptc
LIBS = -lexif -ltag_c -lsqlite3 -ldlna #-lgd LIBS = -lexif -ltag_c -lsqlite3 -lavformat -luuid #-lgd
TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o

7
TODO
View File

@ -1,13 +1,8 @@
Things left to do: Things left to do:
* Clean up more leftover MiniUPnPd code
* Spawn a new thread for new HTTP connections
* Persistent HTTP connection (Keep-Alive) support * Persistent HTTP connection (Keep-Alive) support
* Real Chunked transfer support
* PNG image support * PNG image support
* DLNA Profile Name (DLNA.ORG_PN) support for video files * DLNA Profile Name (DLNA.ORG_PN) support for more audio files
* DLNA Profile Name (DLNA.ORG_PN) support for audio files besides MP3
* Video metadata support
* SortCriteria support * SortCriteria support
* Completely redo the logging scheme. * Completely redo the logging scheme.
* Update scan support (do not require removing the database) * Update scan support (do not require removing the database)

View File

@ -1,55 +0,0 @@
/* MiniUPnP Project
* http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
* (c) 2006-2008 Thomas Bernard
* generated by ./genconfig.sh on Thu Sep 11 15:05:26 PDT 2008 */
#ifndef __CONFIG_H__
#define __CONFIG_H__
#define UPNP_VERSION "20070827"
#define USE_NETFILTER 1
#define OS_NAME "JM"
#define OS_VERSION "Linux/2.6.25.14-108.fc9.i686"
#define OS_URL "http://www.kernel.org/"
/* syslog facility to be used by miniupnpd */
#define LOG_MINIUPNPD LOG_DAEMON
/* Uncomment the following line to allow miniupnpd to be
* controlled by miniupnpdctl */
/*#define USE_MINIUPNPDCTL*/
/* Comment the following line to disable NAT-PMP operations */
//#define ENABLE_NATPMP
/* Uncomment the following line to enable generation of
* filter rules with pf */
/*#define PF_ENABLE_FILTER_RULES*/
/* Uncomment the following line to enable caching of results of
* the getifstats() function */
/*#define ENABLE_GETIFSTATS_CACHING*/
/* The cache duration is indicated in seconds */
#define GETIFSTATS_CACHING_DURATION 2
/* Uncomment the following line to enable multiple external ip support */
/* note : Thas is EXPERIMENTAL, do not use that unless you know perfectly what you are doing */
/*#define MULTIPLE_EXTERNAL_IP*/
/* Comment the following line to use home made daemonize() func instead
* of BSD daemon() */
#define USE_DAEMON
/* Uncomment the following line to enable lease file support */
/*#define ENABLE_LEASEFILE*/
/* Define one or none of the two following macros in order to make some
* clients happy. It will change the XML Root Description of the IGD.
* Enabling the Layer3Forwarding Service seems to be the more compatible
* option. */
/*#define HAS_DUMMY_SERVICE*/
#define ENABLE_L3F_SERVICE
/* Experimental UPnP Events support. */
#define ENABLE_EVENTS
#endif

View File

@ -13,7 +13,9 @@ CONFIGMACRO="__CONFIG_H__"
# version reported in XML descriptions # version reported in XML descriptions
UPNP_VERSION=20070827 UPNP_VERSION=20070827
# Facility to syslog # Facility to syslog
LOG_MINIUPNPD="LOG_DAEMON" LOG_MINIDLNA="LOG_DAEMON"
# Database path
DB_PATH="/tmp/files.db"
# detecting the OS name and version # detecting the OS name and version
OS_NAME=`uname -s` OS_NAME=`uname -s`
@ -52,7 +54,6 @@ case $OS_NAME in
if [ \( $MAJORVER -ge 4 \) -o \( $MAJORVER -eq 3 -a $MINORVER -ge 8 \) ]; then if [ \( $MAJORVER -ge 4 \) -o \( $MAJORVER -eq 3 -a $MINORVER -ge 8 \) ]; then
echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE}
fi fi
echo "#define USE_PF 1" >> ${CONFIGFILE}
OS_URL=http://www.openbsd.org/ OS_URL=http://www.openbsd.org/
;; ;;
FreeBSD) FreeBSD)
@ -60,25 +61,14 @@ case $OS_NAME in
if [ $VER -ge 700049 ]; then if [ $VER -ge 700049 ]; then
echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE} echo "#define PFRULE_INOUT_COUNTS" >> ${CONFIGFILE}
fi fi
if [ -f /usr/include/net/pfvar.h ] ; then
echo "#define USE_PF 1" >> ${CONFIGFILE}
else
echo "#define USE_IPF 1" >> ${CONFIGFILE}
fi
OS_URL=http://www.freebsd.org/ OS_URL=http://www.freebsd.org/
;; ;;
pfSense) pfSense)
# we need to detect if PFRULE_INOUT_COUNTS macro is needed # we need to detect if PFRULE_INOUT_COUNTS macro is needed
echo "#define USE_PF 1" >> ${CONFIGFILE}
OS_URL=http://www.pfsense.com/ OS_URL=http://www.pfsense.com/
;; ;;
NetBSD) NetBSD)
OS_URL=http://www.netbsd.org/ OS_URL=http://www.netbsd.org/
if [ -f /usr/include/net/pfvar.h ] ; then
echo "#define USE_PF 1" >> ${CONFIGFILE}
else
echo "#define USE_IPF 1" >> ${CONFIGFILE}
fi
;; ;;
SunOS) SunOS)
echo "#define USE_IPF 1" >> ${CONFIGFILE} echo "#define USE_IPF 1" >> ${CONFIGFILE}
@ -104,7 +94,7 @@ case $OS_NAME in
OS_URL=http://www.debian.org/ OS_URL=http://www.debian.org/
fi fi
# use lsb_release (Linux Standard Base) when available # use lsb_release (Linux Standard Base) when available
LSB_RELEASE=`which lsb_release` LSB_RELEASE=`which lsb_release 2>/dev/null`
if [ 0 -eq $? ]; then if [ 0 -eq $? ]; then
OS_NAME=`${LSB_RELEASE} -i -s` OS_NAME=`${LSB_RELEASE} -i -s`
OS_VERSION=`${LSB_RELEASE} -r -s` OS_VERSION=`${LSB_RELEASE} -r -s`
@ -123,8 +113,12 @@ echo "#define OS_VERSION \"$OS_NAME/$OS_VERSION\"" >> ${CONFIGFILE}
echo "#define OS_URL \"${OS_URL}\"" >> ${CONFIGFILE} echo "#define OS_URL \"${OS_URL}\"" >> ${CONFIGFILE}
echo "" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE}
echo "/* full path of the file database */" >> ${CONFIGFILE}
echo "#define DB_PATH \"${DB_PATH}\"" >> ${CONFIGFILE}
echo "" >> ${CONFIGFILE}
echo "/* syslog facility to be used by miniupnpd */" >> ${CONFIGFILE} echo "/* syslog facility to be used by miniupnpd */" >> ${CONFIGFILE}
echo "#define LOG_MINIUPNPD ${LOG_MINIUPNPD}" >> ${CONFIGFILE} echo "#define LOG_MINIDLNA ${LOG_MINIDLNA}" >> ${CONFIGFILE}
echo "" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE}
echo "/* Uncomment the following line to allow miniupnpd to be" >> ${CONFIGFILE} echo "/* Uncomment the following line to allow miniupnpd to be" >> ${CONFIGFILE}
@ -132,15 +126,6 @@ echo " * controlled by miniupnpdctl */" >> ${CONFIGFILE}
echo "/*#define USE_MINIUPNPDCTL*/" >> ${CONFIGFILE} echo "/*#define USE_MINIUPNPDCTL*/" >> ${CONFIGFILE}
echo "" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE}
echo "/* Comment the following line to disable NAT-PMP operations */" >> ${CONFIGFILE}
echo "#define ENABLE_NATPMP" >> ${CONFIGFILE}
echo "" >> ${CONFIGFILE}
echo "/* Uncomment the following line to enable generation of" >> ${CONFIGFILE}
echo " * filter rules with pf */" >> ${CONFIGFILE}
echo "/*#define PF_ENABLE_FILTER_RULES*/">> ${CONFIGFILE}
echo "" >> ${CONFIGFILE}
echo "/* Uncomment the following line to enable caching of results of" >> ${CONFIGFILE} echo "/* Uncomment the following line to enable caching of results of" >> ${CONFIGFILE}
echo " * the getifstats() function */" >> ${CONFIGFILE} echo " * the getifstats() function */" >> ${CONFIGFILE}
echo "/*#define ENABLE_GETIFSTATS_CACHING*/" >> ${CONFIGFILE} echo "/*#define ENABLE_GETIFSTATS_CACHING*/" >> ${CONFIGFILE}
@ -162,16 +147,12 @@ echo "/* Uncomment the following line to enable lease file support */" >> ${CONF
echo "/*#define ENABLE_LEASEFILE*/" >> ${CONFIGFILE} echo "/*#define ENABLE_LEASEFILE*/" >> ${CONFIGFILE}
echo "" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE}
echo "/* Define one or none of the two following macros in order to make some" >> ${CONFIGFILE} echo "/* Experimental UPnP Events support. */" >> ${CONFIGFILE}
echo " * clients happy. It will change the XML Root Description of the IGD." >> ${CONFIGFILE} echo "#define ENABLE_EVENTS" >> ${CONFIGFILE}
echo " * Enabling the Layer3Forwarding Service seems to be the more compatible" >> ${CONFIGFILE}
echo " * option. */" >> ${CONFIGFILE}
echo "/*#define HAS_DUMMY_SERVICE*/" >> ${CONFIGFILE}
echo "#define ENABLE_L3F_SERVICE" >> ${CONFIGFILE}
echo "" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE}
echo "/* Experimental UPnP Events support. */" >> ${CONFIGFILE} echo "/* Enable NETGEAR ReadyNAS-specific tweaks. */" >> ${CONFIGFILE}
echo "/*#define ENABLE_EVENTS*/" >> ${CONFIGFILE} echo "/*#define READYNAS*/" >> ${CONFIGFILE}
echo "" >> ${CONFIGFILE} echo "" >> ${CONFIGFILE}
echo "#endif" >> ${CONFIGFILE} echo "#endif" >> ${CONFIGFILE}

View File

@ -5,6 +5,8 @@
* This software is subject to the conditions detailed * This software is subject to the conditions detailed
* in the LICENCE file provided within the distribution */ * in the LICENCE file provided within the distribution */
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <syslog.h> #include <syslog.h>
#include <unistd.h> #include <unistd.h>
@ -53,3 +55,41 @@ getifaddr(const char * ifname, char * buf, int len)
return 0; return 0;
} }
int
getifhwaddr(const char * ifname, char * buf, int len)
{
/* SIOCGIFADDR struct ifreq * */
int s;
struct ifreq ifr;
int ifrlen;
unsigned char addr[6];
char mac_string[4];
int i;
ifrlen = sizeof(ifr);
if( len < 12 )
{
return -2;
}
s = socket(AF_INET, SOCK_DGRAM, 0);
if(s < 0)
{
syslog(LOG_ERR, "socket(PF_INET, SOCK_DGRAM): %m");
return -1;
}
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
if(ioctl(s, SIOCGIFHWADDR, &ifr, &ifrlen) < 0)
{
syslog(LOG_ERR, "ioctl(s, SIOCGIFHWADDR, ...): %m");
close(s);
return -1;
}
close(s);
memmove( addr, ifr.ifr_hwaddr.sa_data, 6);
for (i=0; i<6; ++i) {
sprintf(mac_string, "%2.2x", addr[i]);
strcat(buf, mac_string);
}
return 0;
}

View File

@ -14,5 +14,8 @@
int int
getifaddr(const char * ifname, char * buf, int len); getifaddr(const char * ifname, char * buf, int len);
int
getifhwaddr(const char * ifname, char * buf, int len);
#endif #endif

View File

@ -15,20 +15,23 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#define LIBDLNA_SUPPORT 1
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <taglib/tag_c.h> #include <taglib/tag_c.h>
#include <libexif/exif-loader.h> #include <libexif/exif-loader.h>
#if LIBDLNA_SUPPORT #include <avutil.h>
#include <dlna.h> #include <avcodec.h>
#endif #include <avformat.h>
#include "upnpglobalvars.h" #include "upnpglobalvars.h"
#include "metadata.h" #include "metadata.h"
@ -36,6 +39,17 @@
#define FLAG_ARTIST 0x01 #define FLAG_ARTIST 0x01
/* Audio profile flags */
#define MP3 0x00000001
#define AC3 0x00000002
#define WMA_BASE 0x00000004
#define WMA_FULL 0x00000008
#define WMA_PRO 0x00000010
#define MP2 0x00000020
#define PCM 0x00000040
#define AAC 0x00000100
#define AAC_MULT5 0x00000200
int int
ends_with(const char * haystack, const char * needle) ends_with(const char * haystack, const char * needle)
{ {
@ -111,6 +125,40 @@ strip_ext(char * name)
*rindex(name, '.') = '\0'; *rindex(name, '.') = '\0';
} }
/* This function shamelessly copied from libdlna */
#define MPEG_TS_SYNC_CODE 0x47
#define MPEG_TS_PACKET_LENGTH_DLNA 192 /* prepends 4 bytes to TS packet */
int
dlna_timestamp_is_present(const char * filename)
{
unsigned char buffer[2*MPEG_TS_PACKET_LENGTH_DLNA+1];
int fd, i;
/* read file header */
fd = open(filename, O_RDONLY);
read(fd, buffer, MPEG_TS_PACKET_LENGTH_DLNA*2);
close(fd);
for( i=0; i < MPEG_TS_PACKET_LENGTH_DLNA; i++ )
{
if( buffer[i] == MPEG_TS_SYNC_CODE )
{
if (buffer[i + MPEG_TS_PACKET_LENGTH_DLNA] == MPEG_TS_SYNC_CODE)
{
if (buffer[i] == 0x00 && buffer [i+1] == 0x00 &&
buffer [i+2] == 0x00 && buffer [i+3] == 0x00)
{
break;
}
else
{
return 1;
}
}
}
}
return 0;
}
sqlite_int64 sqlite_int64
GetFolderMetadata(const char * name, const char * artist) GetFolderMetadata(const char * name, const char * artist)
{ {
@ -239,23 +287,33 @@ GetAudioMetadata(const char * path, char * name)
} }
if( 1 ) // Switch on audio file type /* Switch on audio file type */
if( ends_with(path, ".mp3") )
{ {
strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01"); strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01");
strcpy(mime, "audio/mpeg"); strcpy(mime, "audio/mpeg");
} }
else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") )
{
strcpy(mime, "audio/x-flac");
}
else if( ends_with(path, ".m4a") || ends_with(path, ".aac") )
{
//strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01");
strcpy(mime, "audio/mp4");
}
sql = sqlite3_mprintf( "INSERT into DETAILS" sql = sqlite3_mprintf( "INSERT into DETAILS"
" (SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," " (PATH, SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE,"
" TITLE, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME) " " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME) "
"VALUES" "VALUES"
" (%llu, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s');", " (%Q, %llu, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s');",
size, duration, taglib_audioproperties_channels(properties), path, size, duration, taglib_audioproperties_channels(properties),
taglib_audioproperties_bitrate(properties)*1024, taglib_audioproperties_bitrate(properties)*1024,
taglib_audioproperties_samplerate(properties), taglib_audioproperties_samplerate(properties),
strlen(date) ? date : NULL, strlen(date) ? date : NULL,
title, title,
artist, artist, artist,
album, album,
genre, genre,
comment, comment,
@ -388,10 +446,10 @@ GetImageMetadata(const char * path, char * name)
asprintf(&m.resolution, "%dx%d", width, height); asprintf(&m.resolution, "%dx%d", width, height);
sql = sqlite3_mprintf( "INSERT into DETAILS" sql = sqlite3_mprintf( "INSERT into DETAILS"
" (TITLE, SIZE, DATE, RESOLUTION, THUMBNAIL, CREATOR, DLNA_PN, MIME) " " (PATH, TITLE, SIZE, DATE, RESOLUTION, THUMBNAIL, CREATOR, DLNA_PN, MIME) "
"VALUES" "VALUES"
" ('%q', %llu, '%s', %Q, %d, '%q', %Q, %Q);", " (%Q, '%q', %llu, '%s', %Q, %d, '%q', %Q, %Q);",
name, size, date, m.resolution, thumb, model, m.dlna_pn, m.mime); path, name, size, date, m.resolution, thumb, model, m.dlna_pn, m.mime);
//DEBUG printf("SQL: %s\n", sql); //DEBUG printf("SQL: %s\n", sql);
if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK )
{ {
@ -414,6 +472,36 @@ GetImageMetadata(const char * path, char * name)
return ret; return ret;
} }
typedef enum {
AAC_INVALID = 0,
AAC_MAIN = 1, /* AAC Main */
AAC_LC = 2, /* AAC Low complexity */
AAC_SSR = 3, /* AAC SSR */
AAC_LTP = 4, /* AAC Long term prediction */
AAC_HE = 5, /* AAC High efficiency (SBR) */
AAC_SCALE = 6, /* Scalable */
AAC_TWINVQ = 7, /* TwinVQ */
AAC_CELP = 8, /* CELP */
AAC_HVXC = 9, /* HVXC */
AAC_TTSI = 12, /* TTSI */
AAC_MS = 13, /* Main synthetic */
AAC_WAVE = 14, /* Wavetable synthesis */
AAC_MIDI = 15, /* General MIDI */
AAC_FX = 16, /* Algorithmic Synthesis and Audio FX */
AAC_LC_ER = 17, /* AAC Low complexity with error recovery */
AAC_LTP_ER = 19, /* AAC Long term prediction with error recovery */
AAC_SCALE_ER = 20, /* AAC scalable with error recovery */
AAC_TWINVQ_ER = 21, /* TwinVQ with error recovery */
AAC_BSAC_ER = 22, /* BSAC with error recovery */
AAC_LD_ER = 23, /* AAC LD with error recovery */
AAC_CELP_ER = 24, /* CELP with error recovery */
AAC_HXVC_ER = 25, /* HXVC with error recovery */
AAC_HILN_ER = 26, /* HILN with error recovery */
AAC_PARAM_ER = 27, /* Parametric with error recovery */
AAC_SSC = 28, /* AAC SSC */
AAC_HE_L3 = 31, /* Reserved : seems to be HeAAC L3 */
} aac_object_type_t;
sqlite_int64 sqlite_int64
GetVideoMetadata(const char * path, char * name) GetVideoMetadata(const char * path, char * name)
{ {
@ -421,78 +509,373 @@ GetVideoMetadata(const char * path, char * name)
struct stat file; struct stat file;
char *sql; char *sql;
char *zErrMsg = NULL; char *zErrMsg = NULL;
int ret; int ret, i;
struct tm *modtime;
char date[20];
AVFormatContext *ctx;
int audio_stream = -1, video_stream = -1;
int audio_profile = 0;
ts_timestamp_t ts_timestamp = NONE;
int duration, hours, min, sec, ms;
aac_object_type_t aac_type = 0;
metadata_t m; metadata_t m;
memset(&m, '\0', sizeof(m)); memset(&m, '\0', sizeof(m));
date[0] = '\0';
//DEBUG printf("Parsing %s...\n", path); //DEBUG printf("Parsing %s...\n", path);
if ( stat(path, &file) == 0 ) if ( stat(path, &file) == 0 )
{
modtime = localtime(&file.st_mtime);
strftime(date, sizeof(date), "%FT%T", modtime);
size = file.st_size; size = file.st_size;
}
strip_ext(name); strip_ext(name);
//DEBUG printf(" * size: %d\n", size); //DEBUG printf(" * size: %d\n", size);
#if LIBDLNA_SUPPORT av_register_all();
dlna_t *dlna; if( av_open_input_file(&ctx, path, NULL, 0, NULL) == 0 )
dlna_profile_t *p;
dlna_item_t *item;
dlna = dlna_init();
dlna_register_all_media_profiles(dlna);
item = dlna_item_new(dlna, path);
if (item)
{ {
if (item->properties) av_find_stream_info(ctx);
//dump_format(ctx, 0, NULL, 0);
for( i=0; i<ctx->nb_streams; i++)
{ {
if( strlen(item->properties->duration) ) if( audio_stream == -1 &&
m.duration = strdup(item->properties->duration); ctx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO )
if( item->properties->bitrate ) {
asprintf(&m.bitrate, "%d", item->properties->bitrate); audio_stream = i;
if( item->properties->sample_frequency ) continue;
asprintf(&m.frequency, "%d", item->properties->sample_frequency); }
if( item->properties->bps ) else if( video_stream == -1 &&
asprintf(&m.bps, "%d", item->properties->bps); ctx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
if( item->properties->channels ) {
asprintf(&m.channels, "%d", item->properties->channels); video_stream = i;
m.resolution = item->properties->resolution; continue;
}
} }
} if( audio_stream >= 0 )
{
p = dlna_guess_media_profile(dlna, path); switch( ctx->streams[audio_stream]->codec->codec_id )
if (p) {
{ case CODEC_ID_MP3:
m.mime = strdup(p->mime); audio_profile = MP3;
asprintf(&m.dlna_pn, "%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", p->id); break;
case CODEC_ID_AAC:
if( !ctx->streams[audio_stream]->codec->extradata_size ||
!ctx->streams[audio_stream]->codec->extradata )
printf("No AAC type\n");
else
aac_type = ctx->streams[audio_stream]->codec->extradata[0] >> 3;
switch( aac_type )
{
/* AAC Low Complexity variants */
case AAC_LC:
case AAC_LC_ER:
if( ctx->streams[audio_stream]->codec->sample_rate < 8000 ||
ctx->streams[audio_stream]->codec->sample_rate > 48000 )
{
printf("Unsupported AAC: sample rate is not 8000 < %d < 48000\n",
ctx->streams[audio_stream]->codec->sample_rate);
break;
}
/* AAC @ Level 1/2 */
if( ctx->streams[audio_stream]->codec->channels <= 2 &&
ctx->streams[audio_stream]->codec->bit_rate <= 576000 )
audio_profile = AAC;
else if( ctx->streams[audio_stream]->codec->channels <= 6 &&
ctx->streams[audio_stream]->codec->bit_rate <= 1440000 )
audio_profile = AAC_MULT5;
else
printf("Unhandled AAC: %d channels, %d bitrate\n",
ctx->streams[audio_stream]->codec->channels,
ctx->streams[audio_stream]->codec->bit_rate);
break;
default:
printf("Unhandled AAC type [%d]\n", aac_type);
break;
}
break;
case CODEC_ID_AC3:
case CODEC_ID_DTS:
audio_profile = AC3;
break;
case CODEC_ID_WMAV1:
case CODEC_ID_WMAV2:
/* WMA Baseline: stereo, up to 48 KHz, up to 192,999 bps */
if ( ctx->streams[audio_stream]->codec->bit_rate <= 193000 )
audio_profile = WMA_BASE;
/* WMA Full: stereo, up to 48 KHz, up to 385 Kbps */
else if ( ctx->streams[audio_stream]->codec->bit_rate <= 385000 )
audio_profile = WMA_FULL;
break;
case CODEC_ID_WMAPRO:
audio_profile = WMA_PRO;
break;
case CODEC_ID_MP2:
audio_profile = MP2;
break;
default:
if( (ctx->streams[audio_stream]->codec->codec_id >= CODEC_ID_PCM_S16LE) &&
(ctx->streams[audio_stream]->codec->codec_id <= CODEC_ID_PCM_F64LE) )
audio_profile = PCM;
else
printf("Unhandled audio codec [%X]\n", ctx->streams[audio_stream]->codec->codec_id);
break;
}
asprintf(&m.frequency, "%u", ctx->streams[audio_stream]->codec->sample_rate);
#if LIBAVCODEC_VERSION_MAJOR >= 52
asprintf(&m.bps, "%u", ctx->streams[audio_stream]->codec->bits_per_coded_sample);
#else
asprintf(&m.bps, "%u", ctx->streams[audio_stream]->codec->bits_per_sample);
#endif
asprintf(&m.channels, "%u", ctx->streams[audio_stream]->codec->channels);
}
if( video_stream >= 0 )
{
//DEBUG printf("Container: '%s' [%s]\n", ctx->iformat->name, path);
asprintf(&m.resolution, "%dx%d", ctx->streams[video_stream]->codec->width, ctx->streams[video_stream]->codec->height);
asprintf(&m.bitrate, "%u", ctx->bit_rate / 8);
if( ctx->duration > 0 ) {
duration = (int)(ctx->duration / AV_TIME_BASE);
hours = (int)(duration / 3600);
min = (int)(duration / 60 % 60);
sec = (int)(duration % 60);
ms = (int)(ctx->duration / (AV_TIME_BASE/1000) % 1000);
asprintf(&m.duration, "%d:%02d:%02d.%03d", hours, min, sec, ms);
}
/* NOTE: The DLNA spec only provides for ASF (WMV), TS, PS, and MP4 containers -- not AVI. */
switch( ctx->streams[video_stream]->codec->codec_id )
{
case CODEC_ID_MPEG1VIDEO:
if( strcmp(ctx->iformat->name, "mpeg") == 0 )
{
asprintf(&m.dlna_pn, "MPEG1;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
asprintf(&m.mime, "video/mpeg");
}
break;
case CODEC_ID_MPEG2VIDEO:
if( strcmp(ctx->iformat->name, "mpegts") == 0 )
{
printf("Stream %d of %s is %s MPEG2 TS\n", video_stream, path, m.resolution);
char res;
tsinfo_t * ts = ctx->priv_data;
if( ts->packet_size == 192 )
{
asprintf(&m.dlna_pn, "MPEG_TS_HD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
asprintf(&m.mime, "video/vnd.dlna.mpeg-tts");
}
else if( ts->packet_size == 188 )
{
if( (ctx->streams[video_stream]->codec->width >= 1280) &&
(ctx->streams[video_stream]->codec->height >= 720) )
res = 'H';
else
res = 'S';
asprintf(&m.dlna_pn, "MPEG_TS_%cD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0", res);
asprintf(&m.mime, "video/mpeg");
}
}
else if( strcmp(ctx->iformat->name, "mpeg") == 0 )
{
printf("Stream %d of %s is %s MPEG2 PS\n", video_stream, path, m.resolution);
char region[5];
if( (ctx->streams[video_stream]->codec->height == 576) ||
(ctx->streams[video_stream]->codec->height == 288) )
strcpy(region, "PAL");
else
strcpy(region, "NTSC");
asprintf(&m.dlna_pn, "MPEG_PS_%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", region);
asprintf(&m.mime, "video/mpeg");
}
else
{
printf("Stream %d of %s [UNKNOWN CONTAINER] is %s MPEG2\n", video_stream, path, m.resolution);
}
break;
case CODEC_ID_H264:
if( strcmp(ctx->iformat->name, "mpegts") == 0 )
{
tsinfo_t * ts = ctx->priv_data;
if( ts->packet_size == 192 )
{
if( dlna_timestamp_is_present(path) )
ts_timestamp = VALID;
else
ts_timestamp = EMPTY;
}
char res = '\0';
if( ctx->streams[video_stream]->codec->width <= 720 &&
ctx->streams[video_stream]->codec->height <= 576 &&
ctx->streams[video_stream]->codec->bit_rate <= 10000000 )
res = 'S';
else if( ctx->streams[video_stream]->codec->width <= 1920 &&
ctx->streams[video_stream]->codec->height <= 1152 &&
ctx->streams[video_stream]->codec->bit_rate <= 20000000 )
res = 'H';
if( res )
{
switch( audio_profile )
{
case MP3:
asprintf(&m.dlna_pn, "AVC_TS_MP_HD_MPEG1_L3%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0",
ts_timestamp==NONE?"_ISO" : ts_timestamp==VALID?"_T":"");
break;
case AC3:
asprintf(&m.dlna_pn, "AVC_TS_MP_HD_AC3%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0",
ts_timestamp==NONE?"_ISO" : ts_timestamp==VALID?"_T":"");
break;
case AAC_MULT5:
asprintf(&m.dlna_pn, "AVC_TS_MP_HD_AAC_MULT5%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0",
ts_timestamp==NONE?"_ISO" : ts_timestamp==VALID?"_T":"");
break;
default:
printf("No DLNA profile found for TS/AVC/%cD file %s\n", res, path);
break;
}
if( m.dlna_pn && (ts_timestamp != NONE) )
asprintf(&m.mime, "video/vnd.dlna.mpeg-tts");
}
else
{
printf("Unsupported h.264 video profile! [%dx%d, %dbps]\n",
ctx->streams[video_stream]->codec->width,
ctx->streams[video_stream]->codec->height,
ctx->streams[video_stream]->codec->bit_rate);
}
}
else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 )
{
/* AVC wrapped in MP4 only has SD profiles - 10 Mbps max */
if( ctx->streams[video_stream]->codec->width <= 720 &&
ctx->streams[video_stream]->codec->height <= 576 &&
ctx->streams[video_stream]->codec->bit_rate <= 10000000 )
{
switch( audio_profile )
{
case AC3:
asprintf(&m.dlna_pn, "AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
break;
case AAC_MULT5:
asprintf(&m.dlna_pn, "AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
break;
default:
printf("No DLNA profile found for MP4/AVC/SD file %s\n", path);
break;
}
}
}
printf("Stream %d of %s is h.264\n", video_stream, path);
break;
case CODEC_ID_MPEG4:
if( ctx->streams[video_stream]->codec->codec_tag == ff_get_fourcc("XVID") )
{
printf("Stream %d of %s is %s XViD\n", video_stream, path, m.resolution);
asprintf(&m.mime, "video/divx");
}
else if( ctx->streams[video_stream]->codec->codec_tag == ff_get_fourcc("DX50") )
{
printf("Stream %d of %s is %s DiVX5\n", video_stream, path, m.resolution);
asprintf(&m.mime, "video/divx");
}
else if( ctx->streams[video_stream]->codec->codec_tag == ff_get_fourcc("DIVX") )
{
printf("Stream %d of %s is DiVX\n", video_stream, path);
asprintf(&m.mime, "video/divx");
}
else
{
printf("Stream %d of %s is MPEG4 [%X]\n", video_stream, path, ctx->streams[video_stream]->codec->codec_tag);
}
break;
case CODEC_ID_WMV3:
case CODEC_ID_VC1:
printf("Stream %d of %s is VC1\n", video_stream, path);
char profile[5]; profile[0] = '\0';
asprintf(&m.mime, "video/x-ms-wmv");
if( (ctx->streams[video_stream]->codec->width <= 352) &&
(ctx->streams[video_stream]->codec->height <= 288) &&
(ctx->bit_rate/8 <= 384000) )
{
if( audio_profile == MP3 )
strcpy(profile, "MP3");
else if( audio_profile == WMA_BASE )
strcpy(profile, "BASE");
asprintf(&m.dlna_pn, "WMVSPML_%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", profile);
}
else if( (ctx->streams[video_stream]->codec->width <= 720) &&
(ctx->streams[video_stream]->codec->height <= 576) &&
(ctx->bit_rate/8 <= 10000000) )
{
if( audio_profile == WMA_PRO )
strcpy(profile, "PRO");
else if( audio_profile == WMA_FULL )
strcpy(profile, "FULL");
else if( audio_profile == WMA_BASE )
strcpy(profile, "BASE");
asprintf(&m.dlna_pn, "WMVMED_%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", profile);
}
else if( (ctx->streams[video_stream]->codec->width <= 1920) &&
(ctx->streams[video_stream]->codec->height <= 1080) &&
(ctx->bit_rate/8 <= 20000000) )
{
if( audio_profile == WMA_FULL )
strcpy(profile, "FULL");
else if( audio_profile == WMA_PRO )
strcpy(profile, "PRO");
asprintf(&m.dlna_pn, "WMVHIGH_%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", profile);
}
break;
case CODEC_ID_XVID:
printf("Stream %d of %s is %s UNKNOWN XVID\n", video_stream, path, m.resolution);
break;
case CODEC_ID_MSMPEG4V1:
printf("Stream %d of %s is %s MS MPEG4 v1\n", video_stream, path, m.resolution);
case CODEC_ID_MSMPEG4V3:
printf("Stream %d of %s is %s MS MPEG4 v3\n", video_stream, path, m.resolution);
asprintf(&m.mime, "video/avi");
break;
case CODEC_ID_H263I:
printf("Stream %d of %s is h.263i\n", video_stream, path);
break;
case CODEC_ID_MJPEG:
printf("Stream %d of %s is MJPEG\n", video_stream, path);
break;
default:
printf("Stream %d of %s is %d\n", video_stream, path, ctx->streams[video_stream]->codec->codec_id);
break;
}
}
if( !m.mime )
{
if( strcmp(ctx->iformat->name, "avi") == 0 )
asprintf(&m.mime, "video/x-msvideo");
else if( strcmp(ctx->iformat->name, "mpegts") == 0 )
asprintf(&m.mime, "video/mpeg");
else if( strcmp(ctx->iformat->name, "mpeg") == 0 )
asprintf(&m.mime, "video/mpeg");
else if( strcmp(ctx->iformat->name, "asf") == 0 )
asprintf(&m.mime, "video/x-ms-wmv");
else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 )
asprintf(&m.mime, "video/mp4");
}
av_close_input_file(ctx);
} }
else else
{ {
printf ("Unknown format [%s]\n", path); printf("Opening %s failed!\n", path);
if( ends_with(path, ".mpg") || ends_with(path, ".mpeg") || ends_with(path, ".ts") )
asprintf(&m.mime, "video/mpeg");
else if( ends_with(path, ".avi") || ends_with(path, ".divx") )
asprintf(&m.mime, "video/divx");
} }
sql = sqlite3_mprintf( "INSERT into DETAILS" sql = sqlite3_mprintf( "INSERT into DETAILS"
" (SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION," " (PATH, SIZE, DURATION, DATE, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION,"
" TITLE, DLNA_PN, MIME) " " TITLE, DLNA_PN, MIME) "
"VALUES" "VALUES"
" (%lld, %Q, %Q, %Q, %Q, %Q, '%q', %Q, '%q');", " (%Q, %lld, %Q, %Q, %Q, %Q, %Q, %Q, '%q', %Q, '%q');",
size, m.duration, path, size, m.duration,
strlen(date) ? date : NULL,
m.channels, m.channels,
m.bitrate, m.bitrate,
m.frequency, m.frequency,
m.resolution, m.resolution,
name, m.dlna_pn, m.mime); name, m.dlna_pn, m.mime);
dlna_item_free(item);
dlna_uninit(dlna);
#else // LIBDLNA_SUPPORT
sql = sqlite3_mprintf( "INSERT into DETAILS"
" (TITLE, SIZE, MIME) "
"VALUES"
" ('%q', %d, %Q);",
name, size, "video/mpeg");*/
#endif // LIBDLNA_SUPPORT
//DEBUG printf("SQL: %s\n", sql); //DEBUG printf("SQL: %s\n", sql);
if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK ) if( sqlite3_exec(db, sql, 0, 0, &zErrMsg) != SQLITE_OK )
{ {
@ -512,6 +895,8 @@ GetVideoMetadata(const char * path, char * name)
free(m.mime); free(m.mime);
if( m.duration ) if( m.duration )
free(m.duration); free(m.duration);
if( m.resolution )
free(m.resolution);
if( m.bitrate ) if( m.bitrate )
free(m.bitrate); free(m.bitrate);
if( m.frequency ) if( m.frequency )

View File

@ -26,6 +26,17 @@ typedef struct metadata_s {
char *dlna_pn; char *dlna_pn;
} metadata_t; } metadata_t;
typedef struct tsinfo_s {
int x;
int packet_size;
} tsinfo_t;
typedef enum {
NONE,
EMPTY,
VALID
} ts_timestamp_t;
int int
ends_with(const char * haystack, const char * needle); ends_with(const char * haystack, const char * needle);

View File

@ -208,6 +208,29 @@ parselanaddr(struct lan_addr_s * lan_addr, const char * str)
return 0; return 0;
} }
void
getfriendlyname(char * buf, int len)
{
char * hn = calloc(1, 256);
if( gethostname(hn, 256) == 0 )
{
strncpy(buf, hn, len-1);
buf[len] = '\0';
*strstr(buf, ".") = '\0';
}
else
{
strcpy(buf, "Unknown");
}
free(hn);
strcat(buf, ": ");
#ifdef READYNAS
strncat(buf, "ReadyNAS", len-strlen(buf)-1);
#else
strncat(buf, getenv("LOGNAME"), len-strlen(buf)-1);
#endif
}
/* init phase : /* init phase :
* 1) read configuration file * 1) read configuration file
* 2) read command line arguments * 2) read command line arguments
@ -229,6 +252,7 @@ init(int argc, char * * argv, struct runtime_vars * v)
/*const char * logfilename = 0;*/ /*const char * logfilename = 0;*/
const char * presurl = 0; const char * presurl = 0;
const char * optionsfile = "/etc/minidlna.conf"; const char * optionsfile = "/etc/minidlna.conf";
char * mac_str = calloc(1, 64);
/* first check if "-f" option is used */ /* first check if "-f" option is used */
for(i=2; i<argc; i++) for(i=2; i<argc; i++)
@ -241,6 +265,19 @@ init(int argc, char * * argv, struct runtime_vars * v)
} }
} }
/* set up uuid based on mac address */
if( (getifhwaddr("eth0", mac_str, 64) < 0) &&
(getifhwaddr("eth1", mac_str, 64) < 0) )
{
printf("No MAC addresses found!\n");
strcpy(mac_str, "554e4b4e4f57");
}
strcpy(uuidvalue+5, "4d696e69-444c-164e-9d41-");
strncat(uuidvalue, mac_str, 12);
free(mac_str);
getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN);
/*v->n_lan_addr = 0;*/ /*v->n_lan_addr = 0;*/
char ext_ip_addr[INET_ADDRSTRLEN]; char ext_ip_addr[INET_ADDRSTRLEN];
if( (getifaddr("eth0", ext_ip_addr, INET_ADDRSTRLEN) < 0) && if( (getifaddr("eth0", ext_ip_addr, INET_ADDRSTRLEN) < 0) &&
@ -270,12 +307,6 @@ init(int argc, char * * argv, struct runtime_vars * v)
{ {
switch(ary_options[i].id) switch(ary_options[i].id)
{ {
case UPNPEXT_IFNAME:
ext_if_name = ary_options[i].value;
break;
case UPNPEXT_IP:
use_ext_ip_addr = ary_options[i].value;
break;
case UPNPLISTENING_IP: case UPNPLISTENING_IP:
if(n_lan_addr < MAX_LAN_ADDR)/* if(v->n_lan_addr < MAX_LAN_ADDR)*/ if(n_lan_addr < MAX_LAN_ADDR)/* if(v->n_lan_addr < MAX_LAN_ADDR)*/
{ {
@ -313,10 +344,6 @@ init(int argc, char * * argv, struct runtime_vars * v)
if(strcmp(ary_options[i].value, "yes") == 0) if(strcmp(ary_options[i].value, "yes") == 0)
SETFLAG(LOGPACKETSMASK); /*logpackets = 1;*/ SETFLAG(LOGPACKETSMASK); /*logpackets = 1;*/
break; break;
case UPNPUUID:
strncpy(uuidvalue+5, ary_options[i].value,
strlen(uuidvalue+5) + 1);
break;
case UPNPSERIAL: case UPNPSERIAL:
strncpy(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); strncpy(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN);
serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0';
@ -331,20 +358,6 @@ init(int argc, char * * argv, struct runtime_vars * v)
case UPNPCLEANINTERVAL: case UPNPCLEANINTERVAL:
v->clean_ruleset_interval = atoi(ary_options[i].value); v->clean_ruleset_interval = atoi(ary_options[i].value);
break; break;
#ifdef USE_PF
case UPNPQUEUE:
queue = ary_options[i].value;
break;
case UPNPTAG:
tag = ary_options[i].value;
break;
#endif
#ifdef PF_ENABLE_FILTER_RULES
case UPNPQUICKRULES:
if(strcmp(ary_options[i].value, "no") == 0)
SETFLAG(PFNOQUICKRULESMASK);
break;
#endif
case UPNPSECUREMODE: case UPNPSECUREMODE:
if(strcmp(ary_options[i].value, "yes") == 0) if(strcmp(ary_options[i].value, "yes") == 0)
SETFLAG(SECUREMODEMASK); SETFLAG(SECUREMODEMASK);
@ -355,6 +368,10 @@ init(int argc, char * * argv, struct runtime_vars * v)
remove(lease_file); remove(lease_file);
break; break;
#endif #endif
case UPNPFRIENDLYNAME:
strncpy(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN);
friendly_name[FRIENDLYNAME_MAX_LEN-1] = '\0';
break;
case UPNPMEDIADIR: case UPNPMEDIADIR:
strncpy(media_dir, ary_options[i].value, MEDIADIR_MAX_LEN); strncpy(media_dir, ary_options[i].value, MEDIADIR_MAX_LEN);
media_dir[MEDIADIR_MAX_LEN-1] = '\0'; media_dir[MEDIADIR_MAX_LEN-1] = '\0';
@ -375,24 +392,12 @@ init(int argc, char * * argv, struct runtime_vars * v)
} }
else switch(argv[i][1]) else switch(argv[i][1])
{ {
case 'o':
if(i+1 < argc)
use_ext_ip_addr = argv[++i];
else
fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]);
break;
case 't': case 't':
if(i+1 < argc) if(i+1 < argc)
v->notify_interval = atoi(argv[++i]); v->notify_interval = atoi(argv[++i]);
else else
fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]);
break; break;
case 'u':
if(i+1 < argc)
strncpy(uuidvalue+5, argv[++i], strlen(uuidvalue+5) + 1);
else
fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]);
break;
case 's': case 's':
if(i+1 < argc) if(i+1 < argc)
strncpy(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); strncpy(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN);
@ -421,26 +426,6 @@ init(int argc, char * * argv, struct runtime_vars * v)
case 'S': case 'S':
SETFLAG(SECUREMODEMASK); SETFLAG(SECUREMODEMASK);
break; break;
case 'i':
if(i+1 < argc)
ext_if_name = argv[++i];
else
fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]);
break;
#ifdef USE_PF
case 'q':
if(i+1 < argc)
queue = argv[++i];
else
fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]);
break;
case 'T':
if(i+1 < argc)
tag = argv[++i];
else
fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]);
break;
#endif
case 'p': case 'p':
if(i+1 < argc) if(i+1 < argc)
v->port = atoi(argv[++i]); v->port = atoi(argv[++i]);
@ -510,19 +495,15 @@ init(int argc, char * * argv, struct runtime_vars * v)
fprintf(stderr, "Unknown option: %s\n", argv[i]); fprintf(stderr, "Unknown option: %s\n", argv[i]);
} }
} }
if(!ext_if_name || (/*v->*/n_lan_addr==0) || v->port<=0) if( (/*v->*/n_lan_addr==0) || (v->port<=0) )
{ {
fprintf(stderr, "Usage:\n\t" fprintf(stderr, "Usage:\n\t"
"%s [-f config_file] [-i ext_ifname] [-o ext_ip]\n" "%s [-f config_file] [-i ext_ifname] [-o ext_ip]\n"
"\t\t[-a listening_ip] [-p port] [-d] [-L] [-U] [-S]\n" "\t\t[-a listening_ip] [-p port] [-d] [-L] [-U] [-S]\n"
/*"[-l logfile] " not functionnal */ /*"[-l logfile] " not functionnal */
"\t\t[-u uuid] [-s serial] [-m model_number] \n" "\t\t[-s serial] [-m model_number] \n"
"\t\t[-t notify_interval] [-P pid_filename]\n" "\t\t[-t notify_interval] [-P pid_filename]\n"
#ifdef USE_PF
"\t\t[-B down up] [-w url] [-q queue] [-T tag]\n"
#else
"\t\t[-B down up] [-w url]\n" "\t\t[-B down up] [-w url]\n"
#endif
"\nNotes:\n\tThere can be one or several listening_ips.\n" "\nNotes:\n\tThere can be one or several listening_ips.\n"
"\tNotify interval is in seconds. Default is 30 seconds.\n" "\tNotify interval is in seconds. Default is 30 seconds.\n"
"\tDefault pid file is %s.\n" "\tDefault pid file is %s.\n"
@ -533,10 +514,6 @@ init(int argc, char * * argv, struct runtime_vars * v)
"of daemon uptime.\n" "of daemon uptime.\n"
"\t-B sets bitrates reported by daemon in bits per second.\n" "\t-B sets bitrates reported by daemon in bits per second.\n"
"\t-w sets the presentation url. Default is http address on port 80\n" "\t-w sets the presentation url. Default is http address on port 80\n"
#ifdef USE_PF
"\t-q sets the ALTQ queue in pf.\n"
"\t-T sets the tag name in pf.\n"
#endif
"", argv[0], pidfilename); "", argv[0], pidfilename);
return 1; return 1;
} }
@ -563,7 +540,7 @@ init(int argc, char * * argv, struct runtime_vars * v)
openlog_option |= LOG_PERROR; /* also log on stderr */ openlog_option |= LOG_PERROR; /* also log on stderr */
} }
openlog("miniupnpd", openlog_option, LOG_MINIUPNPD); openlog("minidlna", openlog_option, LOG_MINIDLNA);
if(!debug_flag) if(!debug_flag)
{ {
@ -573,7 +550,7 @@ init(int argc, char * * argv, struct runtime_vars * v)
if(checkforrunning(pidfilename) < 0) if(checkforrunning(pidfilename) < 0)
{ {
syslog(LOG_ERR, "MiniUPnPd is already running. EXITING"); syslog(LOG_ERR, "MiniDLNA is already running. EXITING");
return 1; return 1;
} }
@ -594,9 +571,9 @@ init(int argc, char * * argv, struct runtime_vars * v)
} }
/* set signal handler */ /* set signal handler */
signal(SIGCLD, SIG_IGN);
memset(&sa, 0, sizeof(struct sigaction)); memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = sigterm; sa.sa_handler = sigterm;
if (sigaction(SIGTERM, &sa, NULL)) if (sigaction(SIGTERM, &sa, NULL))
{ {
syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGTERM"); syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGTERM");
@ -641,14 +618,31 @@ main(int argc, char * * argv)
LIST_INIT(&upnphttphead); LIST_INIT(&upnphttphead);
if( access("/tmp/files.db", F_OK) ) if( access(DB_PATH, F_OK) )
{ {
sqlite3_open("/tmp/files.db", &db); sqlite3_open(DB_PATH, &db);
freopen("/dev/null", "a", stderr);
ScanDirectory(media_dir, NULL); ScanDirectory(media_dir, NULL);
freopen("/proc/self/fd/2", "a", stderr);
} }
else else
{ {
sqlite3_open("/tmp/files.db", &db); char **result;
int rows;
sqlite3_open(DB_PATH, &db);
if( sqlite3_get_table(db, "pragma user_version", &result, &rows, 0, 0) == SQLITE_OK )
{
if( atoi(result[1]) != DB_VERSION ) {
printf("Database version mismatch; need to recreate...\n");
sqlite3_close(db);
unlink(DB_PATH);
sqlite3_open(DB_PATH, &db);
freopen("/dev/null", "a", stderr);
ScanDirectory(media_dir, NULL);
freopen("/proc/self/fd/2", "a", stderr);
}
sqlite3_free_table(result);
}
} }

View File

@ -1,6 +1,3 @@
# WAN network interface
ext_ifname=eth0
# port for HTTP (descriptions and SOAP) traffic # port for HTTP (descriptions and SOAP) traffic
port=5555 port=5555
@ -31,8 +28,6 @@ system_uptime=no
notify_interval=900 notify_interval=900
clean_ruleset_interval=600 clean_ruleset_interval=600
# uuid : generate your own with "make genuuid"
uuid=fc4ec57e-b051-11db-88f8-0060085db3f6
# serial and model number the daemon will report to clients # serial and model number the daemon will report to clients
# in its XML description # in its XML description

View File

@ -133,15 +133,15 @@ OpenAndConfSSDPNotifySocket(in_addr_t addr)
} }
memset(&sockname, 0, sizeof(struct sockaddr_in)); memset(&sockname, 0, sizeof(struct sockaddr_in));
sockname.sin_family = AF_INET; sockname.sin_family = AF_INET;
sockname.sin_addr.s_addr = addr; /*inet_addr(addr);*/ sockname.sin_addr.s_addr = addr; /*inet_addr(addr);*/
if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
{ {
syslog(LOG_ERR, "bind(udp_notify): %m"); syslog(LOG_ERR, "bind(udp_notify): %m");
close(s); close(s);
return -1; return -1;
} }
return s; return s;
} }

View File

@ -13,18 +13,6 @@
#define ROOTDESC_PATH "/rootDesc.xml" #define ROOTDESC_PATH "/rootDesc.xml"
#ifdef HAS_DUMMY_SERVICE
#define DUMMY_PATH "/dummy.xml"
#endif
#define WANCFG_PATH "/WANCfg.xml"
#define WANCFG_CONTROLURL "/ctl/CmnIfCfg"
#define WANCFG_EVENTURL "/evt/CmnIfCfg"
#define WANIPC_PATH "/WANIPCn.xml"
#define WANIPC_CONTROLURL "/ctl/IPConn"
#define WANIPC_EVENTURL "/evt/IPConn"
#define CONTENTDIRECTORY_PATH "/ContentDir.xml" #define CONTENTDIRECTORY_PATH "/ContentDir.xml"
#define CONTENTDIRECTORY_CONTROLURL "/ctl/ContentDir" #define CONTENTDIRECTORY_CONTROLURL "/ctl/ContentDir"
#define CONTENTDIRECTORY_EVENTURL "/evt/ContentDir" #define CONTENTDIRECTORY_EVENTURL "/evt/ContentDir"
@ -37,11 +25,5 @@
#define X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL "/ctl/X_MS_MediaReceiverRegistrar" #define X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL "/ctl/X_MS_MediaReceiverRegistrar"
#define X_MS_MEDIARECEIVERREGISTRAR_EVENTURL "/evt/X_MS_MediaReceiverRegistrar" #define X_MS_MEDIARECEIVERREGISTRAR_EVENTURL "/evt/X_MS_MediaReceiverRegistrar"
#ifdef ENABLE_L3F_SERVICE
#define L3F_PATH "/L3F.xml"
#define L3F_CONTROLURL "/ctl/L3F"
#define L3F_EVENTURL "/evt/L3F"
#endif
#endif #endif

View File

@ -36,20 +36,11 @@ static const struct {
{ UPNPMODEL_NUMBER, "model_number"}, { UPNPMODEL_NUMBER, "model_number"},
{ UPNPCLEANTHRESHOLD, "clean_ruleset_threshold"}, { UPNPCLEANTHRESHOLD, "clean_ruleset_threshold"},
{ UPNPCLEANINTERVAL, "clean_ruleset_interval"}, { UPNPCLEANINTERVAL, "clean_ruleset_interval"},
#ifdef ENABLE_NATPMP
{ UPNPENABLENATPMP, "enable_natpmp"},
#endif
{ UPNPENABLE, "enable_upnp"}, { UPNPENABLE, "enable_upnp"},
#ifdef USE_PF
{ UPNPQUEUE, "queue"},
{ UPNPTAG, "tag"},
#endif
#ifdef PF_ENABLE_FILTER_RULES
{ UPNPQUICKRULES, "quickrules" },
#endif
#ifdef ENABLE_LEASEFILE #ifdef ENABLE_LEASEFILE
{ UPNPLEASEFILE, "lease_file"}, { UPNPLEASEFILE, "lease_file"},
#endif #endif
{ UPNPFRIENDLYNAME, "friendly_name"},
{ UPNPMEDIADIR, "media_dir"}, { UPNPMEDIADIR, "media_dir"},
{ UPNPSECUREMODE, "secure_mode"} { UPNPSECUREMODE, "secure_mode"}
}; };

View File

@ -29,17 +29,11 @@ enum upnpconfigoptions {
UPNPCLEANTHRESHOLD, /* clean_ruleset_threshold */ UPNPCLEANTHRESHOLD, /* clean_ruleset_threshold */
UPNPCLEANINTERVAL, /* clean_ruleset_interval */ UPNPCLEANINTERVAL, /* clean_ruleset_interval */
UPNPENABLENATPMP, /* enable_natpmp */ UPNPENABLENATPMP, /* enable_natpmp */
#ifdef USE_PF
UPNPQUEUE, /* queue */
UPNPTAG, /* tag */
#endif
#ifdef PF_ENABLE_FILTER_RULES
UPNPQUICKRULES, /* quickrules */
#endif
UPNPSECUREMODE, /* secure_mode */ UPNPSECUREMODE, /* secure_mode */
#ifdef ENABLE_LEASEFILE #ifdef ENABLE_LEASEFILE
UPNPLEASEFILE, /* lease_file */ UPNPLEASEFILE, /* lease_file */
#endif #endif
UPNPFRIENDLYNAME, /* how the system should show up to DLNA clients */
UPNPMEDIADIR, /* directory to search for UPnP-A/V content */ UPNPMEDIADIR, /* directory to search for UPnP-A/V content */
UPNPENABLE /* enable_upnp */ UPNPENABLE /* enable_upnp */
}; };

108
scanner.c
View File

@ -33,14 +33,19 @@
int int
is_video(const char * file) is_video(const char * file)
{ {
return (ends_with(file, ".mpg") || ends_with(file, ".mpeg") || return (ends_with(file, ".mpg") || ends_with(file, ".mpeg") ||
ends_with(file, ".ts") || ends_with(file, ".avi")); ends_with(file, ".asf") || ends_with(file, ".wmv") ||
ends_with(file, ".mp4") || ends_with(file, ".m4v") ||
ends_with(file, ".mts") || ends_with(file, ".m2ts") ||
ends_with(file, ".vob") || ends_with(file, ".ts") ||
ends_with(file, ".avi") || ends_with(file, ".xvid"));
} }
int int
is_audio(const char * file) is_audio(const char * file)
{ {
return (ends_with(file, ".mp3") || return (ends_with(file, ".mp3") || ends_with(file, ".flac") ||
ends_with(file, ".fla") || ends_with(file, ".flc") ||
ends_with(file, ".m4a") || ends_with(file, ".aac")); ends_with(file, ".m4a") || ends_with(file, ".aac"));
} }
@ -124,21 +129,27 @@ insert_containers(const char * name, const char *path, const char * refID, const
if( strstr(class, "imageItem") ) if( strstr(class, "imageItem") )
{ {
char *date = result[12+cols], *cam = result[16+cols]; char *date = result[13+cols], *cam = result[16+cols];
char date_taken[11]; date_taken[10] = '\0'; char date_taken[11]; date_taken[10] = '\0';
static int last_all_objectID = 0; static int last_all_objectID = 0;
if( date )
strncpy(date_taken, date, 10);
if( date ) if( date )
{ {
if( strcmp(date, "0000-00-00") == 0 )
{
strcpy(date_taken, "Unknown");
}
else
{
strncpy(date_taken, date, 10);
}
container = insert_container("DATES", date_taken, "3$12", NULL, "album.photoAlbum", NULL); container = insert_container("DATES", date_taken, "3$12", NULL, "album.photoAlbum", NULL);
parentID = container>>32; parentID = container>>32;
objectID = container; objectID = container;
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
"VALUES" "VALUES"
" ('3$12$%X$%X', '3$12$%X', '%s', '%s', %lu, %Q, %Q)", " ('3$12$%X$%X', '3$12$%X', '%s', '%s', %lu, %Q)",
parentID, objectID, parentID, refID, class, detailID, path, name); parentID, objectID, parentID, refID, class, detailID, name);
sql_exec(db, sql); sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
} }
@ -153,26 +164,26 @@ insert_containers(const char * name, const char *path, const char * refID, const
int subParentID = subcontainer>>32; int subParentID = subcontainer>>32;
int subObjectID = subcontainer; int subObjectID = subcontainer;
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
"VALUES" "VALUES"
" ('3$13$%X$%X$%X', '3$13$%X$%X', '%s', '%s', %lu, %Q, %Q)", " ('3$13$%X$%X$%X', '3$13$%X$%X', '%s', '%s', %lu, %Q)",
parentID, subParentID, subObjectID, parentID, subParentID, refID, class, detailID, path, name); parentID, subParentID, subObjectID, parentID, subParentID, refID, class, detailID, name);
sql_exec(db, sql); sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
} }
/* All Images */ /* All Images */
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
"VALUES" "VALUES"
" ('3$11$%X', '3$11', '%s', '%s', %lu, %Q, %Q)", " ('3$11$%X', '3$11', '%s', '%s', %lu, %Q)",
last_all_objectID++, refID, class, detailID, path, name); last_all_objectID++, refID, class, detailID, name);
sql_exec(db, sql); sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
} }
else if( strstr(class, "audioItem") ) else if( strstr(class, "audioItem") )
{ {
char *artist = result[6+cols], *album = result[7+cols], *genre = result[8+cols]; char *artist = cols ? result[7+cols]:NULL, *album = cols ? result[8+cols]:NULL, *genre = cols ? result[9+cols]:NULL;
static char last_artist[1024]; static char last_artist[1024] = "0";
static int last_artist_parentID, last_artist_objectID; static int last_artist_parentID, last_artist_objectID;
static char last_album[1024]; static char last_album[1024];
static int last_album_parentID, last_album_objectID; static int last_album_parentID, last_album_objectID;
@ -197,10 +208,10 @@ insert_containers(const char * name, const char *path, const char * refID, const
last_artist_parentID = parentID; last_artist_parentID = parentID;
} }
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
"VALUES" "VALUES"
" ('1$6$%X$%X', '1$6$%X', '%s', '%s', %lu, %Q, %Q)", " ('1$6$%X$%X', '1$6$%X', '%s', '%s', %lu, %Q)",
parentID, objectID, parentID, refID, class, detailID, path, name); parentID, objectID, parentID, refID, class, detailID, name);
sql_exec(db, sql); sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
} }
@ -221,10 +232,10 @@ insert_containers(const char * name, const char *path, const char * refID, const
last_album_parentID = parentID; last_album_parentID = parentID;
} }
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
"VALUES" "VALUES"
" ('1$7$%X$%X', '1$7$%X', '%s', '%s', %lu, %Q, %Q)", " ('1$7$%X$%X', '1$7$%X', '%s', '%s', %lu, %Q)",
parentID, objectID, parentID, refID, class, detailID, path, name); parentID, objectID, parentID, refID, class, detailID, name);
sql_exec(db, sql); sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
} }
@ -245,19 +256,19 @@ insert_containers(const char * name, const char *path, const char * refID, const
last_genre_parentID = parentID; last_genre_parentID = parentID;
} }
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
"VALUES" "VALUES"
" ('1$5$%X$%X', '1$5$%X', '%s', '%s', %lu, %Q, %Q)", " ('1$5$%X$%X', '1$5$%X', '%s', '%s', %lu, %Q)",
parentID, objectID, parentID, refID, class, detailID, path, name); parentID, objectID, parentID, refID, class, detailID, name);
sql_exec(db, sql); sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
} }
/* All Music */ /* All Music */
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
"VALUES" "VALUES"
" ('1$4$%X', '1$4', '%s', '%s', %lu, %Q, %Q)", " ('1$4$%X', '1$4', '%s', '%s', %lu, %Q)",
last_all_objectID++, refID, class, detailID, path, name); last_all_objectID++, refID, class, detailID, name);
sql_exec(db, sql); sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
} }
@ -278,10 +289,10 @@ insert_directory(const char * name, const char * path, const char * parentID, in
for( i=0; base[i]; i++ ) for( i=0; base[i]; i++ )
{ {
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, PATH, NAME) " " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) "
"VALUES" "VALUES"
" ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q', '%q')", " ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')",
base[i], parentID, objectID, base[i], parentID, refID, detailID, class, path, name); base[i], parentID, objectID, base[i], parentID, refID, detailID, class, name);
//DEBUG printf("SQL: %s\n", sql); //DEBUG printf("SQL: %s\n", sql);
ret = sql_exec(db, sql); ret = sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
@ -327,12 +338,14 @@ insert_file(char * name, const char * path, const char * parentID, int object)
detailID = GetVideoMetadata(path, name); detailID = GetVideoMetadata(path, name);
} }
//DEBUG printf("Got DetailID %lu!\n", detailID); //DEBUG printf("Got DetailID %lu!\n", detailID);
if( !detailID )
return -1;
sql = sqlite3_mprintf( "INSERT into OBJECTS" sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, PATH, NAME) " " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) "
"VALUES" "VALUES"
" ('%s', '%s%s', '%s', %lu, '%q', '%q')", " ('%s', '%s%s', '%s', %lu, '%q')",
objectID, BROWSEDIR_ID, parentID, class, detailID, path, name); objectID, BROWSEDIR_ID, parentID, class, detailID, name);
//DEBUG printf("SQL: %s\n", sql); //DEBUG printf("SQL: %s\n", sql);
sql_exec(db, sql); sql_exec(db, sql);
sqlite3_free(sql); sqlite3_free(sql);
@ -378,6 +391,10 @@ create_database(void)
sql_exec(db, "pragma synchronous = OFF;"); sql_exec(db, "pragma synchronous = OFF;");
sql_exec(db, "pragma cache_size = 8192;"); sql_exec(db, "pragma cache_size = 8192;");
//JM: Set up a db version number, so we know if we need to rebuild due to a new structure.
sprintf(sql_buf, "pragma user_version = %d;", DB_VERSION);
sql_exec(db, sql_buf);
ret = sql_exec(db, "CREATE TABLE OBJECTS ( " ret = sql_exec(db, "CREATE TABLE OBJECTS ( "
"ID INTEGER PRIMARY KEY AUTOINCREMENT, " "ID INTEGER PRIMARY KEY AUTOINCREMENT, "
"OBJECT_ID TEXT NOT NULL, " "OBJECT_ID TEXT NOT NULL, "
@ -385,13 +402,13 @@ create_database(void)
"REF_ID TEXT DEFAULT NULL, " "REF_ID TEXT DEFAULT NULL, "
"CLASS TEXT NOT NULL, " "CLASS TEXT NOT NULL, "
"DETAIL_ID INTEGER DEFAULT NULL, " "DETAIL_ID INTEGER DEFAULT NULL, "
"PATH TEXT DEFAULT NULL, "
"NAME TEXT DEFAULT NULL" "NAME TEXT DEFAULT NULL"
");"); ");");
if( ret != SQLITE_OK ) if( ret != SQLITE_OK )
goto sql_failed; goto sql_failed;
ret = sql_exec(db, "CREATE TABLE DETAILS ( " ret = sql_exec(db, "CREATE TABLE DETAILS ( "
"ID INTEGER PRIMARY KEY AUTOINCREMENT, " "ID INTEGER PRIMARY KEY AUTOINCREMENT, "
"PATH TEXT DEFAULT NULL, "
"SIZE INTEGER, " "SIZE INTEGER, "
"TITLE TEXT, " "TITLE TEXT, "
"DURATION TEXT, " "DURATION TEXT, "
@ -433,7 +450,7 @@ create_database(void)
sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);");
sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);");
sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);"); sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);");
sql_exec(db, "create INDEX IDX_OBJECTS_PATH ON OBJECTS(PATH);"); sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);");
sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);"); sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);");
@ -467,6 +484,9 @@ ScanDirectory(const char * dir, const char * parent)
char parent_id[PATH_MAX]; char parent_id[PATH_MAX];
char full_path[PATH_MAX]; char full_path[PATH_MAX];
char * name; char * name;
#if USE_FORK
pid_t newpid;
#endif
if( !parent ) if( !parent )
{ {
@ -475,12 +495,17 @@ ScanDirectory(const char * dir, const char * parent)
fprintf(stderr, "Error creating database!\n"); fprintf(stderr, "Error creating database!\n");
return; return;
} }
#if USE_FORK
newpid = fork();
if( newpid )
return;
#endif
} }
setlocale(LC_COLLATE, ""); setlocale(LC_COLLATE, "");
if( chdir(dir) != 0 ) if( chdir(dir) != 0 )
return; return;
printf("\nScanning %s\n", dir); printf("\nScanning %s\n", dir);
n = scandir(".", &namelist, filter_media, alphasort); n = scandir(".", &namelist, filter_media, alphasort);
if (n < 0) { if (n < 0) {
fprintf(stderr, "Error scanning %s [scandir]\n", dir); fprintf(stderr, "Error scanning %s [scandir]\n", dir);
@ -512,4 +537,11 @@ printf("\nScanning %s\n", dir);
} }
free(namelist); free(namelist);
chdir(".."); chdir("..");
if( !parent )
{
printf("Scanning \"%s\" finished!\n", dir);
#if USE_FORK
_exit(0);
#endif
}
} }

View File

@ -13,12 +13,12 @@
#include "upnpdescgen.h" #include "upnpdescgen.h"
char uuidvalue[] = "uuid:12345678-0000-0000-0000-00000000abcd"; char uuidvalue[] = "uuid:12345678-0000-0000-0000-00000000abcd";
char friendly_name[] = "localhost: system_type";
char serialnumber[] = "12345678"; char serialnumber[] = "12345678";
char modelnumber[] = "1"; char modelnumber[] = "1";
char presentationurl[] = "http://192.168.0.1:8080/"; char presentationurl[] = "http://192.168.0.1:8080/";
char * use_ext_ip_addr = NULL; char * use_ext_ip_addr = NULL;
const char * ext_if_name = "eth0";
int getifaddr(const char * ifname, char * buf, int len) int getifaddr(const char * ifname, char * buf, int len)
{ {

View File

@ -85,10 +85,14 @@ static const char * const upnpallowedvalues[] =
"STOPPED", "STOPPED",
0, 0,
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," /* 44 */ "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," /* 44 */
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01,"
"http-get:*:audio/x-ms-wma:*," "http-get:*:audio/x-ms-wma:*,"
"http-get:*:audio/wav:*," "http-get:*:audio/wav:*,"
@ -129,7 +133,7 @@ static const struct XMLElt rootDesc[] =
{"/major", "1"}, {"/major", "1"},
{"/minor", "0"}, {"/minor", "0"},
{"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"}, {"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"},
{"/friendlyName", ROOTDEV_FRIENDLYNAME}, /* required */ {"/friendlyName", friendly_name}, /* required */
{"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */ {"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */
{"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */ {"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */
{"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */ {"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */
@ -175,12 +179,6 @@ static const struct argument AddPortMappingArgs[] =
{NULL, 0, 0} {NULL, 0, 0}
}; };
static const struct argument GetExternalIPAddressArgs[] =
{
{NULL, 2, 7},
{NULL, 0, 0}
};
static const struct argument DeletePortMappingArgs[] = static const struct argument DeletePortMappingArgs[] =
{ {
{NULL, 1, 11}, {NULL, 1, 11},
@ -202,14 +200,6 @@ static const struct argument GetConnectionTypeInfoArgs[] =
{NULL, 0, 0} {NULL, 0, 0}
}; };
static const struct argument GetStatusInfoArgs[] =
{
{NULL, 2, 2},
{NULL, 2, 4},
{NULL, 2, 3},
{NULL, 0, 0}
};
static const struct argument GetNATRSIPStatusArgs[] = static const struct argument GetNATRSIPStatusArgs[] =
{ {
{NULL, 2, 5}, {NULL, 2, 5},
@ -494,71 +484,6 @@ static const struct argument GetTotalPacketsReceivedArgs[] =
{NULL, 0, 0} {NULL, 0, 0}
}; };
static const struct action WANCfgActions[] =
{
{"GetCommonLinkProperties", GetCommonLinkPropertiesArgs}, /* Required */
{"GetTotalBytesSent", GetTotalBytesSentArgs}, /* optional */
{"GetTotalBytesReceived", GetTotalBytesReceivedArgs}, /* optional */
{"GetTotalPacketsSent", GetTotalPacketsSentArgs}, /* optional */
{"GetTotalPacketsReceived", GetTotalPacketsReceivedArgs}, /* optional */
{0, 0}
};
/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */
static const struct stateVar WANCfgVars[] =
{
{"WANAccessType", 0, 0, 1},
/* Allowed Values : DSL / POTS / Cable / Ethernet
* Default value : empty string */
{"Layer1UpstreamMaxBitRate", 3, 0},
{"Layer1DownstreamMaxBitRate", 3, 0},
{"PhysicalLinkStatus", 0|0x80, 0, 6, 6},
/* allowed values :
* Up / Down / Initializing (optional) / Unavailable (optionnal)
* no Default value
* Evented */
{"TotalBytesSent", 3, 0}, /* Optional */
{"TotalBytesReceived", 3, 0}, /* Optional */
{"TotalPacketsSent", 3, 0}, /* Optional */
{"TotalPacketsReceived", 3, 0},/* Optional */
/*{"MaximumActiveConnections", 2, 0}, // allowed Range value // OPTIONAL */
{0, 0}
};
static const struct serviceDesc scpdWANCfg =
{ WANCfgActions, WANCfgVars };
#ifdef ENABLE_L3F_SERVICE
/* Read UPnP_IGD_Layer3Forwarding_1.0.pdf */
static const struct argument SetDefaultConnectionServiceArgs[] =
{
{NULL, 1, 0}, /* in */
{NULL, 0, 0}
};
static const struct argument GetDefaultConnectionServiceArgs[] =
{
{NULL, 2, 0}, /* out */
{0, 0}
};
static const struct action L3FActions[] =
{
{"SetDefaultConnectionService", SetDefaultConnectionServiceArgs}, /* Req */
{"GetDefaultConnectionService", GetDefaultConnectionServiceArgs}, /* Req */
{0, 0}
};
static const struct stateVar L3FVars[] =
{
{"DefaultConnectionService", 0|0x80, 0, 0, 255}, /* Required */
{0, 0}
};
static const struct serviceDesc scpdL3F =
{ L3FActions, L3FVars };
#endif
static const struct serviceDesc scpdContentDirectory = static const struct serviceDesc scpdContentDirectory =
{ ContentDirectoryActions, ContentDirectoryVars }; { ContentDirectoryActions, ContentDirectoryVars };
//{ ContentDirectoryActions, ContentDirectoryVars }; //{ ContentDirectoryActions, ContentDirectoryVars };
@ -709,9 +634,7 @@ genRootDesc(int * len)
char *ret = calloc(1, 8192); char *ret = calloc(1, 8192);
sprintf(ret, "<?xml version='1.0' encoding='UTF-8' ?>\r\n" sprintf(ret, "<?xml version='1.0' encoding='UTF-8' ?>\r\n"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n" "<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n"
//"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>MiniDLNA (MaggardMachine2)</friendlyName><manufacturer>NETGEAR</manufacturer><manufacturerURL>http://www.netgear.com</manufacturerURL><modelDescription>NETGEAR ReadyNAS NV</modelDescription><modelName>ReadyNAS</modelName><modelNumber>NV</modelNumber><modelURL>http://www.netgear.com</modelURL><UDN>uuid:aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d</UDN><dlna:X_DLNACAP>av-upload,image-upload,create-child-container,audio-upload</dlna:X_DLNACAP><dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>\r\n"
"<serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service></serviceList></device></root>"); "<serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service></serviceList></device></root>");
//"</iconList><serviceList><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><SCPDURL>/ConnectionMgr.xml</SCPDURL><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL></service><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType><serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><SCPDURL>/ContentDir.xml</SCPDURL><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL></service><service><serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType><serviceId>763f907c-8cfb-11dd-a382-c9c0ad9eae41</serviceId><SCPDURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</SCPDURL><controlURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/control/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</controlURL><eventSubURL>/upnp/aefc3d94-8cf7-11dd-b3bb-ff0d6f9a7e6d/events/aefdc437-8cf7-11dd-b3bb-ff0d6f9a7e6d</eventSubURL></service></serviceList></device></root>\r\n");
* len = strlen(ret); * len = strlen(ret);
return ret; return ret;
#endif #endif

View File

@ -68,11 +68,6 @@ genWANIPCn(int * len);
char * char *
genWANCfg(int * len); genWANCfg(int * len);
#ifdef ENABLE_L3F_SERVICE
char *
genL3F(int * len);
#endif
#ifdef ENABLE_EVENTS #ifdef ENABLE_EVENTS
char * char *
getVarsContentDirectory(int * len); getVarsContentDirectory(int * len);
@ -81,13 +76,7 @@ char *
getVarsConnectionManager(int * len); getVarsConnectionManager(int * len);
char * char *
getVarsWANIPCn(int * len); getVarsX_MS_MediaReceiverRegistrar(int * len);
char *
getVarsWANCfg(int * len);
char *
getVarsL3F(int * len);
#endif #endif
#endif #endif

View File

@ -9,30 +9,14 @@
#include "config.h" #include "config.h"
/* strings used in the root device xml description */ /* strings used in the root device xml description */
#define ROOTDEV_FRIENDLYNAME "MiniDLNA ReadyNAS:" #ifdef READYNAS
#define ROOTDEV_MANUFACTURER "NETGEAR" #define ROOTDEV_MANUFACTURER "NETGEAR"
#else
#define ROOTDEV_MANUFACTURER "Justin Maggard"
#endif
#define ROOTDEV_MANUFACTURERURL OS_URL #define ROOTDEV_MANUFACTURERURL OS_URL
#define ROOTDEV_MODELNAME "Windows Media Connect compatible (minidlna)" #define ROOTDEV_MODELNAME "Windows Media Connect compatible (minidlna)"
#define ROOTDEV_MODELDESCRIPTION OS_NAME " *ReadyNAS dev DLNA" #define ROOTDEV_MODELDESCRIPTION OS_NAME " *ReadyNAS dev DLNA"
#define ROOTDEV_MODELURL OS_URL #define ROOTDEV_MODELURL OS_URL
#define WANDEV_FRIENDLYNAME "WANDevice"
#define WANDEV_MANUFACTURER "MiniUPnP"
#define WANDEV_MANUFACTURERURL "http://miniupnp.free.fr/"
#define WANDEV_MODELNAME "WAN Device"
#define WANDEV_MODELDESCRIPTION "WAN Device"
#define WANDEV_MODELNUMBER UPNP_VERSION
#define WANDEV_MODELURL "http://miniupnp.free.fr/"
#define WANDEV_UPC "MINIUPNPD"
#define WANCDEV_FRIENDLYNAME "WANConnectionDevice"
#define WANCDEV_MANUFACTURER WANDEV_MANUFACTURER
#define WANCDEV_MANUFACTURERURL WANDEV_MANUFACTURERURL
#define WANCDEV_MODELNAME "MiniUPnPd"
#define WANCDEV_MODELDESCRIPTION "MiniUPnP daemon"
#define WANCDEV_MODELNUMBER UPNP_VERSION
#define WANCDEV_MODELURL "http://miniupnp.free.fr/"
#define WANCDEV_UPC "MINIUPNPD"
#endif #endif

View File

@ -24,6 +24,12 @@
#include "upnpglobalvars.h" #include "upnpglobalvars.h"
#include "upnpdescgen.h" #include "upnpdescgen.h"
#define HAVE_UUID 1
#ifdef HAVE_UUID
#include <uuid/uuid.h>
#endif
#ifdef ENABLE_EVENTS #ifdef ENABLE_EVENTS
/*enum subscriber_service_enum { /*enum subscriber_service_enum {
EWanCFG = 1, EWanCFG = 1,
@ -80,18 +86,12 @@ newSubscriber(const char * eventurl, const char * callback, int callbacklen)
if(!eventurl || !callback || !callbacklen) if(!eventurl || !callback || !callbacklen)
return NULL; return NULL;
tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1);
if(strcmp(eventurl, WANCFG_EVENTURL)==0) if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0)
tmp->service = EWanCFG;
else if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0)
tmp->service = EContentDirectory; tmp->service = EContentDirectory;
else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0) else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0)
tmp->service = EConnectionManager; tmp->service = EConnectionManager;
else if(strcmp(eventurl, WANIPC_EVENTURL)==0) else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0)
tmp->service = EWanIPC; tmp->service = EMSMediaReceiverRegistrar;
#ifdef ENABLE_L3F_SERVICE
else if(strcmp(eventurl, L3F_EVENTURL)==0)
tmp->service = EL3F;
#endif
else { else {
free(tmp); free(tmp);
return NULL; return NULL;
@ -99,10 +99,15 @@ newSubscriber(const char * eventurl, const char * callback, int callbacklen)
memcpy(tmp->callback, callback, callbacklen); memcpy(tmp->callback, callback, callbacklen);
tmp->callback[callbacklen] = '\0'; tmp->callback[callbacklen] = '\0';
/* make a dummy uuid */ /* make a dummy uuid */
/* TODO: improve that */
strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid)); strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid));
#ifdef HAVE_UUID
uuid_t uuid;
uuid_generate_time(uuid);
uuid_unparse_lower(uuid, tmp->uuid+5);
#else
tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; tmp->uuid[sizeof(tmp->uuid)-1] = '\0';
snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff);
#endif
return tmp; return tmp;
} }
@ -136,7 +141,7 @@ renewSubscription(const char * sid, int sidlen, int timeout)
{ {
struct subscriber * sub; struct subscriber * sub;
for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
if(memcmp(sid, sub->uuid, 41)) { if(memcmp(sid, sub->uuid, 41) == 0) {
sub->timeout = (timeout ? time(NULL) + timeout : 0); sub->timeout = (timeout ? time(NULL) + timeout : 0);
return 0; return 0;
} }
@ -290,11 +295,14 @@ static void upnp_event_prepare(struct upnp_event_notify * obj)
case EConnectionManager: case EConnectionManager:
xml = getVarsConnectionManager(&l); xml = getVarsConnectionManager(&l);
break; break;
case EMSMediaReceiverRegistrar:
xml = getVarsX_MS_MediaReceiverRegistrar(&l);
break;
default: default:
xml = NULL; xml = NULL;
l = 0; l = 0;
} }
obj->buffersize = 1536; obj->buffersize = 2048;
obj->buffer = malloc(obj->buffersize); obj->buffer = malloc(obj->buffersize);
/*if(!obj->buffer) { /*if(!obj->buffer) {
}*/ }*/

View File

@ -8,11 +8,9 @@
#define __UPNPEVENTS_H__ #define __UPNPEVENTS_H__
#ifdef ENABLE_EVENTS #ifdef ENABLE_EVENTS
enum subscriber_service_enum { enum subscriber_service_enum {
EWanCFG = 1, EContentDirectory = 1,
EContentDirectory,
EConnectionManager, EConnectionManager,
EWanIPC, EMSMediaReceiverRegistrar
EL3F
}; };
void void

View File

@ -11,9 +11,6 @@
#include "config.h" #include "config.h"
#include "upnpglobalvars.h" #include "upnpglobalvars.h"
/* network interface for internet */
const char * ext_if_name = 0;
/* file to store leases */ /* file to store leases */
#ifdef ENABLE_LEASEFILE #ifdef ENABLE_LEASEFILE
const char* lease_file = 0; const char* lease_file = 0;
@ -39,9 +36,6 @@ int sysuptime = 0;
/* log packets flag */ /* log packets flag */
int logpackets = 0; int logpackets = 0;
#ifdef ENABLE_NATPMP
int enablenatpmp = 0;
#endif
#endif #endif
int runtime_flags = 0; int runtime_flags = 0;
@ -61,21 +55,10 @@ char presentationurl[PRESENTATIONURL_MAX_LEN];
struct upnpperm * upnppermlist = 0; struct upnpperm * upnppermlist = 0;
unsigned int num_upnpperm = 0; unsigned int num_upnpperm = 0;
#ifdef ENABLE_NATPMP
/* NAT-PMP */
unsigned int nextnatpmptoclean_timestamp = 0;
unsigned short nextnatpmptoclean_eport = 0;
unsigned short nextnatpmptoclean_proto = 0;
#endif
#ifdef USE_PF
const char * queue = 0;
const char * tag = 0;
#endif
int n_lan_addr = 0; int n_lan_addr = 0;
struct lan_addr_s lan_addr[MAX_LAN_ADDR]; struct lan_addr_s lan_addr[MAX_LAN_ADDR];
/* UPnP-A/V [DLNA] */ /* UPnP-A/V [DLNA] */
sqlite3 *db; sqlite3 *db;
char media_dir[256]; char media_dir[MEDIADIR_MAX_LEN];
char friendly_name[FRIENDLYNAME_MAX_LEN];

View File

@ -13,8 +13,8 @@
#include <sqlite3.h> #include <sqlite3.h>
/* name of the network interface used to acces internet */ #define USE_FORK 1
extern const char * ext_if_name; #define DB_VERSION 1
/* file to store all leases */ /* file to store all leases */
#ifdef ENABLE_LEASEFILE #ifdef ENABLE_LEASEFILE
@ -36,16 +36,9 @@ extern time_t startup_time;
extern int runtime_flags; extern int runtime_flags;
#define LOGPACKETSMASK 0x0001 #define LOGPACKETSMASK 0x0001
#define SYSUPTIMEMASK 0x0002 #define SYSUPTIMEMASK 0x0002
#ifdef ENABLE_NATPMP
#define ENABLENATPMPMASK 0x0004
#endif
#define CHECKCLIENTIPMASK 0x0008 #define CHECKCLIENTIPMASK 0x0008
#define SECUREMODEMASK 0x0010 #define SECUREMODEMASK 0x0010
#ifdef PF_ENABLE_FILTER_RULES
#define PFNOQUICKRULESMASK 0x0040
#endif
#define SETFLAG(mask) runtime_flags |= mask #define SETFLAG(mask) runtime_flags |= mask
#define GETFLAG(mask) runtime_flags & mask #define GETFLAG(mask) runtime_flags & mask
#define CLEARFLAG(mask) runtime_flags &= ~mask #define CLEARFLAG(mask) runtime_flags &= ~mask
@ -67,19 +60,6 @@ extern char presentationurl[];
extern struct upnpperm * upnppermlist; extern struct upnpperm * upnppermlist;
extern unsigned int num_upnpperm; extern unsigned int num_upnpperm;
#ifdef ENABLE_NATPMP
/* NAT-PMP */
extern unsigned int nextnatpmptoclean_timestamp;
extern unsigned short nextnatpmptoclean_eport;
extern unsigned short nextnatpmptoclean_proto;
#endif
#ifdef USE_PF
/* queue and tag for PF rules */
extern const char * queue;
extern const char * tag;
#endif
/* lan addresses */ /* lan addresses */
/* MAX_LAN_ADDR : maximum number of interfaces /* MAX_LAN_ADDR : maximum number of interfaces
* to listen to SSDP traffic */ * to listen to SSDP traffic */
@ -91,5 +71,7 @@ extern struct lan_addr_s lan_addr[];
extern sqlite3 *db; extern sqlite3 *db;
#define MEDIADIR_MAX_LEN (256) #define MEDIADIR_MAX_LEN (256)
extern char media_dir[]; extern char media_dir[];
#define FRIENDLYNAME_MAX_LEN (64)
extern char friendly_name[];
#endif #endif

View File

@ -367,25 +367,6 @@ findendheaders(const char * s, int len)
return NULL; return NULL;
} }
#ifdef HAS_DUMMY_SERVICE
static void
sendDummyDesc(struct upnphttp * h)
{
static const char xml_desc[] = "<?xml version=\"1.0\"?>\r\n"
"<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">"
" <specVersion>"
" <major>1</major>"
" <minor>0</minor>"
" </specVersion>"
" <actionList />"
" <serviceStateTable />"
"</scpd>\r\n";
BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
#endif
/* Sends the description generated by the parameter */ /* Sends the description generated by the parameter */
static void static void
sendXMLdesc(struct upnphttp * h, char * (f)(int *)) sendXMLdesc(struct upnphttp * h, char * (f)(int *))
@ -551,17 +532,31 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
HttpVer[i] = '\0'; HttpVer[i] = '\0';
syslog(LOG_INFO, "HTTP REQUEST : %s %s (%s)", syslog(LOG_INFO, "HTTP REQUEST : %s %s (%s)",
HttpCommand, HttpUrl, HttpVer); HttpCommand, HttpUrl, HttpVer);
//DEBUG printf("HTTP REQUEST:\n%s\n", h->req_buf); //DEBUG printf("HTTP REQUEST:\n%.*s\n", h->req_buflen, h->req_buf);
ParseHttpHeaders(h); ParseHttpHeaders(h);
if( (h->reqflags & FLAG_CHUNKED) && (h->req_chunklen > (h->req_buflen - h->req_contentoff) || h->req_chunklen < 0) ) /* see if we need to wait for remaining data */
if( (h->reqflags & FLAG_CHUNKED) )
{ {
/* waiting for remaining data */ char * chunkstart = h->req_buf+h->req_contentoff;
printf("*** %d < %d\n", (h->req_buflen - h->req_contentoff), h->req_contentlen); char * numstart;
printf("Chunked request [%ld]. Need more input.\n", h->req_chunklen);
h->state = 2; h->state = 2;
while( h->req_chunklen )
{
if( chunkstart >= (h->req_buf+h->req_buflen) )
return;
numstart = chunkstart+h->req_chunklen+2;
h->req_chunklen = strtol(numstart, &chunkstart, 16);
if( !h->req_chunklen && (chunkstart == numstart) )
{
printf("Chunked request needs more input.\n");
return;
}
chunkstart = chunkstart+2;
}
h->state = 100;
} }
else if(strcmp("POST", HttpCommand) == 0) if(strcmp("POST", HttpCommand) == 0)
{ {
h->req_command = EPost; h->req_command = EPost;
ProcessHTTPPOST_upnphttp(h); ProcessHTTPPOST_upnphttp(h);
@ -602,12 +597,6 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
{ {
sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); sendXMLdesc(h, genX_MS_MediaReceiverRegistrar);
} }
#ifdef HAS_DUMMY_SERVICE
else if(strcmp(DUMMY_PATH, HttpUrl) == 0)
{
sendDummyDesc(h);
}
#endif
else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0)
{ {
SendResp_dlnafile(h, HttpUrl+12); SendResp_dlnafile(h, HttpUrl+12);
@ -864,6 +853,7 @@ SendResp_thumbnail(struct upnphttp * h, char * object)
char header[1500]; char header[1500];
char sql_buf[256]; char sql_buf[256];
char **result; char **result;
int rows;
char date[30]; char date[30];
time_t curtime = time(NULL); time_t curtime = time(NULL);
int n; int n;
@ -879,8 +869,14 @@ SendResp_thumbnail(struct upnphttp * h, char * object)
return; return;
} }
sprintf(sql_buf, "SELECT PATH from OBJECTS where OBJECT_ID = '%s'", object); sprintf(sql_buf, "SELECT d.PATH from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object);
sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0);
if( !rows )
{
syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object);
Send404(h);
goto error;
}
printf("Serving up thumbnail for ObjectId: %s [%s]\n", object, result[1]); printf("Serving up thumbnail for ObjectId: %s [%s]\n", object, result[1]);
if( access(result[1], F_OK) == 0 ) if( access(result[1], F_OK) == 0 )
@ -976,7 +972,7 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
return; return;
} }
sprintf(sql_buf, "SELECT o.PATH, d.WIDTH, d.HEIGHT from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); sprintf(sql_buf, "SELECT d.PATH, d.WIDTH, d.HEIGHT from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object);
sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); sqlite3_get_table(db, sql_buf, &result, 0, 0, 0);
printf("Serving up resized image for ObjectId: %s [%s]\n", object, result[1]); printf("Serving up resized image for ObjectId: %s [%s]\n", object, result[1]);
@ -1073,17 +1069,26 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
time_t curtime = time(NULL); time_t curtime = time(NULL);
off_t total, send_size; off_t total, send_size;
char *path, *mime, *dlna; char *path, *mime, *dlna;
#if USE_FORK
pid_t newpid = 0;
#endif
memset(header, 0, 1500); memset(header, 0, 1500);
sprintf(sql_buf, "SELECT o.PATH, d.MIME, d.DLNA_PN from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); sprintf(sql_buf, "SELECT d.PATH, d.MIME, d.DLNA_PN from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object);
sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0);
if( !rows ) if( !rows )
{ {
syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object); syslog(LOG_NOTICE, "%s not found, responding ERROR 404", object);
Send404(h); Send404(h);
goto error; sqlite3_free_table(result);
return;
} }
#if USE_FORK
newpid = fork();
if( newpid )
return;
#endif
path = result[3]; path = result[3];
mime = result[4]; mime = result[4];
@ -1186,8 +1191,8 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
} }
else //if( h->reqflags & FLAG_XFERINTERACTIVE ) else //if( h->reqflags & FLAG_XFERINTERACTIVE )
{ {
if( (strncmp(mime, "vide", 4) == 0) || if( (strncmp(mime, "video", 5) == 0) ||
(strncmp(mime, "audi", 4) == 0) ) (strncmp(mime, "audio", 5) == 0) )
strcat(header, "transferMode.dlna.org: Streaming\r\n"); strcat(header, "transferMode.dlna.org: Streaming\r\n");
else else
strcat(header, "transferMode.dlna.org: Interactive\r\n"); strcat(header, "transferMode.dlna.org: Interactive\r\n");
@ -1240,4 +1245,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
} }
error: error:
sqlite3_free_table(result); sqlite3_free_table(result);
#if USE_FORK
_exit(0);
#endif
} }

View File

@ -64,28 +64,6 @@ BuildSendAndCloseSoapResp(struct upnphttp * h,
CloseSocket_upnphttp(h); CloseSocket_upnphttp(h);
} }
static void
GetStatusInfo(struct upnphttp * h, const char * action)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewConnectionStatus>Connected</NewConnectionStatus>"
"<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>"
"<NewUptime>%ld</NewUptime>"
"</u:%sResponse>";
char body[512];
int bodylen;
time_t uptime;
uptime = (time(NULL) - startup_time);
bodylen = snprintf(body, sizeof(body), resp,
action, "urn:schemas-upnp-org:service:WANIPConnection:1",
(long)uptime, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void static void
GetSystemUpdateID(struct upnphttp * h, const char * action) GetSystemUpdateID(struct upnphttp * h, const char * action)
{ {
@ -129,16 +107,15 @@ GetProtocolInfo(struct upnphttp * h, const char * action)
"<u:%sResponse " "<u:%sResponse "
"xmlns:u=\"%s\">" "xmlns:u=\"%s\">"
"<Source>" "<Source>"
/*"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_CI=1,"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01,"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01,"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01,"
"http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01,"*/
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN,"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01," "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01," "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0,"
"http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01,"
"http-get:*:audio/x-ms-wma:*," "http-get:*:audio/x-ms-wma:*,"
"http-get:*:audio/wav:*," "http-get:*:audio/wav:*,"
@ -212,7 +189,7 @@ GetCurrentConnectionIDs(struct upnphttp * h, const char * action)
static const char resp[] = static const char resp[] =
"<u:%sResponse " "<u:%sResponse "
"xmlns:u=\"%s\">" "xmlns:u=\"%s\">"
"<ConnectionIDs>-1</ConnectionIDs>" "<ConnectionIDs>0</ConnectionIDs>"
"</u:%sResponse>"; "</u:%sResponse>";
char body[512]; char body[512];
@ -378,13 +355,16 @@ static int callback(void *args, int argc, char **argv, char **azColName)
sprintf(str_buf, "childCount=\"%s\"", result[1]); sprintf(str_buf, "childCount=\"%s\"", result[1]);
strcat(passed_args->resp, str_buf); strcat(passed_args->resp, str_buf);
} }
/* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s */ /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) ) if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
{ {
strcat(passed_args->resp, "&gt;" if( !passed_args->filter || strstr(passed_args->filter, "upnp:searchClass") )
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;" {
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;" strcat(passed_args->resp, "&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass"); "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
}
} }
sprintf(str_buf, "&gt;" sprintf(str_buf, "&gt;"
"&lt;dc:title&gt;%s&lt;/dc:title&gt;" "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
@ -575,49 +555,6 @@ SearchContentDirectory(struct upnphttp * h, const char * action)
free(resp); free(resp);
} }
static void
GetExternalIPAddress(struct upnphttp * h, const char * action)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewExternalIPAddress>%s</NewExternalIPAddress>"
"</u:%sResponse>";
char body[512];
int bodylen;
char ext_ip_addr[INET_ADDRSTRLEN];
#ifndef MULTIPLE_EXTERNAL_IP
if(use_ext_ip_addr)
{
strncpy(ext_ip_addr, use_ext_ip_addr, INET_ADDRSTRLEN);
}
else if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN) < 0)
{
syslog(LOG_ERR, "Failed to get ip address for interface %s",
ext_if_name);
strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN);
}
#else
int i;
strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN);
for(i = 0; i<n_lan_addr; i++)
{
if( (h->clientaddr.s_addr & lan_addr[i].mask.s_addr)
== (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
{
strncpy(ext_ip_addr, lan_addr[i].ext_ip_str, INET_ADDRSTRLEN);
break;
}
}
#endif
bodylen = snprintf(body, sizeof(body), resp,
action, "urn:schemas-upnp-org:service:WANIPConnection:1",
ext_ip_addr, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
/* /*
If a control point calls QueryStateVariable on a state variable that is not If a control point calls QueryStateVariable on a state variable that is not
buffered in memory within (or otherwise available from) the service, buffered in memory within (or otherwise available from) the service,
@ -659,7 +596,7 @@ QueryStateVariable(struct upnphttp * h, const char * action)
BuildSendAndCloseSoapResp(h, body, bodylen); BuildSendAndCloseSoapResp(h, body, bodylen);
} }
#if 0 #if 0
/* not usefull */ /* not useful */
else if(strcmp(var_name, "ConnectionType") == 0) else if(strcmp(var_name, "ConnectionType") == 0)
{ {
bodylen = snprintf(body, sizeof(body), resp, "IP_Routed"); bodylen = snprintf(body, sizeof(body), resp, "IP_Routed");
@ -687,9 +624,7 @@ static const struct
} }
soapMethods[] = soapMethods[] =
{ {
{ "GetExternalIPAddress", GetExternalIPAddress},
{ "QueryStateVariable", QueryStateVariable}, { "QueryStateVariable", QueryStateVariable},
{ "GetStatusInfo", GetStatusInfo},
{ "Browse", BrowseContentDirectory}, { "Browse", BrowseContentDirectory},
{ "Search", SearchContentDirectory}, { "Search", SearchContentDirectory},
{ "GetSearchCapabilities", GetSearchCapabilities}, { "GetSearchCapabilities", GetSearchCapabilities},