/* daapd 0.2.4, a server for the DAA protocol (c) deleet 2003, Alexander Oberdoerster main program daapd 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. daapd 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 daapd; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "types.h" #include "dboutput.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HOWL_ENABLE #include #endif const int DAAP_OK = 200; const char* DAAPD_VERSION = "daapd 0.2.4pre"; #if( UCHAR_MAX != 0xFF ) #error "change size of 'char' to 8-bit" #endif #ifndef __BASIC_DAAP__ #error please install daaplib (http://www.deleet.de/projekte/daap/daaplib) #endif using namespace std; std::ostream& operator << ( ostream& out, const vector *stringvect ) { u32 i; for( i = 0; i < stringvect->size() - 1; ++i ) out << (*stringvect)[i] << ", "; if( stringvect->size() != 0 ) out << (*stringvect)[i]; return( out ); } // httpd helper /*#define USEC_PER_SECOND 1000000 double get_time() { struct timeval tv; double res; if (gettimeofday(&tv,NULL)) { perror("get_time"); return -1.0; } res = (tv.tv_sec + ((double) tv.tv_usec) / USEC_PER_SECOND); return res; } */ void sendTag( httpd* server, const Chunk& c, const bool zipped ) { httpdSetContentType( server, "application/x-dmap-tagged" ); if ( strncmp( httpdRequestAcceptEncoding( server ), "gzip", 4) == 0 && c.size() > 1000 && zipped ) { // double before = get_time(); z_stream strm; int status = 0; int compressed = false; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.next_in = (Bytef *)c.begin(); strm.avail_in = c.size(); int zBufLen = (int) ceil(1.001 * c.size()) + 12 + 6; // gzip has 18 Bytes Header + Trailer Bytef *zBuffer = (Bytef *)malloc( zBufLen ); strm.next_out = zBuffer; strm.avail_out = zBufLen; int windowBits = 15 // the default + 16; // for gzip header; // see deflateInit2() documentation in zlib.h // compression level: 1 = fastest, 6 = Z_DEFAULT_COMPRESSION, 9 = best if( ( status = deflateInit2( &strm, 1, Z_DEFLATED, windowBits, 8, Z_DEFAULT_STRATEGY ) ) == Z_OK ) { // run until the buffer is processed or there is an error while( ( status = deflate( &strm, Z_FINISH ) ) == Z_OK ); if( status == Z_STREAM_END ) compressed = true; } // double after = get_time(); // cerr << "gzip took " << after - before << " seconds. " << c.size() << " Bytes compressed to " << strm.total_out << " Bytes. Compression Ratio " << (double)strm.total_out / (double)c.size() * 100.0 << "%." << endl; if (compressed) { httpdAddHeader ( server, "Content-Encoding: gzip" ); httpdWrite( server, (char*) zBuffer, strm.total_out ); } else { // perror("compress"); // cerr << "Return code is " << status << endl; httpdWrite( server, (char*) c.begin(), c.size() ); } free(zBuffer); } else { httpdWrite( server, (char*) c.begin(), c.size() ); } } // httpd stuff void sendServerInfo( httpd *server, const Database& db ) { Version daapVersion( 3, 0 ); Version dmapVersion( 2, 0 ); double version = httpdRequestDaapVersion( server ); if( version == 1.0 ) { daapVersion.hi = 1; daapVersion.lo = 0; dmapVersion.hi = 1; dmapVersion.lo = 0; } else if( version == 2.0 ) { daapVersion.hi = 2; daapVersion.lo = 0; dmapVersion.hi = 1; dmapVersion.lo = 0; } TagOutput response; response << Tag( 'msrv' ) << Tag('mstt') << (u32) DAAP_OK << end << Tag('mpro') << dmapVersion << end << Tag('apro') << daapVersion << end << Tag('minm') << db.serverName << end << Tag('mslr') << (u8) 0 << end << Tag('msal') << (u8) 0 << end << Tag('mstm') << (u32) 1800 << end << Tag('msup') << (u8) 0 << end << Tag('msau') << (u32) 2 << end << // Disable some features for now /* Tag('mspi') << (u8) 0 << end << Tag('msex') << (u8) 0 << end << Tag('msbr') << (u8) 0 << end << Tag('msqy') << (u8) 0 << end << Tag('msix') << (u8) 0 << end << Tag('msrs') << (u8) 0 << end << */ Tag('msdc') << (u32) 1 << end << end; sendTag( server, response.data(), db.compression ); } void sendContentCodes( httpd *server ) { sendTag( server, TypeRegistry::getDictionary(), false ); } void sendLogin( httpd *server, u32 sessionId ) { TagOutput response; response << Tag( 'mlog' ) << Tag('mstt') << (u32) DAAP_OK << end << Tag('mlid') << (u32) sessionId << end << end; sendTag( server, response.data(), false ); } void sendUpdate( httpd *server, u32 revision ) { cerr << "sendUpdates" << endl; TagOutput response; response << Tag( 'mupd' ) << Tag('mstt') << (u32) DAAP_OK << end << Tag('musr') << revision << end << end; sendTag( server, response.data(), false ); } void distributeUpdates( httpd *server, u32 revision ) { cerr << "distributeUpdates" << endl; TagOutput response; response << Tag( 'mupd' ) << Tag('mstt') << (u32) DAAP_OK << end << Tag('musr') << revision << end << end; httpdSetContentType( server, "application/x-dmap-tagged" ); httpdWriteSubscribers( server, (char*) response.data().begin(), response.data().size() ); } void sendDatabaseList( httpd *server, const Database& db ) { TagOutput response; response << Tag( 'avdb' ) << Tag('mstt') << (u32) DAAP_OK << end << Tag('muty') << (u8) 0 << end << Tag('mtco') << (u32) 1 << end << Tag('mrco') << (u32) 1 << end << Tag('mlcl') << Tag('mlit') << Tag('miid') << (u32) 1 << end << // Tag('mper') << // (u64) 543675286654 << // end << Tag('minm') << db.dbName << end << Tag('mimc') << (u32) db.songs.size() << end << Tag('mctc') << (u32) db.containers.size() << end << end << end << end; sendTag( server, response.data(), db.compression ); } int sendDatabase( httpd *server, const Database& db, const u64& meta ) { TagOutput response; response << Tag( 'adbs' ) << Tag('mstt') << (u32) DAAP_OK << end << Tag('muty') << (u8) 0 << end << Tag('mtco') << (u32) db.songs.size() << end << Tag('mrco') << (u32) db.songs.size() << end << Tag('mlcl') << Filtered( db.songs, meta ) << end << end; sendTag( server, response.data(), db.compression ); return 0; } int sendDatabaseItem( httpd *server, const Song& song ) { httpdSendFile( server, (char *) song.path.c_str(), httpdRequestContentRange( server ) ); return 0; } void sendContainers( httpd *server, const Database& db, const u64& meta ) { Container *library = db.containers.findId(DAAP_LIBRARY_ID); std::vector containerlist; db.containers.collectPointers( containerlist ); TagOutput response; response << Tag( 'aply' ) << Tag('mstt') << (u32) DAAP_OK << end << Tag('muty') << (u8) 0 << end << Tag('mtco') << (u32) db.containers.size() << end << Tag('mrco') << (u32) db.containers.size() << end << Tag('mlcl') << // hack: send library first // because iTunes uses the first container as library // regardless of id *library; for( u32 i = 0; i < containerlist.size(); ++i ) { if( containerlist[i]->id != (u32) DAAP_LIBRARY_ID ) response << *(containerlist[i]); } response << end << end; cerr << db.containers.size() << " containers\n"; sendTag( server, response.data(), db.compression ); } int sendContainer( httpd *server, const Container& container, const u64& meta, const bool compressed ) { TagOutput response; response << Tag( 'apso' ) << Tag('mstt') << (u32) DAAP_OK << end << Tag('muty') << (u8) 0 << end << Tag('mtco') << (u32) container.size() << end << Tag('mrco') << (u32) container.size() << end << Tag('mlcl') << Filtered( container.items, meta) << end << end; sendTag( server, response.data(), compressed ); return 0; } void sendAdminRoot( httpd *server, const Database& db ) { std::string adminRoot = "daapdThis will turn into a full-blown admin interface someday. Hopefully."; // TODO: display lots of stuff. // * clients connected to the server // * current song delivered to the client // * last error in connection to a particular client // * general status of the server task (like scanning for new files etc.) httpdWrite( server, (char*) adminRoot.c_str(), adminRoot.size() ); } void sendRescanConfirmation( httpd *server ) { std::string confirmation = "daapd serverRescan finished."; httpdWrite( server, (char*) confirmation.c_str(), confirmation.size() ); } // parsing and decision tree int parseDatabases( httpd *server, Database& db, char *path, int delta, const u64& meta ) { // (just ignore delta for now) ComponentVect components; components = *tokenizeString( components, path, '/' ); assert( components.size() > 0 ); if ( components.size() == 1 ) { assert( components[0] == "databases" ); sendDatabaseList( server, db ); } else { u32 dbId = strtol( components[1].c_str(), NULL, 10 ); // just a single database for now if ( db.id != dbId || components.size() < 3) { httpdSend403( server ); return -1; } else { if( components[2] == "items" ) { if( components.size() == 3 ) { sendDatabase( server, db, meta ); } else if ( components.size() == 4 ) { u32 songId = strtol( components[3].c_str(), NULL, 10 ); Song *song; pthread_mutex_lock( &(db.dbLock) ); if( ( song = db.songs.findId( songId ) ) == NULL ) { httpdSend403( server ); return -1; } else { sendDatabaseItem( server, *song ); } pthread_mutex_unlock( &(db.dbLock) ); } else { httpdSend403( server ); return -1; } } else if( components[2] == "containers" ) { if( components.size() == 3 ) { sendContainers( server, db, meta ); } else if ( components.size() == 5 && components[4] == "items" ) { u32 contId = strtol( components[3].c_str(), NULL, 10); Container *container; if( ( container = db.containers.findId( contId ) ) == NULL ) { httpdSend403( server ); return -1; } else { sendContainer( server, *container, meta, db.compression ); } } else { httpdSend403( server ); return -1; } } else { httpdSend403( server ); return -1; } } } return 0; } int parseAdmin( httpd *server, void *db_, char *path ) { Database *db = (Database*) db_; ComponentVect components; components = *tokenizeString( components, path, '/' ); assert( components.size() > 0 ); assert( components[0] == "admin" ); if ( components.size() == 1 ) { sendAdminRoot( server, *db ); return 0; } else { if ( components.size() == 2) { /* if( components[1] == "rescan" ) { db->scan(); sendRescanConfirmation( server ); return 0; } else { */ httpdSend403( server ); return -1; // } } else { httpdSend403( server ); return -1; } } return 0; } int parsePath( httpd *server, Request &request ) { char *path = httpdRequestPath( server ); // first resolve the path to a request number if ( strncmp( path, "/server-info", sizeof("/server-info") - 1 ) == 0 ) { request.number = DAAP_SERVINFO; } else if ( strncmp( path, "/content-codes", sizeof("/content-codes") - 1 ) == 0 ) { request.number = DAAP_CONTCODES; } else if ( strncmp( path, "/login", sizeof("/login") - 1 ) == 0 ) { request.number = DAAP_LOGIN; } else if ( strncmp( path, "/logout", sizeof("/logout") - 1 ) == 0 ) { request.number = DAAP_LOGOUT; } else if ( strncmp( path, "/update", sizeof("/update") - 1 ) == 0 ) { request.number = DAAP_UPDATE; } else if ( strncmp( path, "/databases", sizeof("/databases") - 1 ) == 0 ) { request.number = DAAP_DATABASES; // no resolve or browse yet // } else if ( strncmp( path, "/resolve", sizeof("/resolve") - 1 ) == 0 ) { // request.number = DAAP_RESOLVE; // } else if ( strncmp( path, "/browse", sizeof("/browse") - 1 ) == 0 ) { // request.number = DAAP_BROWSE; // Administration: Not part of DAAP } else if ( strncmp( path, "/admin", sizeof("/admin") - 1 ) == 0 ) { request.number = DAAP_ADMIN; } else { httpdSend400( server ); return -1; } return 0; } u64 *parseMeta( u64& meta, const char *s ) { ComponentVect components; components = *tokenizeString( components, s, ',' ); for( u32 i = 0; i < components.size(); ++i ) { if( components[i] == "dmap.itemid" ) meta |= ITEMID; else if( components[i] == "dmap.itemname" ) meta |= ITEMNAME; else if( components[i] == "dmap.itemkind" ) meta |= ITEMKIND; else if( components[i] == "dmap.persistentid" ) meta |= PERSISTENTID; else if( components[i] == "daap.songalbum" ) meta |= SONGALBUM; else if( components[i] == "daap.songartist" ) meta |= SONGARTIST; else if( components[i] == "daap.songbitrate" ) meta |= SONGBITRATE; else if( components[i] == "daap.songbeatsperminute" ) meta |= SONGBEATSPERMINUTE; else if( components[i] == "daap.songcomment" ) meta |= SONGCOMMENT; else if( components[i] == "daap.songcompilation" ) meta |= SONGCOMPILATION; else if( components[i] == "daap.songcomposer" ) meta |= SONGCOMPOSER; else if( components[i] == "daap.songdateadded" ) meta |= SONGDATEADDED; else if( components[i] == "daap.songdatemodified" ) meta |= SONGDATEMODIFIED; else if( components[i] == "daap.songdisccount" ) meta |= SONGDISCCOUNT; else if( components[i] == "daap.songdiscnumber" ) meta |= SONGDISCNUMBER; else if( components[i] == "daap.songdisabled" ) meta |= SONGDISABLED; else if( components[i] == "daap.songeqpreset" ) meta |= SONGEQPRESET; else if( components[i] == "daap.songformat" ) meta |= SONGFORMAT; else if( components[i] == "daap.songgenre" ) meta |= SONGGENRE; else if( components[i] == "daap.songdescription" ) meta |= SONGDESCRIPTION; else if( components[i] == "daap.songrelativevolume" ) meta |= SONGRELATIVEVOLUME; else if( components[i] == "daap.songsamplerate" ) meta |= SONGSAMPLERATE; else if( components[i] == "daap.songsize" ) meta |= SONGSIZE; else if( components[i] == "daap.songstarttime" ) meta |= SONGSTARTTIME; else if( components[i] == "daap.songstoptime" ) meta |= SONGSTOPTIME; else if( components[i] == "daap.songtime" ) meta |= SONGTIME; else if( components[i] == "daap.songtrackcount" ) meta |= SONGTRACKCOUNT; else if( components[i] == "daap.songtracknumber" ) meta |= SONGTRACKNUMBER; else if( components[i] == "daap.songuserrating" ) meta |= SONGUSERRATING; else if( components[i] == "daap.songyear" ) meta |= SONGYEAR; else if( components[i] == "daap.songdatakind" ) meta |= SONGDATAKIND; else if( components[i] == "daap.songdataurl" ) meta |= SONGDATAURL; else if( components[i] == "com.apple.itunes.norm-volume" ) meta |= NORMVOLUME; else if( components[i] == "com.apple.itunes.smart-playlist" ) meta |= SMARTPLAYLIST; else if( components[i] == "dmap.containeritemid" ) meta |= CONTAINERITEMID; } return &meta; } void parseVariables( httpd *server, Request &request ) { httpVar *varPtr; if ( ( varPtr = httpdGetVariableByName ( server, "session-id" ) ) != NULL) request.session = strtol( varPtr->value, NULL, 10 ); if ( ( varPtr = httpdGetVariableByName ( server, "revision-number" ) ) != NULL) request.revision = strtol( varPtr->value, NULL, 10 ); if ( ( varPtr = httpdGetVariableByName ( server, "delta" ) ) != NULL) request.delta = strtol( varPtr->value, NULL, 10 ); if ( ( varPtr = httpdGetVariableByName ( server, "type" ) ) != NULL) request.type = varPtr->value; if ( ( varPtr = httpdGetVariableByName ( server, "meta" ) ) != NULL) request.meta = *parseMeta( request.meta, varPtr->value ); if ( ( varPtr = httpdGetVariableByName ( server, "filter" ) ) != NULL) request.filter = varPtr->value; if ( ( varPtr = httpdGetVariableByName ( server, "query" ) ) != NULL) request.query = varPtr->value; if ( ( varPtr = httpdGetVariableByName ( server, "index" ) ) != NULL) request.index = varPtr->value; } void parseRequest( httpd *server, void *db_ ) { /* // Works without authentication, ignores session, revision and delta server-info // Needs authentication, ignores session, revision and delta content-codes, login // Needs authentication, honors session, revision and delta update, databases // Needs authentication, honors session, ignores revision and delta logout */ Database *db = (Database*) db_; Request request; static Sessions sessions; if ( parsePath( server, request ) < 0 ) { return; } // check if we need authentication before proceeding if ( request.number == DAAP_SERVINFO ) { sendServerInfo( server, *db ); } else if ( request.number == DAAP_DATABASES ) { // give database information and files without authentication // because iTunes doesn't send it at this point (stupid, but true) parseVariables( server, request ); if( sessions.isValid( request.session ) ) { if ( request.revision != 0 && request.revision != db->revision ) { sendUpdate( server, db->revision ); } else { if ( request.delta != 0 && request.delta <= db->revision ) { parseDatabases( server, *db, httpdRequestPath( server ), request.delta, request.meta ); } else { parseDatabases( server, *db, httpdRequestPath( server ), 0, request.meta ); // everything } } } } else { // httpdAuthenticate just looks if there's an auth user/pwd, // sends a 401 if there is none and returns the auto length. if ( db->authPassword != "" ) if ( !httpdAuthenticate( server, "daap" ) || db->authPassword != server->request.authPassword ) return; // client authenticated; get variables parseVariables( server, request ); if ( request.number == DAAP_ADMIN ) { parseAdmin( server, db, httpdRequestPath( server ) ); } else if ( request.number == DAAP_CONTCODES ) { sendContentCodes( server ); } else if ( request.number == DAAP_LOGIN ) { // random sessions ids u32 sessionId = random(); sessions << sessionId; sendLogin( server, sessionId ); } else if ( request.number == DAAP_LOGOUT ) { if ( sessions.isValid( request.session ) ) { httpdSend204( server ); cerr << "session " << request.session << " logout\n"; sessions.erase( request.session ); } else { httpdSend403( server ); return; } } else if ( sessions.isValid( request.session ) ) { if ( request.revision != 0 && request.revision != db->revision ) { sendUpdate( server, db->revision ); } else { if ( request.number == DAAP_UPDATE ) httpdSubscribe( server, server->clientSock ); } } else { httpdSend403( server ); return; } } return; } void idleFunction( httpd *server, void *db_ ) { Database *db = (Database*) db_; if( db->revision > db->lastRevision ) { distributeUpdates( server, db->revision ); db->lastRevision = db->revision; } } //////////// // commandline and config file parsing inline void usage( const char *progname ) { cout << DAAPD_VERSION << endl; cout << "usage: " << progname << " [-dhqvz] [-c config-file] [-C cache-file] [-n name] [-p port] [-s interval] [-t vbr-limit] [file/directory]...\n"; } void help( const char *progname ) { usage(progname); cout << " -c config-file use config-file" << endl; cout << " -C cache-file save to and recover from" << endl; cout << " -d Daemon mode - implies quiet" << endl; cout << " -h give this help" << endl; cout << " -n name advertise name" << endl; cout << " -p port listen on port" << endl; cout << " -q quiet - no output" << endl; cout << " -s interval rescan filesystem every seconds" << endl; cout << " -t vbr-limit scan mp3s for length: -1 not at all, 0 entirely, n>0 first n frames" << endl; cout << " -v output verbose debugging info" << endl; cout << " -z disable gzip compression" << endl; } InitParams *parseConfig( FILE* f, InitParams& initParams ) { char key_[32]; char val[1024]; std::string key; while( fscanf( f, "%31s%*[ \t]%1023[^\n]", key_, val ) != EOF ) { key = key_; if( key == "Password" ) { if( val != NULL ) initParams.password = val; } else if( key == "ServerName" ) { if( val != NULL ) initParams.serverName = val; } else if( key == "DBName" ) { if( val != NULL ) initParams.dbName = val; } else if( key == "Port" ) { if( val != NULL ) initParams.port = strtol( val, NULL, 10 ); } else if( key == "Root" ) { if( val != NULL ) { std::string dir( val ); initParams.dirs->push_back( dir ); } } else if( key == "Cache" ) { if( val != NULL ) initParams.cacheFile = val; } else if( key == "Timescan" ) { if( val != NULL ) initParams.timeScan = strtol( val, NULL, 10 ); } else if( key == "RescanInterval" ) { if( val != NULL ) initParams.rescanInterval = strtol( val, NULL, 10 ); } else if( key == "Compression" ) { if( val != NULL ) { if ( strtol( val, NULL, 10 ) == 0 ) { initParams.compression = false; } } } bzero( val, 1024 ); } fclose( f ); return( &initParams ); } InitParams *readConfig( InitParams& initParams ) { FILE *conf; if( initParams.configFile != "" ) { conf = fopen( initParams.configFile.c_str(), "r" ); if( conf == NULL ) { cout << "Could not open config file " << initParams.configFile; cout << ". Trying default config file." << endl; } else { return( parseConfig( conf, initParams ) ); } } conf = fopen( "/etc/daapd.conf", "r" ); if( conf != NULL ) { return( parseConfig( conf, initParams ) ); } return( &initParams ); } InitParams *parseArgs( int argc, char *argv[], InitParams& initParams ) { int oc; int i; #ifdef __sgi__ getoptreset(); #elif __linux__ putenv( "POSIXLY_CORRECT=1" ); #elif __sun__ // do nothing #else optreset = 1; #endif optind = 1; #define OPTS "dhqvzc:C:n:p:s:t:" while ( ( oc = getopt( argc, argv, OPTS ) ) != -1 ) { switch(oc) { case 'c': break; case 'C': initParams.cacheFile = optarg; break; case 'd': initParams.quiet = true; initParams.daemon = true; break; case 'n': initParams.serverName = optarg; initParams.dbName = initParams.serverName; break; case 'p': if( ( i = strtol( optarg, NULL, 10 ) ) == 0 ) { cout << "invalid port. using the default " << initParams.port << " instead." << endl; } else { initParams.port = i; } break; case 'q': initParams.quiet = true; break; case 's': i = strtol( optarg, NULL, 10 ); if( errno != EINVAL ) initParams.rescanInterval = i; break; case 't': i = strtol( optarg, NULL, 10 ); if( errno != EINVAL ) initParams.timeScan = i; break; case 'v': initParams.verbose = true; printf("Verbose output requested.\n"); break; case 'z': initParams.compression = false; break; default: usage( argv[0] ); exit(1); } } while (optind < argc) { std::string dir( argv[optind] ); initParams.dirs->push_back( dir ); ++optind; } return &initParams; } InitParams *getConfigFile( int argc, char *argv[], InitParams& initParams ) { int oc; while ( ( oc = getopt( argc, argv, OPTS ) ) != -1 ) { switch(oc) { case 'h': help( argv[0] ); exit(0); case 'c': initParams.configFile = optarg; break; } } return &initParams; } void startAsDaemon( u8 verbose ) { if (verbose) cout << "Forking into daemon mode..." << endl; #if defined(__sgi__) _daemonize( 0, -1, -1, -1 ); /* chroot and close all files */ #elif defined(__BSD__) daemon( 0, 0 ); /* chroot and close stdin,out and err */ #elif defined(__linux__) daemon( 0, 0 ); /* chroot and close stdin,out and err */ #elif defined(__APPLE__) daemon( 0, 0 ); /* chroot and close stdin,out and err */ #else switch(fork()) { case 0: /* we are the child.. continue; */ break; case -1: /* bang ! */ perror( "Failed to fork off as a daemon" ); exit(1); break; default: exit(0); } setsid(); chdir("/"); #ifdef _PATH_DEVNULL { int fd = _open(_PATH_DEVNULL, O_RDWR, 0); if (fd != -1) { (void)dup2(fd, STDIN_FILENO); (void)dup2(fd, STDOUT_FILENO); (void)dup2(fd, STDERR_FILENO); if (fd > 2) (void)close(fd); } } #endif #endif } //////////// // start database scan thread // intended to be called by pthread_create void *scanThread( void *ptr ) { Database *db = (Database *)ptr; while( true ) { sleep( db->rescanInterval ); if( db->verbose ) printf( "rescanning...\n" ); db->scan(); } } void startScanThread ( Database *db, const bool verbose ) { pthread_t tid; /* the thread identifier */ pthread_attr_t attr; /* set of attributes for the thread */ /* get the default attributes */ pthread_attr_init( &attr ); #if defined( _POSIX_THREAD_PRIORITY_SCHEDULING) /* set the scheduling algorithm PROCESS or SYSTEM */ if (pthread_attr_setscope( &attr, PTHREAD_SCOPE_PROCESS ) != 0) if(verbose) printf( "unable to set to PTHREAD_SCOPE_PROCESS\n" ); /* set the scheduling policy - FIFO, RT, or OTHER */ if (pthread_attr_setschedpolicy( &attr, SCHED_OTHER ) != 0) if(verbose) printf( "unable to set scheduling policy to SCHED_OTHER\n" ); /* get some scheduling priority information */ struct sched_param thread_param; /* for scheduling */ if (pthread_attr_getschedparam( &attr, &thread_param ) != 0) { if(verbose) { printf( "unable to get scheduling info" ); } } else { int min_pri = sched_get_priority_min( SCHED_OTHER ); thread_param.sched_priority = min_pri; pthread_attr_setschedparam( &attr, &thread_param ); } #else if(verbose) printf( "_POSIX_THREAD_PRIORITY_SCHEDULING undefined\n" ); #endif /* create the thread */ pthread_create( &tid, &attr, &scanThread, (void *)db ); } //////////// // howl (rendezvous, zeroconf, service discovery, mDNS-SD) #ifdef HOWL_ENABLE static sw_result HOWL_API replyServiceCallback( sw_discovery discovery, sw_discovery_oid oid, sw_discovery_publish_status status, sw_opaque extra) { /* static sw_string status_text[] = { "Started", "Stopped", "Name Collision", "Invalid" }; fprintf(stderr, "publish reply: %s\n", status_text[status]); */ return SW_OKAY; } sw_discovery setupServiceDiscovery( const char *name, const int port, const bool quiet ) { sw_discovery session; sw_result result; sw_discovery_oid oid; sw_uint32 interface_index = 0; // SET THIS! if (sw_discovery_init(&session) != SW_OKAY) { if( !quiet ) fprintf(stderr, "howl: sw_discovery_init() failed\n"); return NULL; } if ((result = sw_discovery_publish( session, interface_index, name, "_daap._tcp", NULL, NULL, port, NULL, 0, replyServiceCallback, NULL, &oid)) != SW_OKAY) { if( !quiet ) fprintf(stderr, "howl: publish failed, result %d\n", result); return NULL; } return session; } // intended to be called by pthread_create void *startServiceDiscovery( void *ptr ) { sw_discovery *howlSessionPtr = (sw_discovery *)ptr; sw_discovery_run( *howlSessionPtr ); return NULL; } #endif //////////// // main int main ( int argc, char *argv[] ) { httpd *server; // initialization InitParams initParams; initParams = *getConfigFile( argc, argv, initParams ); initParams = *readConfig( initParams ); initParams = *parseArgs( argc, argv, initParams ); int buflen = 255; char buffer[buflen]; if( gethostname( buffer, buflen ) == 0 ) { if( initParams.serverName == std::string( "daapd server" ) ) initParams.serverName = (char *)buffer; if( initParams.dbName == std::string( "daapd music" ) ) initParams.dbName = (char *)buffer; } if( initParams.dirs->size() == 0 ) { std::string dir( "." ); initParams.dirs->push_back( dir ); } // scan for audio files if (!initParams.quiet) cout << "scanning " << initParams.dirs << " for audio files... " << flush; Database db( initParams ); if (!initParams.quiet) cout << "done. " << endl; // start http Server errno = 0; server = httpdCreate( NULL, initParams.port ); if ( server == NULL ) { perror ( "Couldn't create HTTP server" ); exit(1); } // unbuffer stdout if redirected (*BSD) if ( !isatty( fileno( stdout ) ) ) setvbuf( stdout, NULL, _IONBF, 0 ); httpdSetAccessLog( server, stdout ); httpdSetErrorLog( server, stderr ); // we do all URL parsing ourselves httpdAddCSiteContent( server, parseRequest, (void*)&db ); httpdSetContentType( server, "application/x-dmap-tagged" ); httpdSetIdle( server, idleFunction, (void*)&db ); // At this point we're fairly sure we're going to run just // just fine - so we exit cleanly if we are in daemon // mode. XXX - of course we really should switch any // error logging and what not to syslog at this point. if ( initParams.daemon ) startAsDaemon( initParams.verbose ); // start rescan thread startScanThread( &db, initParams.verbose ); #ifdef HOWL_ENABLE // start mDNS discovery (howl) sw_discovery howlSession = setupServiceDiscovery( initParams.serverName.c_str(), initParams.port, initParams.quiet ); pthread_t thread; pthread_attr_t threadAttribute; if( howlSession != NULL ) { pthread_attr_init( &threadAttribute ); pthread_create( &thread, &threadAttribute, &startServiceDiscovery, (void *) &howlSession); } #endif struct timeval timeout; timeout.tv_sec = 10; timeout.tv_usec = 0; // main event loop while( true ) { httpdSelectLoop( server, timeout ); } #ifdef HOWL_ENABLE if( howlSession != NULL ) { pthread_attr_destroy( &threadAttribute ); } #endif exit(0); }