diff --git a/src/Makefile.am b/src/Makefile.am index 8737505f..7cd940cb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -30,7 +30,7 @@ MUSEPACKSRC=scan-mpc.c endif if COND_SQLITE -SQLITEDB=dbs-sqlite.c dbs-sqlite.h +SQLITEDB=db-sql.c db-sql.h db-sql-sqlite2.c db-sql-sqlite2.h endif wavstreamer_SOURCES = wavstreamer.c diff --git a/src/configfile.c b/src/configfile.c index 2a57e8c2..080a5e83 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -22,7 +22,7 @@ /** * \file configfile.c - * + * * Configfile and web interface handling. */ @@ -178,7 +178,7 @@ void config_content_type(WS_CONNINFO *pwsc, char *path) { /** * Try and create a directory, including parents. - * + * * \param path path to make * \returns 0 on success, -1 otherwise, with errno set */ @@ -208,11 +208,11 @@ int config_makedir(char *path) { err=errno; free(pathdup); errno=err; - return -1; + return -1; } } else { errno=ENAMETOOLONG; - return -1; + return -1; } } @@ -281,7 +281,7 @@ int config_read(char *file) { return -1; } -#ifdef NSLU2 +#ifdef NSLU2 config.always_scan=0; #else config.always_scan=1; @@ -334,7 +334,7 @@ int config_read(char *file) { if((value) && (term) && (strlen(term))) { while(strlen(value) && (strchr("\t ",*value))) value++; - + pce=config_elements; handled=0; while((!handled) && (pce->config_element != -1)) { @@ -342,9 +342,9 @@ int config_read(char *file) { /* valid config directive */ handled=1; pce->changed=1; - + DPRINTF(E_DBG,L_CONF,"Read %s: %s\n",pce->name,value); - + switch(pce->type) { case CONFIG_TYPE_STRING: /* DWB: free space to prevent small leak */ @@ -359,7 +359,7 @@ int config_read(char *file) { } pce++; } - + if(!handled) { fprintf(stderr,"Invalid config directive: %s\n",buffer); fclose(fin); @@ -396,7 +396,7 @@ int config_read(char *file) { pce->changed=0; pce++; } - + /* Set the directory components to realpaths */ realpath(config.web_root,path_buffer); free(config.web_root); @@ -471,7 +471,7 @@ int config_read(char *file) { DPRINTF(E_FATAL,L_MISC,"Alloc error.\n"); currentterm=0; - + term_begin=config.compdirs; while(*term_begin && *term_begin ==' ') term_begin++; @@ -521,8 +521,8 @@ void config_close(void) { pce=config_elements; err=0; while((pce->config_element != -1)) { - if((pce->config_element) && - (pce->type == CONFIG_TYPE_STRING) && + if((pce->config_element) && + (pce->type == CONFIG_TYPE_STRING) && (*((char**)pce->var))) { DPRINTF(E_DBG,L_CONF,"Freeing %s\n",pce->name); free(*((char**)pce->var)); @@ -575,9 +575,9 @@ int config_write(WS_CONNINFO *pwsc) { fprintf(configfile,"art_filename\t%s\n",ws_getvar(pwsc,"art_filename")); if(ws_getvar(pwsc,"logfile") && strlen(ws_getvar(pwsc,"logfile"))) fprintf(configfile,"logfile\t\t%s\n",ws_getvar(pwsc,"logfile")); - fprintf(configfile,"process_m3u\t%s\n",ws_getvar(pwsc,"process_m3u")); + fprintf(configfile,"process_m3u\t%s\n",ws_getvar(pwsc,"process_m3u")); fprintf(configfile,"compress\t%s\n",ws_getvar(pwsc,"compress")); - + fprintf(configfile,"debuglevel\t%s\n",ws_getvar(pwsc,"debuglevel")); fprintf(configfile,"compdirs\t%s\n",ws_getvar(pwsc,"compdirs")); @@ -668,12 +668,12 @@ void config_handler(WS_CONNINFO *pwsc) { DPRINTF(E_DBG,L_CONF|L_WS,"Entering config_handler\n"); config_set_status(pwsc,0,"Serving admin pages"); - + pwsc->close=1; ws_addresponseheader(pwsc,"Connection","close"); if(strcasecmp(pwsc->uri,"/xml-rpc")==0) { - // perhaps this should get a separate handler + // perhaps this should get a separate handler config_set_status(pwsc,0,"Serving xml-rpc method"); xml_handle(pwsc); DPRINTF(E_DBG,L_CONF|L_XML,"Thread %d: xml-rpc served\n",pwsc->threadno); @@ -717,7 +717,7 @@ void config_handler(WS_CONNINFO *pwsc) { config_set_status(pwsc,0,NULL); return; } - + if(strcasecmp(pwsc->uri,"/config-update.html")==0) { /* don't update (and turn everything to (null)) the configuration file if what the user's really trying to do is @@ -763,10 +763,10 @@ void config_handler(WS_CONNINFO *pwsc) { ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); ws_emitheaders(pwsc); - + if(strcasecmp(&resolved_path[strlen(resolved_path) - 5],".html") == 0) { config_subst_stream(pwsc, file_fd); - } else { + } else { copyfile(file_fd,pwsc->fd); } @@ -800,7 +800,7 @@ int config_auth(char *user, char *password) { void config_emit_host(WS_CONNINFO *pwsc, void *value, char *arg) { char *host; char *port; - + if(ws_getrequestheader(pwsc,"host")) { host = strdup(ws_getrequestheader(pwsc,"host")); if((port = strrchr(host,':'))) { @@ -868,6 +868,7 @@ void config_emit_service_status(WS_CONNINFO *pwsc, void *value, char *arg) { char buf[256]; int r_days, r_hours, r_mins, r_secs; int scanning; + int song_count; ws_writefd(pwsc,""); ws_writefd(pwsc,"\n"); @@ -925,11 +926,11 @@ void config_emit_service_status(WS_CONNINFO *pwsc, void *value, char *arg) { r_secs -= 60 * r_mins; memset(buf,0x0,sizeof(buf)); - if(r_days) + if(r_days) sprintf((char*)&buf[strlen(buf)],"%d day%s, ", r_days, r_days == 1 ? "" : "s"); - if(r_days || r_hours) + if(r_days || r_hours) sprintf((char*)&buf[strlen(buf)],"%d hour%s, ", r_hours, r_hours == 1 ? "" : "s"); @@ -939,13 +940,14 @@ void config_emit_service_status(WS_CONNINFO *pwsc, void *value, char *arg) { sprintf((char*)&buf[strlen(buf)],"%d second%s ", r_secs, r_secs == 1 ? "" : "s"); - + ws_writefd(pwsc," \n",buf); ws_writefd(pwsc,"\n"); - + ws_writefd(pwsc,"\n"); ws_writefd(pwsc," \n"); - ws_writefd(pwsc," \n",db_get_song_count()); + db_get_song_count(NULL,&song_count); + ws_writefd(pwsc," \n",song_count); ws_writefd(pwsc,"\n"); ws_writefd(pwsc,"\n"); @@ -975,7 +977,7 @@ void config_emit_service_status(WS_CONNINFO *pwsc, void *value, char *arg) { * Get the count of connected users. This is actually not totally accurate, * as there may be a "connected" host that is in between requesting songs. * It's marginally close though. It is really the number of connections - * with non-zero session numbers. + * with non-zero session numbers. * * \returns connected user count */ @@ -1018,7 +1020,7 @@ void config_emit_threadstatus(WS_CONNINFO *pwsc, void *value, char *arg) { WS_CONNINFO *pci; SCAN_STATUS *pss; WSTHREADENUM wste; - + ws_writefd(pwsc,"
ServiceStatusControl
%s
Songs%d%d
"); ws_writefd(pwsc,""); ws_writefd(pwsc,"\n"); @@ -1058,7 +1060,7 @@ void config_emit_ispage(WS_CONNINFO *pwsc, void *value, char *arg) { first=last=arg; strsep(&last,":"); - + if(last) { page=strdup(first); if(!page) @@ -1152,7 +1154,7 @@ void config_emit_readonly(WS_CONNINFO *pwsc, void *value, char *arg) { } /** - * implement the INCLUDE command. This is essentially a server + * implement the INCLUDE command. This is essentially a server * side include. * * \param pwsc web connection @@ -1166,7 +1168,7 @@ void config_emit_include(WS_CONNINFO *pwsc, void *value, char *arg) { struct stat sb; DPRINTF(E_DBG,L_CONF|L_WS,"Preparing to include %s\n",arg); - + snprintf(path,PATH_MAX,"%s/%s",config.web_root,arg); if(!realpath(path,resolved_path)) { pwsc->error=errno; @@ -1203,7 +1205,7 @@ void config_emit_include(WS_CONNINFO *pwsc, void *value, char *arg) { ws_writefd(pwsc,"
error: cannot open %s: %s
",arg,strerror(errno)); return; } - + config_subst_stream(pwsc, file_fd); r_close(file_fd); @@ -1212,13 +1214,13 @@ void config_emit_include(WS_CONNINFO *pwsc, void *value, char *arg) { } /** - * free a SCAN_STATUS block + * free a SCAN_STATUS block * * @param vp pointer to SCAN_STATUS block */ void config_freescan(void *vp) { SCAN_STATUS *pss = (SCAN_STATUS*)vp; - + if(pss) { if(pss->what) free(pss->what); @@ -1243,7 +1245,7 @@ void config_set_status(WS_CONNINFO *pwsc, int session, char *fmt, ...) { va_list ap; SCAN_STATUS *pfirst; char *newmsg = NULL; - + DPRINTF(E_DBG,L_CONF,"Entering config_set_status\n"); if(fmt) { @@ -1262,24 +1264,24 @@ void config_set_status(WS_CONNINFO *pwsc, int session, char *fmt, ...) { pfirst->what = NULL; pfirst->thread = pwsc->threadno; pfirst->host = strdup(pwsc->hostname); - ws_set_local_storage(pwsc,pfirst,config_freescan); + ws_set_local_storage(pwsc,pfirst,config_freescan); } else { DPRINTF(E_FATAL,L_CONF,"Malloc Error\n"); } } - + if(pfirst) { - /* just update */ - if(pfirst->what) { - free(pfirst->what); - } - pfirst->what=newmsg; - pfirst->session=session; + /* just update */ + if(pfirst->what) { + free(pfirst->what); + } + pfirst->what=newmsg; + pfirst->session=session; } else { - if(newmsg) - free(newmsg); + if(newmsg) + free(newmsg); } - + ws_unlock_local_storage(pwsc); DPRINTF(E_DBG,L_CONF,"Exiting config_set_status\n"); } @@ -1288,7 +1290,7 @@ void config_set_status(WS_CONNINFO *pwsc, int session, char *fmt, ...) { * Get the next available session id. * This is vulnerable to races, but we don't track sessions, * so there really isn't a point anyway. - * + * * @returns duh... the next available session id */ int config_get_next_session(void) { diff --git a/src/db-generic.c b/src/db-generic.c index 767e9379..f2438705 100644 --- a/src/db-generic.c +++ b/src/db-generic.c @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -34,7 +35,7 @@ #include "err.h" #include "mp3-scanner.h" -#include "dbs-sqlite.h" +#include "db-sql.h" #define DB_VERSION 1 #define MAYBEFREE(a) { if((a)) free((a)); }; @@ -42,61 +43,59 @@ /** pointers to database-specific functions */ typedef struct tag_db_functions { char *name; - int(*dbs_open)(char *); + int(*dbs_open)(char **, char *); int(*dbs_init)(int); int(*dbs_deinit)(void); - int(*dbs_add)(MP3FILE*); - int(*dbs_add_playlist)(char *, int, char *,char *, int, int *); - int(*dbs_add_playlist_item)(int, int); - int(*dbs_delete_playlist)(int); - int(*dbs_delete_playlist_item)(int, int); - int(*dbs_edit_playlist)(int, char*, char*); - int(*dbs_enum_start)(DBQUERYINFO *); - int(*dbs_enum_size)(DBQUERYINFO *, int *); - int(*dbs_enum_fetch)(DBQUERYINFO *, unsigned char **); - int(*dbs_enum_reset)(DBQUERYINFO *); - int(*dbs_enum_end)(void); + int(*dbs_add)(char **, MP3FILE*); + int(*dbs_add_playlist)(char **, char *, int, char *,char *, int, int *); + int(*dbs_add_playlist_item)(char **, int, int); + int(*dbs_delete_playlist)(char **, int); + int(*dbs_delete_playlist_item)(char **, int, int); + int(*dbs_edit_playlist)(char **, int, char*, char*); + int(*dbs_enum_start)(char **, DBQUERYINFO *); + int(*dbs_enum_size)(char **, DBQUERYINFO *, int *, int *); + int(*dbs_enum_fetch)(char **, DBQUERYINFO *, int *, unsigned char **); + int(*dbs_enum_reset)(char **, DBQUERYINFO *); + int(*dbs_enum_end)(char **); int(*dbs_start_scan)(void); int(*dbs_end_song_scan)(void); int(*dbs_end_scan)(void); - int(*dbs_get_count)(CountType_t); - MP3FILE*(*dbs_fetch_item)(int); - MP3FILE*(*dbs_fetch_path)(char *,int); - M3UFILE*(*dbs_fetch_playlist)(char *, int); + int(*dbs_get_count)(char **, int *, CountType_t); + MP3FILE*(*dbs_fetch_item)(char **, int); + MP3FILE*(*dbs_fetch_path)(char **, char *,int); + M3UFILE*(*dbs_fetch_playlist)(char **, char *, int); void(*dbs_dispose_item)(MP3FILE*); void(*dbs_dispose_playlist)(M3UFILE*); }DB_FUNCTIONS; /** All supported backend databases, and pointers to the db specific implementations */ DB_FUNCTIONS db_functions[] = { -#ifdef HAVE_LIBSQLITE { - "sqlite", - db_sqlite_open, - db_sqlite_init, - db_sqlite_deinit, - db_sqlite_add, - db_sqlite_add_playlist, - db_sqlite_add_playlist_item, - db_sqlite_delete_playlist, - db_sqlite_delete_playlist_item, - db_sqlite_edit_playlist, - db_sqlite_enum_start, - db_sqlite_enum_size, - db_sqlite_enum_fetch, - db_sqlite_enum_reset, - db_sqlite_enum_end, - db_sqlite_start_scan, - db_sqlite_end_song_scan, - db_sqlite_end_scan, - db_sqlite_get_count, - db_sqlite_fetch_item, - db_sqlite_fetch_path, - db_sqlite_fetch_playlist, - db_sqlite_dispose_item, - db_sqlite_dispose_playlist + "sql", + db_sql_open, + db_sql_init, + db_sql_deinit, + db_sql_add, + db_sql_add_playlist, + db_sql_add_playlist_item, + db_sql_delete_playlist, + db_sql_delete_playlist_item, + db_sql_edit_playlist, + db_sql_enum_start, + db_sql_enum_size, + db_sql_enum_fetch, + db_sql_enum_reset, + db_sql_enum_end, + db_sql_start_scan, + db_sql_end_song_scan, + db_sql_end_scan, + db_sql_get_count, + db_sql_fetch_item, + db_sql_fetch_path, + db_sql_fetch_playlist, + db_sql_dispose_item, + db_sql_dispose_playlist }, -#endif { NULL,NULL } }; @@ -152,11 +151,11 @@ DAAP_ITEMS taglist[] = { { 0x0C, "adbs", "daap.databasesongs" }, { 0x09, "asal", "daap.songalbum" }, { 0x09, "asar", "daap.songartist" }, - { 0x03, "asbt", "daap.songbeatsperminute" }, + { 0x03, "asbt", "daap.songbeatsperminute" }, { 0x03, "asbr", "daap.songbitrate" }, - { 0x09, "ascm", "daap.songcomment" }, - { 0x01, "asco", "daap.songcompilation" }, - { 0x09, "ascp", "daap.songcomposer" }, + { 0x09, "ascm", "daap.songcomment" }, + { 0x01, "asco", "daap.songcompilation" }, + { 0x09, "ascp", "daap.songcomposer" }, { 0x0A, "asda", "daap.songdateadded" }, { 0x0A, "asdm", "daap.songdatemodified" }, { 0x03, "asdc", "daap.songdisccount" }, @@ -210,43 +209,43 @@ DAAP_ITEMS taglist[] = { }; /** map the string names specified in the meta= tag to bit numbers */ -static METAMAP db_metamap[] = { - { "dmap.itemid", metaItemId }, - { "dmap.itemname", metaItemName }, - { "dmap.itemkind", metaItemKind }, - { "dmap.persistentid", metaPersistentId }, +static METAMAP db_metamap[] = { + { "dmap.itemid", metaItemId }, + { "dmap.itemname", metaItemName }, + { "dmap.itemkind", metaItemKind }, + { "dmap.persistentid", metaPersistentId }, { "dmap.containeritemid", metaContainerItemId }, - { "dmap.parentcontainerid", metaParentContainerId }, + { "dmap.parentcontainerid", metaParentContainerId }, /* end generics */ - { "daap.songalbum", metaSongAlbum }, - { "daap.songartist", metaSongArtist }, - { "daap.songbitrate", metaSongBitRate }, + { "daap.songalbum", metaSongAlbum }, + { "daap.songartist", metaSongArtist }, + { "daap.songbitrate", metaSongBitRate }, { "daap.songbeatsperminute", metaSongBPM }, - { "daap.songcomment", metaSongComment }, - { "daap.songcompilation", metaSongCompilation }, - { "daap.songcomposer", metaSongComposer }, - { "daap.songdatakind", metaSongDataKind }, + { "daap.songcomment", metaSongComment }, + { "daap.songcompilation", metaSongCompilation }, + { "daap.songcomposer", metaSongComposer }, + { "daap.songdatakind", metaSongDataKind }, { "daap.songdataurl", metaSongDataURL }, - { "daap.songdateadded", metaSongDateAdded }, - { "daap.songdatemodified", metaSongDateModified }, - { "daap.songdescription", metaSongDescription }, - { "daap.songdisabled", metaSongDisabled }, - { "daap.songdisccount", metaSongDiscCount }, - { "daap.songdiscnumber", metaSongDiscNumber }, - { "daap.songeqpreset", metaSongEqPreset }, - { "daap.songformat", metaSongFormat }, - { "daap.songgenre", metaSongGenre }, - { "daap.songgrouping", metaSongGrouping }, + { "daap.songdateadded", metaSongDateAdded }, + { "daap.songdatemodified", metaSongDateModified }, + { "daap.songdescription", metaSongDescription }, + { "daap.songdisabled", metaSongDisabled }, + { "daap.songdisccount", metaSongDiscCount }, + { "daap.songdiscnumber", metaSongDiscNumber }, + { "daap.songeqpreset", metaSongEqPreset }, + { "daap.songformat", metaSongFormat }, + { "daap.songgenre", metaSongGenre }, + { "daap.songgrouping", metaSongGrouping }, { "daap.songrelativevolume", metaSongRelativeVolume }, - { "daap.songsamplerate", metaSongSampleRate }, - { "daap.songsize", metaSongSize }, - { "daap.songstarttime", metaSongStartTime }, - { "daap.songstoptime", metaSongStopTime }, - { "daap.songtime", metaSongTime }, - { "daap.songtrackcount", metaSongTrackCount }, - { "daap.songtracknumber", metaSongTrackNumber }, - { "daap.songuserrating", metaSongUserRating }, - { "daap.songyear", metaSongYear }, + { "daap.songsamplerate", metaSongSampleRate }, + { "daap.songsize", metaSongSize }, + { "daap.songstarttime", metaSongStartTime }, + { "daap.songstoptime", metaSongStopTime }, + { "daap.songtime", metaSongTime }, + { "daap.songtrackcount", metaSongTrackCount }, + { "daap.songtracknumber", metaSongTrackNumber }, + { "daap.songuserrating", metaSongUserRating }, + { "daap.songyear", metaSongYear }, /* iTunes 4.5+ (forgot exactly when) */ { "daap.songcodectype", metaSongCodecType }, { "daap.songcodecsubtype", metaSongCodecSubType }, @@ -261,9 +260,20 @@ static METAMAP db_metamap[] = { /* mt-daapd specific */ { "org.mt-daapd.smart-playlist-spec", metaMPlaylistSpec }, { "org.mt-daapd.playlist-type", metaMPlaylistType }, - { 0, 0 } + { 0, 0 } }; +char *db_error_list[] = { + "Success", + "Misc SQL Error: %s", + "Duplicate Playlist: %s", + "Missing playlist spec", + "Cannot add playlist items to a playlist of that type", + "No rows returned", + "Invalid playlist id: %d", + "Invalid song id: %d", + "Parse error" +}; /* Globals */ static DB_FUNCTIONS *db_current=&db_functions[0]; /**< current database backend */ @@ -287,30 +297,30 @@ static void db_trim_string(char *string); * \param meta meta string variable from GET request */ MetaField_t db_encode_meta(char *meta) { - MetaField_t bits = 0; + MetaField_t bits = 0; char *start; char *end; METAMAP *m; for(start = meta ; *start ; start = end) { - int len; + int len; - if(0 == (end = strchr(start, ','))) - end = start + strlen(start); + if(0 == (end = strchr(start, ','))) + end = start + strlen(start); - len = end - start; + len = end - start; - if(*end != 0) - end++; + if(*end != 0) + end++; - for(m = db_metamap ; m->tag ; ++m) - if(!strncmp(m->tag, start, len)) - break; + for(m = db_metamap ; m->tag ; ++m) + if(!strncmp(m->tag, start, len)) + break; - if(m->tag) - bits |= (((MetaField_t) 1) << m->bit); - else - DPRINTF(E_WARN,L_DAAP,"Unknown meta code: %.*s\n", len, start); + if(m->tag) + bits |= (((MetaField_t) 1) << m->bit); + else + DPRINTF(E_WARN,L_DAAP,"Unknown meta code: %.*s\n", len, start); } DPRINTF(E_DBG, L_DAAP, "meta codes: %llu\n", bits); @@ -329,42 +339,58 @@ int db_wantsmeta(MetaField_t meta, MetaFieldName_t fieldNo) { } -/* +/* * db_readlock * - * If this fails, something is so amazingly hosed, we might just as well - * terminate. + * If this fails, something is so amazingly hosed, we might just as well + * terminate. */ void db_readlock(void) { int err; if((err=pthread_rwlock_rdlock(&db_rwlock))) { - DPRINTF(E_FATAL,L_DB,"cannot lock rdlock: %s\n",strerror(err)); + DPRINTF(E_FATAL,L_DB,"cannot lock rdlock: %s\n",strerror(err)); } } -/* +/* * db_writelock - * + * * same as above */ void db_writelock(void) { int err; if((err=pthread_rwlock_wrlock(&db_rwlock))) { - DPRINTF(E_FATAL,L_DB,"cannot lock rwlock: %s\n",strerror(err)); + DPRINTF(E_FATAL,L_DB,"cannot lock rwlock: %s\n",strerror(err)); } } /* * db_unlock - * - * useless, but symmetrical + * + * useless, but symmetrical */ int db_unlock(void) { return pthread_rwlock_unlock(&db_rwlock); } +/** + * Build an error string + */ +void db_get_error(char **pe, int error, ...) { + va_list ap; + char errbuf[1024]; + + if(!pe) + return; + + va_start(ap, error); + vsnprintf(errbuf, sizeof(errbuf), db_error_list[error], ap); + va_end(ap); + + *pe = strdup(errbuf); +} /** * Must dynamically initialize the rwlock, as Mac OSX 10.3 (at least) @@ -385,17 +411,17 @@ extern int db_set_backend(char *type) { DPRINTF(E_DBG,L_DB,"Setting backend database to %s\n",type); if(!db_functions[0].name) { - DPRINTF(E_FATAL,L_DB,"No database backends are available. Install sqlite!\n"); + DPRINTF(E_FATAL,L_DB,"No database backends are available. Install sqlite!\n"); } - + db_current=&db_functions[0]; while((db_current->name) && (strcasecmp(db_current->name,type))) { - db_current++; + db_current++; } if(!db_current->name) { - DPRINTF(E_WARN,L_DB,"Could not find db backend %s. Aborting.\n",type); - return -1; + DPRINTF(E_WARN,L_DB,"Could not find db backend %s. Aborting.\n",type); + return -1; } DPRINTF(E_DBG,L_DB,"Backend database set\n"); @@ -409,15 +435,15 @@ extern int db_set_backend(char *type) { * * \param parameters This is backend-specific (mysql, sqlite, etc) */ -int db_open(char *parameters) { +int db_open(char **pe, char *parameters) { int result; DPRINTF(E_DBG,L_DB,"Opening database\n"); if(pthread_once(&db_initlock,db_init_once)) - return -1; + return -1; - result=db_current->dbs_open(parameters); + result=db_current->dbs_open(pe, parameters); DPRINTF(E_DBG,L_DB,"Results: %d\n",result); return result; @@ -463,13 +489,13 @@ int db_scanning(void) { /** * add (or update) a file */ -int db_add(MP3FILE *pmp3) { +int db_add(char **pe, MP3FILE *pmp3) { int retval; db_writelock(); db_utf8_validate(pmp3); db_trim_strings(pmp3); - retval=db_current->dbs_add(pmp3); + retval=db_current->dbs_add(pe,pmp3); db_revision_no++; db_unlock(); @@ -483,15 +509,15 @@ int db_add(MP3FILE *pmp3) { * \param type type of playlist to add: 0 - static, 1 - smart, 2 - m3u * \param clause where clause (if type 1) * \param playlistid returns the id of the playlist created - * \returns 0 on success, error code otherwise + * \returns 0 on success, error code otherwise */ -int db_add_playlist(char *name, int type, char *clause, char *path, int index, int *playlistid) { +int db_add_playlist(char **pe, char *name, int type, char *clause, char *path, int index, int *playlistid) { int retval; db_writelock(); - retval=db_current->dbs_add_playlist(name,type,clause,path,index,playlistid); + retval=db_current->dbs_add_playlist(pe,name,type,clause,path,index,playlistid); if(retval == DB_E_SUCCESS) - db_revision_no++; + db_revision_no++; db_unlock(); return retval; @@ -504,13 +530,13 @@ int db_add_playlist(char *name, int type, char *clause, char *path, int index, i * \param songid song to add to playlist * \returns 0 on success, DB_E_ code otherwise */ -int db_add_playlist_item(int playlistid, int songid) { +int db_add_playlist_item(char **pe, int playlistid, int songid) { int retval; db_writelock(); - retval=db_current->dbs_add_playlist_item(playlistid,songid); + retval=db_current->dbs_add_playlist_item(pe,playlistid,songid); if(retval == DB_E_SUCCESS) - db_revision_no++; + db_revision_no++; db_unlock(); return retval; @@ -520,15 +546,15 @@ int db_add_playlist_item(int playlistid, int songid) { * delete a playlist * * \param playlistid id of the playlist to delete - * \returns 0 on success, error code otherwise + * \returns 0 on success, error code otherwise */ -int db_delete_playlist(int playlistid) { +int db_delete_playlist(char **pe, int playlistid) { int retval; db_writelock(); - retval=db_current->dbs_delete_playlist(playlistid); + retval=db_current->dbs_delete_playlist(pe,playlistid); if(retval == DB_E_SUCCESS) - db_revision_no++; + db_revision_no++; db_unlock(); return retval; @@ -539,15 +565,15 @@ int db_delete_playlist(int playlistid) { * * \param playlistid id of the playlist to delete * \param songid id of the song to delete - * \returns 0 on success, error code otherwise + * \returns 0 on success, error code otherwise */ -int db_delete_playlist_item(int playlistid, int songid) { +int db_delete_playlist_item(char **pe, int playlistid, int songid) { int retval; db_writelock(); - retval=db_current->dbs_delete_playlist_item(playlistid,songid); + retval=db_current->dbs_delete_playlist_item(pe,playlistid,songid); if(retval == DB_E_SUCCESS) - db_revision_no++; + db_revision_no++; db_unlock(); return retval; @@ -560,12 +586,12 @@ int db_delete_playlist_item(int playlistid, int songid) { * @param name new name of playlist * @param clause new where clause */ -int db_edit_playlist(int id, char *name, char *clause) { +int db_edit_playlist(char **pe, int id, char *name, char *clause) { int retval; - + db_writelock(); - - retval = db_current->dbs_edit_playlist(id, name, clause); + + retval = db_current->dbs_edit_playlist(pe, id, name, clause); db_unlock(); return retval; } @@ -577,15 +603,15 @@ int db_edit_playlist(int id, char *name, char *clause) { * \param pinfo pointer to DBQUERYINFO struction * \returns 0 on success, -1 on failure */ -int db_enum_start(DBQUERYINFO *pinfo) { +int db_enum_start(char **pe, DBQUERYINFO *pinfo) { int retval; db_writelock(); - retval=db_current->dbs_enum_start(pinfo); + retval=db_current->dbs_enum_start(pe, pinfo); if(retval) { - db_unlock(); - return retval; + db_unlock(); + return retval; } return 0; @@ -596,8 +622,8 @@ int db_enum_start(DBQUERYINFO *pinfo) { * db__enum_reset, so it should be positioned at the head * of the list of returned items. */ -int db_enum_size(DBQUERYINFO *pinfo, int *count) { - return db_current->dbs_enum_size(pinfo,count); +int db_enum_size(char **pe, DBQUERYINFO *pinfo, int *size, int *count) { + return db_current->dbs_enum_size(pe,pinfo,size,count); } @@ -607,27 +633,29 @@ int db_enum_size(DBQUERYINFO *pinfo, int *count) { * the dmap item. * * \param plen length of the dmap item returned - * \returns dmap item + * \returns dmap item */ -int db_enum_fetch(DBQUERYINFO *pinfo, unsigned char **pdmap) { - return db_current->dbs_enum_fetch(pinfo,pdmap); +int db_enum_fetch(char **pe, DBQUERYINFO *pinfo, int *size, + unsigned char **pdmap) +{ + return db_current->dbs_enum_fetch(pe,pinfo,size,pdmap); } /** * reset the enum, without coming out the the db_writelock */ -int db_enum_reset(DBQUERYINFO *pinfo) { - return db_current->dbs_enum_reset(pinfo); +int db_enum_reset(char **pe, DBQUERYINFO *pinfo) { + return db_current->dbs_enum_reset(pe,pinfo); } /** * finish the enumeration */ -int db_enum_end(void) { +int db_enum_end(char **pe) { int retval; - retval=db_current->dbs_enum_end(); + retval=db_current->dbs_enum_end(pe); db_unlock(); return retval; } @@ -638,32 +666,32 @@ int db_enum_end(void) { * mostly only by the web interface, and when streaming a song * * \param id id of the item to get details for - */ -MP3FILE *db_fetch_item(int id) { + */ +MP3FILE *db_fetch_item(char **pe, int id) { MP3FILE *retval; - + db_readlock(); - retval=db_current->dbs_fetch_item(id); + retval=db_current->dbs_fetch_item(pe, id); db_unlock(); return retval; } -MP3FILE *db_fetch_path(char *path,int index) { +MP3FILE *db_fetch_path(char **pe, char *path,int index) { MP3FILE *retval; - + db_readlock(); - retval=db_current->dbs_fetch_path(path, index); + retval=db_current->dbs_fetch_path(pe,path, index); db_unlock(); return retval; } -M3UFILE *db_fetch_playlist(char *path, int index) { +M3UFILE *db_fetch_playlist(char **pe, char *path, int index) { M3UFILE *retval; db_readlock(); - retval=db_current->dbs_fetch_playlist(path,index); + retval=db_current->dbs_fetch_playlist(pe,path,index); db_unlock(); return retval; @@ -676,7 +704,7 @@ int db_start_scan(void) { retval=db_current->dbs_start_scan(); db_is_scanning=1; db_unlock(); - + return retval; } @@ -686,7 +714,7 @@ int db_end_song_scan(void) { db_writelock(); retval=db_current->dbs_end_song_scan(); db_unlock(); - + return retval; } @@ -697,10 +725,10 @@ int db_end_scan(void) { retval=db_current->dbs_end_scan(); db_is_scanning=0; db_unlock(); - + return retval; } - + void db_dispose_item(MP3FILE *pmp3) { return db_current->dbs_dispose_item(pmp3); } @@ -709,26 +737,26 @@ void db_dispose_playlist(M3UFILE *pm3u) { return db_current->dbs_dispose_playlist(pm3u); } -int db_get_count(CountType_t type) { - int retval; +int db_get_count(char **pe, int *count, CountType_t type) { + int retval; db_readlock(); - retval=db_current->dbs_get_count(type); + retval=db_current->dbs_get_count(pe,count,type); db_unlock(); return retval; } - -/* + +/* * FIXME: clearly a stub */ -int db_get_song_count() { - return db_get_count(countSongs); +int db_get_song_count(char **pe, int *count) { + return db_get_count(pe, count, countSongs); } -int db_get_playlist_count() { - return db_get_count(countPlaylists); +int db_get_playlist_count(char **pe, int *count) { + return db_get_count(pe, count, countPlaylists); } @@ -799,7 +827,7 @@ int db_dmap_add_int(unsigned char *where, char *tag, int value) { where[9] = (value >> 16) & 0xFF; where[10] = (value >> 8) & 0xFF; where[11] = value & 0xFF; - + return 12; } @@ -835,8 +863,8 @@ int db_dmap_add_string(unsigned char *where, char *tag, char *value) { * \param value what to put there * \param size how much data to cram in there */ -int db_dmap_add_literal(unsigned char *where, char *tag, - char *value, int size) { +int db_dmap_add_literal(unsigned char *where, char *tag, + char *value, int size) { /* tag */ memcpy(where,tag,4); @@ -885,7 +913,7 @@ void db_utf8_validate(MP3FILE *pmp3) { int is_invalid=0; /* we won't bother with path and fname... those were culled with the - * scan. Even if they are invalid (_could_ they be?), then we + * scan. Even if they are invalid (_could_ they be?), then we * won't be able to open the file if we change them. Likewise, * we won't do type or description, as these can't be bad, or they * wouldn't have been scanned */ @@ -902,7 +930,7 @@ void db_utf8_validate(MP3FILE *pmp3) { is_invalid |= db_utf8_validate_string(pmp3->url); if(is_invalid) { - DPRINTF(E_LOG,L_SCAN,"Invalid UTF-8 in %s\n",pmp3->path); + DPRINTF(E_LOG,L_SCAN,"Invalid UTF-8 in %s\n",pmp3->path); } } @@ -919,38 +947,38 @@ int db_utf8_validate_string(char *string) { int retval=0; if(!string) - return 0; + return 0; while(*current) { - if(!((*current) & 0x80)) { - current++; - } else { - run=0; + if(!((*current) & 0x80)) { + current++; + } else { + run=0; - /* it's a lead utf-8 character */ - if((*current & 0xE0) == 0xC0) run=1; - if((*current & 0xF0) == 0xE0) run=2; - if((*current & 0xF8) == 0xF0) run=3; + /* it's a lead utf-8 character */ + if((*current & 0xE0) == 0xC0) run=1; + if((*current & 0xF0) == 0xE0) run=2; + if((*current & 0xF8) == 0xF0) run=3; - if(!run) { - /* high bit set, but invalid */ - *current++='?'; - retval=1; - } else { - r_current=0; - while((r_current != run) && (*(current + r_current + 1)) && - ((*(current + r_current + 1) & 0xC0) == 0x80)) { - r_current++; - } + if(!run) { + /* high bit set, but invalid */ + *current++='?'; + retval=1; + } else { + r_current=0; + while((r_current != run) && (*(current + r_current + 1)) && + ((*(current + r_current + 1) & 0xC0) == 0x80)) { + r_current++; + } - if(r_current != run) { - *current++ = '?'; - retval=1; - } else { - current += (1 + run); - } - } - } + if(r_current != run) { + *current++ = '?'; + retval=1; + } else { + current += (1 + run); + } + } + } } return retval; @@ -986,9 +1014,9 @@ void db_trim_strings(MP3FILE *pmp3) { */ void db_trim_string(char *string) { if(!string) - return; + return; while(strlen(string) && (string[strlen(string) - 1] == ' ')) - string[strlen(string) - 1] = '\0'; + string[strlen(string) - 1] = '\0'; } diff --git a/src/db-generic.h b/src/db-generic.h index 5e673c44..83d10135 100644 --- a/src/db-generic.h +++ b/src/db-generic.h @@ -32,7 +32,7 @@ typedef enum { metaPersistentId, metaContainerItemId, metaParentContainerId, - + firstTypeSpecificMetaId, // song meta data @@ -123,7 +123,7 @@ typedef struct tag_dbqueryinfo { } DBQUERYINFO; typedef struct { - const char* tag; + const char* tag; MetaFieldName_t bit; } METAMAP; @@ -137,34 +137,36 @@ extern DAAP_ITEMS taglist[]; extern int db_set_backend(char *type); -extern int db_open(char *parameters); +extern int db_open(char **pe, char *parameters); extern int db_init(int reload); extern int db_deinit(void); extern int db_revision(void); -extern int db_add(MP3FILE *pmp3); +extern int db_add(char **pe, MP3FILE *pmp3); -extern int db_enum_start(DBQUERYINFO *pinfo); -extern int db_enum_size(DBQUERYINFO *pinfo, int *count); -extern int db_enum_fetch(DBQUERYINFO *pinfo, unsigned char **pdmap); -extern int db_enum_reset(DBQUERYINFO *pinfo); -extern int db_enum_end(void); +extern int db_enum_start(char **pe, DBQUERYINFO *pinfo); +extern int db_enum_size(char **pe, DBQUERYINFO *pinfo, int *count, int *total_size); +extern int db_enum_fetch(char **pe, DBQUERYINFO *pinfo, int *size, unsigned char **pdmap); +extern int db_enum_reset(char **pe, DBQUERYINFO *pinfo); +extern int db_enum_end(char **pe); extern int db_start_scan(void); extern int db_end_song_scan(void); extern int db_end_scan(void); extern int db_exists(char *path); extern int db_scanning(void); -extern int db_add_playlist(char *name, int type, char *clause, char *path, int index, int *playlistid); -extern int db_add_playlist_item(int playlistid, int songid); -extern int db_edit_playlist(int id, char *name, char *clause); -extern int db_delete_playlist(int playlistid); -extern int db_delete_playlist_item(int playlistid, int songid); +extern int db_add_playlist(char **pe, char *name, int type, char *clause, char *path, int index, int *playlistid); +extern int db_add_playlist_item(char **pe, int playlistid, int songid); +extern int db_edit_playlist(char **pe, int id, char *name, char *clause); +extern int db_delete_playlist(char **pe, int playlistid); +extern int db_delete_playlist_item(char **pe, int playlistid, int songid); -extern MP3FILE *db_fetch_item(int id); -extern MP3FILE *db_fetch_path(char *path, int index); -extern M3UFILE *db_fetch_playlist(char *path, int index); +extern void db_get_error(char **pe, int err, ...); + +extern MP3FILE *db_fetch_item(char **pe, int id); +extern MP3FILE *db_fetch_path(char **pe, char *path, int index); +extern M3UFILE *db_fetch_playlist(char **pe, char *path, int index); /* metatag parsing */ @@ -182,8 +184,9 @@ extern int db_dmap_add_container(unsigned char *where, char *tag, int size); /* Holdover functions from old db interface... * should these be removed? Refactored? */ -extern int db_get_song_count(void); -extern int db_get_playlist_count(void); + +extern int db_get_song_count(char **pe, int *count); +extern int db_get_playlist_count(char **pe, int *count); extern void db_dispose_item(MP3FILE *pmp3); extern void db_dispose_playlist(M3UFILE *pm3u); diff --git a/src/db-sql-sqlite2.c b/src/db-sql-sqlite2.c new file mode 100644 index 00000000..2b1bcad4 --- /dev/null +++ b/src/db-sql-sqlite2.c @@ -0,0 +1,456 @@ +/* + * $Id$ + * sqlite2-specific db implementation + * + * Copyright (C) 2005 Ron Pedde (ron@pedde.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This file handles sqlite2 databases. SQLite2 databases + * should have a dsn of: + * + * sqlite2:/path/to/folder + * + * The actual db will be appended to the passed path. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define _XOPEN_SOURCE 500 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "err.h" +#include "db-generic.h" +#include "db-sql.h" +#include "db-sql-sqlite2.h" + +#ifndef TRUE +# define TRUE 1 +# define FALSE 0 +#endif + + +/* Globals */ +static sqlite *db_sqlite2_songs; /**< Database that holds the mp3 info */ +static pthread_mutex_t db_sqlite2_mutex = PTHREAD_MUTEX_INITIALIZER; /**< sqlite not reentrant */ +static sqlite_vm *db_sqlite2_pvm; +static int db_sqlite2_reload=0; +static char *db_sqlite2_enum_query; +static int db_sqlite2_in_enum=0; + +static char db_sqlite2_path[PATH_MAX + 1]; + +#define DB_SQLITE2_VERSION 8 + + +/* Forwards */ +void db_sqlite2_lock(void); +void db_sqlite2_unlock(void); +extern char *db_initial; + +/** + * lock the db_mutex + */ +void db_sqlite2_lock(void) { + int err; + + if((err=pthread_mutex_lock(&db_sqlite2_mutex))) { + DPRINTF(E_FATAL,L_DB,"cannot lock sqlite lock: %s\n",strerror(err)); + } +} + +/** + * unlock the db_mutex + */ +void db_sqlite2_unlock(void) { + int err; + + if((err=pthread_mutex_unlock(&db_sqlite2_mutex))) { + DPRINTF(E_FATAL,L_DB,"cannot unlock sqlite2 lock: %s\n",strerror(err)); + } +} + +/** + * + */ +char *db_sqlite2_vmquery(char *fmt,va_list ap) { + return sqlite_vmprintf(fmt,ap); +} + +/** + * + */ +void db_sqlite2_vmfree(char *query) { + sqlite_freemem(query); +} + + +/** + * open a sqlite2 database + * + * @param dsn the full dns to the database + * (sqlite2:/path/to/database) + * + * @returns DB_E_SUCCESS on success + */ +int db_sqlite2_open(char **pe, char *dsn) { + char *perr; + int ver; + int err; + + snprintf(db_sqlite2_path,sizeof(db_sqlite2_path),"%s/songs.db",dsn); + + db_sqlite2_lock(); + db_sqlite2_songs=sqlite_open(db_sqlite2_path,0666,&perr); + if(!db_sqlite2_songs) { + db_get_error(pe,DB_E_SQL_ERROR,perr); + DPRINTF(E_LOG,L_DB,"db_sqlite2_open: %s (%s)\n",perr, + db_sqlite2_path); + sqlite_freemem(perr); + db_sqlite2_unlock(); + return DB_E_SQL_ERROR; + } + + sqlite_busy_timeout(db_sqlite2_songs,30000); /* 30 seconds */ + + err = db_sql_fetch_int(pe,&ver,"select value from config where term='version'"); + if(err != DB_E_SUCCESS) { + free(*pe); + /* create the table */ + DPRINTF(E_FATAL,L_DB,"Can't create table yet!\n"); + } + + if(ver != DB_SQLITE2_VERSION) { + DPRINTF(E_FATAL,L_DB,"Can't upgrade database!\n"); + } + + db_sqlite2_unlock(); + return DB_E_SUCCESS; +} + +/** + * close the database + */ +int db_sqlite2_close(void) { + db_sqlite2_lock(); + sqlite_close(db_sqlite2_songs); + db_sqlite2_unlock(); + return DB_E_SUCCESS; +} + +/** + * execute a throwaway query against the database, disregarding + * the outcome + * + * @param pe db error structure + * @param loglevel error level to return if the query fails + * @param fmt sprintf-style arguements + * + * @returns DB_E_SUCCESS on success + */ +int db_sqlite2_exec(char **pe, int loglevel, char *fmt, ...) { + va_list ap; + char *query; + int err; + char *perr; + + va_start(ap,fmt); + query=sqlite_vmprintf(fmt,ap); + va_end(ap); + + DPRINTF(E_DBG,L_DB,"Executing: %s\n",query); + + db_sqlite2_lock(); + err=sqlite_exec(db_sqlite2_songs,query,NULL,NULL,&perr); + if(err != SQLITE_OK) { + db_get_error(pe,DB_E_SQL_ERROR,perr); + + DPRINTF(loglevel == E_FATAL ? E_LOG : loglevel,L_DB,"Query: %s\n", + query); + DPRINTF(loglevel,L_DB,"Error: %s\n",perr); + sqlite_freemem(perr); + } else { + DPRINTF(E_DBG,L_DB,"Rows: %d\n",sqlite_changes(db_sqlite2_songs)); + } + sqlite_freemem(query); + + db_sqlite2_unlock(); + + if(err != SQLITE_OK) + return DB_E_SQL_ERROR; + return DB_E_SUCCESS; +} + +/** + * start enumerating rows in a select + */ +int db_sqlite2_enum_begin(char **pe, char *fmt, ...) { + va_list ap; + int err; + char *perr; + const char *ptail; + + if(!db_sqlite2_in_enum) { + va_start(ap, fmt); + db_sqlite2_lock(); + db_sqlite2_enum_query = sqlite_vmprintf(fmt,ap); + va_end(ap); + } + + DPRINTF(E_DBG,L_DB,"Executing :%s\n",db_sqlite2_enum_query); + db_sqlite2_in_enum=1; + + err=sqlite_compile(db_sqlite2_songs,db_sqlite2_enum_query,&ptail,&db_sqlite2_pvm,&perr); + + if(err != SQLITE_OK) { + db_get_error(pe,DB_E_SQL_ERROR,perr); + sqlite_freemem(perr); + db_sqlite2_in_enum=0; + db_sqlite2_unlock(); + sqlite_freemem(db_sqlite2_enum_query); + return DB_E_SQL_ERROR; + } + + /* otherwise, we leave the db locked while we walk through the enums */ + return DB_E_SUCCESS; +} + +/** + * fetch the next row + * + * @param pe error string, if result isn't DB_E_SUCCESS + * @param pr pointer to a row struct + * + * @returns DB_E_SUCCESS with *pr=NULL when end of table, + * DB_E_SUCCESS with a valid row when more data, + * DB_E_* on error + */ +int db_sqlite2_enum_fetch(char **pe, SQL_ROW *pr) { + int err; + char *perr=NULL;; + const char **colarray; + int cols; + int counter=10; + + while(counter--) { + err=sqlite_step(db_sqlite2_pvm,&cols,(const char ***)pr,&colarray); + if(err != SQLITE_BUSY) + break; + usleep(100); + } + + if(err == SQLITE_DONE) { + *pr = NULL; + return DB_E_SUCCESS; + } + + if(err == SQLITE_ROW) { + return DB_E_SUCCESS; + } + + db_get_error(pe,DB_E_SQL_ERROR,perr); + return DB_E_SQL_ERROR; +} + +/** + * end the db enumeration + */ +int db_sqlite2_enum_end(char **pe) { + int err; + char *perr; + + db_sqlite2_in_enum=0; + sqlite_freemem(db_sqlite2_enum_query); + + err = sqlite_finalize(db_sqlite2_pvm,&perr); + if(err != SQLITE_OK) { + db_get_error(pe,DB_E_SQL_ERROR,perr); + sqlite_freemem(perr); + db_sqlite2_unlock(); + return DB_E_SQL_ERROR; + } + + db_sqlite2_unlock(); + return DB_E_SUCCESS; +} + +/** + * restart the enumeration + */ +int db_sqlite2_enum_restart(char **pe) { + return db_sqlite2_enum_begin(pe,NULL); +} + + +int db_sqlite2_event(int event_type) { + switch(event_type) { + + case DB_SQL_EVENT_STARTUP: /* this is a startup with existing songs */ + db_sqlite2_exec(NULL,E_FATAL,"vacuum"); + db_sqlite2_reload=0; + break; + + case DB_SQL_EVENT_FULLRELOAD: /* either a fresh load or force load */ + db_sqlite2_exec(NULL,E_DBG,"delete index idx_path"); + db_sqlite2_exec(NULL,E_DBG,"delete index idx_songid"); + db_sqlite2_exec(NULL,E_DBG,"delete index idx_playlistid"); + + db_sqlite2_exec(NULL,E_DBG,"drop table songs"); + db_sqlite2_exec(NULL,E_DBG,"drop table playlists"); + db_sqlite2_exec(NULL,E_DBG,"drop table playlistitems"); + db_sqlite2_exec(NULL,E_DBG,"drop table config"); + + db_sqlite2_exec(NULL,E_DBG,"vacuum"); + + db_sqlite2_exec(NULL,E_DBG,db_initial); + db_sqlite2_reload=1; + break; + + case DB_SQL_EVENT_SONGSCANSTART: + if(db_sqlite2_reload) { + db_sqlite2_exec(NULL,E_FATAL,"pragma synchronous = off"); + db_sqlite2_exec(NULL,E_FATAL,"begin transaction"); + } else { + db_sqlite2_exec(NULL,E_DBG,"drop table updated"); + db_sqlite2_exec(NULL,E_FATAL,"create temp table updated (id int)"); + db_sqlite2_exec(NULL,E_DBG,"drop table plupdated"); + db_sqlite2_exec(NULL,E_FATAL,"create temp table plupdated(id int)"); + } + break; + + case DB_SQL_EVENT_SONGSCANEND: + if(db_sqlite2_reload) { + db_sqlite2_exec(NULL,E_FATAL,"commit transaction"); + db_sqlite2_exec(NULL,E_FATAL,"create index idx_path on songs(path)"); + db_sqlite2_exec(NULL,E_DBG,"delete from config where term='rescan'"); + } else { + db_sqlite2_exec(NULL,E_FATAL,"delete from songs where id not in (select id from updated)"); + db_sqlite2_exec(NULL,E_FATAL,"update songs set force_update=0"); + db_sqlite2_exec(NULL,E_FATAL,"drop table updated"); + } + break; + + case DB_SQL_EVENT_PLSCANSTART: + db_sqlite2_exec(NULL,E_FATAL,"begin transaction"); + break; + + case DB_SQL_EVENT_PLSCANEND: + db_sqlite2_exec(NULL,E_FATAL,"end transaction"); + + if(db_sqlite2_reload) { + db_sqlite2_exec(NULL,E_FATAL,"pragma synchronous=normal"); + db_sqlite2_exec(NULL,E_FATAL,"create index idx_songid on playlistitems(songid)"); + db_sqlite2_exec(NULL,E_FATAL,"create index idx_playlistid on playlistitems(playlistid)"); + + } else { + db_sqlite2_exec(NULL,E_FATAL,"delete from playlists where " + "((type=%d) OR (type=%d)) and " + "id not in (select id from plupdated)", + PL_STATICFILE,PL_STATICXML); + db_sqlite2_exec(NULL,E_FATAL,"delete from playlistitems where " + "playlistid not in (select distinct " + "id from playlists)"); + db_sqlite2_exec(NULL,E_FATAL,"drop table plupdated"); + } + break; + + default: + break; + } + + return DB_E_SUCCESS; +} + +char *db_initial = + "create table songs (\n" + " id INTEGER PRIMARY KEY NOT NULL,\n" + " path VARCHAR(4096) UNIQUE NOT NULL,\n" + " fname VARCHAR(255) NOT NULL,\n" + " title VARCHAR(1024) DEFAULT NULL,\n" + " artist VARCHAR(1024) DEFAULT NULL,\n" + " album VARCHAR(1024) DEFAULT NULL,\n" + " genre VARCHAR(255) DEFAULT NULL,\n" + " comment VARCHAR(4096) DEFAULT NULL,\n" + " type VARCHAR(255) DEFAULT NULL,\n" + " composer VARCHAR(1024) DEFAULT NULL,\n" + " orchestra VARCHAR(1024) DEFAULT NULL,\n" + " conductor VARCHAR(1024) DEFAULT NULL,\n" + " grouping VARCHAR(1024) DEFAULT NULL,\n" + " url VARCHAR(1024) DEFAULT NULL,\n" + " bitrate INTEGER DEFAULT 0,\n" + " samplerate INTEGER DEFAULT 0,\n" + " song_length INTEGER DEFAULT 0,\n" + " file_size INTEGER DEFAULT 0,\n" + " year INTEGER DEFAULT 0,\n" + " track INTEGER DEFAULT 0,\n" + " total_tracks INTEGER DEFAULT 0,\n" + " disc INTEGER DEFAULT 0,\n" + " total_discs INTEGER DEFAULT 0,\n" + " bpm INTEGER DEFAULT 0,\n" + " compilation INTEGER DEFAULT 0,\n" + " rating INTEGER DEFAULT 0,\n" + " play_count INTEGER DEFAULT 0,\n" + " data_kind INTEGER DEFAULT 0,\n" + " item_kind INTEGER DEFAULT 0,\n" + " description INTEGER DEFAULT 0,\n" + " time_added INTEGER DEFAULT 0,\n" + " time_modified INTEGER DEFAULT 0,\n" + " time_played INTEGER DEFAULT 0,\n" + " db_timestamp INTEGER DEFAULT 0,\n" + " disabled INTEGER DEFAULT 0,\n" + " sample_count INTEGER DEFAULT 0,\n" + " force_update INTEGER DEFAULT 0,\n" + " codectype VARCHAR(5) DEFAULT NULL,\n" + " idx INTEGER NOT NULL\n" + ");\n" + "create table config (\n" + " term VARCHAR(255) NOT NULL,\n" + " subterm VARCHAR(255) DEFAULT NULL,\n" + " value VARCHAR(1024) NOT NULL\n" + ");\n" + "create table playlistitems (\n" + " id INTEGER PRIMARY KEY NOT NULL,\n" + " playlistid INTEGER NOT NULL,\n" + " songid INTEGER NOT NULL\n" + ");\n" + "create table playlists (\n" + " id INTEGER PRIMARY KEY NOT NULL,\n" + " title VARCHAR(255) NOT NULL,\n" + " type INTEGER NOT NULL,\n" + " items INTEGER NOT NULL,\n" + " query VARCHAR(1024),\n" + " db_timestamp INTEGER NOT NULL,\n" + " path VARCHAR(4096),\n" + " idx INTEGER NOT NULL\n" + ");\n" + "insert into config values ('version','','1');\n" + "insert into playlists values (1,'Library',1,0,'1',0,'',0);\n" + "create index idx_path on songs(path);\n" + "create index idx_songid on playlistitems(songid);\n" + "create index idx_playlistid on playlistitems(playlistid);\n"; + + diff --git a/src/db-sql-sqlite2.h b/src/db-sql-sqlite2.h new file mode 100644 index 00000000..36600009 --- /dev/null +++ b/src/db-sql-sqlite2.h @@ -0,0 +1,42 @@ +/* + * $Id$ + * sqlite2-specific db implementation + * + * Copyright (C) 2005 Ron Pedde (ron@pedde.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _DB_SQL_SQLITE2_ +#define _DB_SQL_SQLITE2_ + +extern int db_sqlite2_open(char **pe, char *dsn); +extern int db_sqlite2_close(void); + +/* simple utility functions */ +extern int db_sqlite2_exec(char **pe, int loglevel, char *fmt, ...); +extern char *db_sqlite2_vmquery(char *fmt,va_list ap); +extern void db_sqlite2_vmfree(char *query); + +/* walk through a table */ +extern int db_sqlite2_enum_begin(char **pe, char *fmt, ...); +extern int db_sqlite2_enum_fetch(char **pe, SQL_ROW *pr); +extern int db_sqlite2_enum_end(char **pe); +extern int db_sqlite2_enum_restart(char **pe); + +int db_sqlite2_event(int event_type); + +#endif /* _DB_SQL_SQLITE2_ */ + diff --git a/src/db-sql.c b/src/db-sql.c new file mode 100644 index 00000000..97e46604 --- /dev/null +++ b/src/db-sql.c @@ -0,0 +1,1636 @@ +/* + * $Id$ + * sql-specific db implementation + * + * Copyright (C) 2005 Ron Pedde (ron@pedde.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define _XOPEN_SOURCE 500 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "err.h" +#include "mp3-scanner.h" +#include "db-generic.h" +#include "db-sql.h" +#include "db-sql-sqlite2.h" +#include "restart.h" +#include "ssc.h" +#include "smart-parser.h" + +/* Globals */ +static int db_sql_reload=0; +static int db_sql_in_playlist_scan=0; +static int db_sql_in_scan=0; +static int db_sql_need_dispose=0; + +/* Forwards */ +int db_sql_get_size(DBQUERYINFO *pinfo, char **valarray); +int db_sql_build_dmap(DBQUERYINFO *pinfo, char **valarray, unsigned char *presult, int len); +void db_sql_build_mp3file(char **valarray, MP3FILE *pmp3); +int db_sql_update(char **pe, MP3FILE *pmp3); +int db_sql_update_playlists(char **pe); +char *db_sql_parse_smart(char *phrase); + + +#define STR(a) (a) ? (a) : "" +#define ISSTR(a) ((a) && strlen((a))) +#define MAYBEFREE(a) { if((a)) free((a)); }; + + +/** + * fetch a single row, using the underlying database enum + * functions + */ +int db_sql_fetch_row(char **pe, SQL_ROW *row, char *fmt, ...) { + int err; + char *query; + va_list ap; + + db_sql_need_dispose = 0; + + va_start(ap,fmt); + query=db_sqlite2_vmquery(fmt,ap); + va_end(ap); + + err=db_sqlite2_enum_begin(pe,query); + db_sqlite2_vmfree(query); + + + if(err != DB_E_SUCCESS) { + return err; + } + + err=db_sqlite2_enum_fetch(pe, row); + if(err != DB_E_SUCCESS) { + db_sqlite2_enum_end(NULL); + return err; + } + + if(!row) { + db_sqlite2_enum_end(NULL); + db_get_error(pe,DB_E_NOROWS); + return DB_E_NOROWS; + } + + db_sql_need_dispose = 1; + return DB_E_SUCCESS; +} + +int db_sql_fetch_int(char **pe, int *result, char *fmt, ...) { + int err; + char *query; + va_list ap; + SQL_ROW row; + + va_start(ap,fmt); + query=db_sqlite2_vmquery(fmt,ap); + va_end(ap); + + err = db_sql_fetch_row(pe, &row, query); + if(err != DB_E_SUCCESS) + return err; + + *result = atoi(row[0]); + db_sql_dispose_row(); + return DB_E_SUCCESS; +} + +int db_sql_fetch_char(char **pe, char **result, char *fmt, ...) { + int err; + char *query; + va_list ap; + SQL_ROW row; + + va_start(ap,fmt); + query=db_sqlite2_vmquery(fmt,ap); + va_end(ap); + + err = db_sql_fetch_row(pe, &row, query); + if(err != DB_E_SUCCESS) + return err; + + *result = strdup(row[0]); + db_sql_dispose_row(); + return DB_E_SUCCESS; +} + +int db_sql_dispose_row(void) { + int err = DB_E_SUCCESS; + + /* don't really need the row */ + if(db_sql_need_dispose) { + err=db_sqlite2_enum_end(NULL); + db_sql_need_dispose=0; + } + + return err; +} + +/** + * get the sql where clause for a smart playlist spec. This + * where clause must be freed by the caller + * + * @param phrase playlist spec to be converted + * @returns sql where clause if successful, NULL otherwise + */ +char *db_sql_parse_smart(char *phrase) { + PARSETREE pt; + char *result = NULL; + + if(strcmp(phrase,"1") == 0) + return strdup("1"); + + pt=sp_init(); + if(!pt) + return NULL; + + if(!sp_parse(pt,phrase)) { + DPRINTF(E_LOG,L_DB,"Error parsing smart playlist: %s",sp_get_error(pt)); + sp_dispose(pt); + return strdup("0"); + } else { + result = sp_sql_clause(pt); + } + + sp_dispose(pt); + return result; +} + +/** + * open sqlite database + * + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_open(char **pe, char *parameters) { + return db_sqlite2_open(pe, parameters); +} + +/** + * initialize the sqlite database, reloading if requested + * + * @param reload whether or not to do a full reload on the db + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_init(int reload) { + int items; + int rescan = 0; + int err; + + + err=db_sql_get_count(NULL,&items, countSongs); + if(err != DB_E_SUCCESS) + items = 0; + + /* check if a request has been written into the db (by a db upgrade?) */ + if(db_sql_fetch_int(NULL,&rescan,"select value from config where " + "term='rescan'") == DB_E_SUCCESS) + { + if(rescan) + reload=1; + } + + + + if(reload || (!items)) { + DPRINTF(E_LOG,L_DB,"Full reload...\n"); + db_sqlite2_event(DB_SQL_EVENT_FULLRELOAD); + db_sql_reload=1; + } else { + db_sqlite2_event(DB_SQL_EVENT_STARTUP); + db_sql_reload=0; + } + + return DB_E_SUCCESS; +} + +/** + * close the database + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_deinit(void) { + return db_sqlite2_close(); +} + +/** + * start a background scan + * + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_start_scan(void) { + DPRINTF(E_DBG,L_DB,"Starting db scan\n"); + db_sqlite2_event(DB_SQL_EVENT_SONGSCANSTART); + + db_sql_in_scan=1; + db_sql_in_playlist_scan=0; + return DB_E_SUCCESS; +} + +/** + * end song scan -- start playlist scan + * + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_end_song_scan(void) { + DPRINTF(E_DBG,L_DB,"Ending song scan\n"); + + db_sqlite2_event(DB_SQL_EVENT_SONGSCANEND); + db_sqlite2_event(DB_SQL_EVENT_PLSCANSTART); + + db_sql_in_scan=0; + db_sql_in_playlist_scan=1; + + return DB_E_SUCCESS; +} + +/** + * stop a db scan + * + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_end_scan(void) { + db_sqlite2_event(DB_SQL_EVENT_PLSCANEND); + + db_sql_update_playlists(NULL); + db_sql_reload=0; + db_sql_in_playlist_scan=0; + + return DB_E_SUCCESS; +} + +/** + * delete a playlist + * + * @param playlistid playlist to delete + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_delete_playlist(char **pe, int playlistid) { + int type; + int result; + + result=db_sql_fetch_int(pe,&type,"select type from playlists where id=%d", + playlistid); + + if(result != DB_E_SUCCESS) { + if(result == DB_E_NOROWS) { /* Override the generic error */ + free(*pe); + db_get_error(pe,DB_E_INVALID_PLAYLIST); + return DB_E_INVALID_PLAYLIST; + } + + return result; + } + + /* got a good playlist, now do what we need to do */ + db_sqlite2_exec(pe,E_FATAL,"delete from playlists where id=%d",playlistid); + db_sqlite2_exec(pe,E_FATAL,"delete from playlistitems where playlistid=%d",playlistid); + + return DB_E_SUCCESS; +} + +/** + * delete a song from a playlist + * + * @param playlistid playlist to delete item from + * @param songid song to delete from playlist + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_delete_playlist_item(char **pe, int playlistid, int songid) { + int result; + int playlist_type; + int count; + + /* first, check the playlist */ + result=db_sql_fetch_int(pe,&playlist_type, + "select type from playlists where id=%d", + playlistid); + + if(result != DB_E_SUCCESS) { + if(result == DB_E_NOROWS) { /* Override generic error */ + free(*pe); + db_get_error(pe,DB_E_INVALID_PLAYLIST); + return DB_E_INVALID_PLAYLIST; + } + return result; + } + + if(playlist_type == PL_SMART) { /* can't delete from a smart playlist */ + db_get_error(pe,DB_E_INVALIDTYPE); + return DB_E_INVALIDTYPE; + } + + /* make sure the songid is valid */ + result=db_sql_fetch_int(pe,&count,"select count(*) from playlistitems " + "where playlistid=%d and songid=%d", + playlistid,songid); + + if(result != DB_E_SUCCESS) { + if(result == DB_E_NOROWS) { /* Override generic error */ + free(*pe); + db_get_error(pe,DB_E_INVALID_SONGID); + return DB_E_INVALID_SONGID; + } + return result; + } + + /* looks valid, so lets add the item */ + result=db_sqlite2_exec(pe,E_DBG,"delete from playlistitems where " + "playlistid=%d and songid=%d",playlistid,songid); + + return result; +} + +/** + * edit a playlist. The only things worth changing are the name + * and the "where" clause. + * + * @param id id of the playlist to alter + * @param name new name of the playlist + * @param where new where clause + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_edit_playlist(char **pe, int id, char *name, char *clause) { + int result; + int playlist_type; + + /* first, check the playlist */ + result=db_sql_fetch_int(pe,&playlist_type, + "select type from playlists where id=%d",id); + + if(result != DB_E_SUCCESS) { + if(result == DB_E_NOROWS) { /* Override generic error */ + free(*pe); + db_get_error(pe,DB_E_INVALID_PLAYLIST); + return DB_E_INVALID_PLAYLIST; + } + return result; + } + + /* TODO: check for duplicate names here */ + + if(playlist_type != PL_SMART) { /* Ignore the clause */ + return db_sqlite2_exec(pe,E_LOG,"update playlists set title='%q' " + "where id=%d",name,id); + } + + return db_sqlite2_exec(pe,E_LOG,"update playlists set title='%q'," + "query='%q' where id=%d",name, clause, id); +} + +/** + * add a playlist + * + * @param name name of the playlist + * @param type playlist type: 0 - static, 1 - smart, 2 - m3u + * @param clause: "where" clause for smart playlist + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_add_playlist(char **pe, char *name, int type, char *clause, char *path, int index, int *playlistid) { + int cnt=0; + int result=DB_E_SUCCESS; + char *criteria; + + result=db_sql_fetch_int(pe,&cnt,"select count(*) from playlists where " + "upper(title)=upper('%q')",name); + + if(result == DB_E_NOROWS) { /* good playlist name */ + free(*pe); + } else { + if(result != DB_E_SUCCESS) { + return result; + } else { + db_get_error(pe,DB_E_DUPLICATE_PLAYLIST); + return DB_E_DUPLICATE_PLAYLIST; + } + } + + if((type == PL_SMART) && (!clause)) { + db_get_error(pe,DB_E_NOCLAUSE); + return DB_E_NOCLAUSE; + } + + /* Let's throw it in */ + switch(type) { + case PL_STATICWEB: /* static, maintained in web interface */ + case PL_STATICFILE: /* static, from file */ + case PL_STATICXML: /* from iTunes XML file */ + result = db_sqlite2_exec(pe,E_LOG,"insert into playlists " + "(title,type,items,query,db_timestamp,path,idx) " + "values ('%q',%d,0,NULL,%d,'%q',%d)", + name,type,time(NULL),path,index); + break; + case PL_SMART: /* smart */ + criteria = db_sql_parse_smart(clause); + if(!criteria) { + db_get_error(pe,DB_E_PARSE); + return DB_E_PARSE; + } + free(criteria); + + result = db_sqlite2_exec(pe,E_LOG,"insert into playlists " + "(title,type,items,query,db_timestamp,idx) " + "values ('%q',%d,%d,'%q',%d,0)", + name,PL_SMART,cnt,clause,time(NULL)); + break; + } + + if(result) + return result; + + result = db_sql_fetch_int(pe,playlistid, + "select id from playlists where title='%q'", + name); + + if(((type==PL_STATICFILE)||(type==PL_STATICXML)) + && (db_sql_in_playlist_scan) && (!db_sql_reload)) { + db_sqlite2_exec(NULL,E_FATAL,"insert into plupdated values (%d)",*playlistid); + } + + return result; +} + +/** + * add a song to a static playlist + * + * @param playlistid playlist to add song to + * @param songid song to add + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_add_playlist_item(char **pe, int playlistid, int songid) { + int result; + int playlist_type; + int count; + + /* first, check the playlist */ + result=db_sql_fetch_int(pe,&playlist_type, + "select type from playlists where id=%d", + playlistid); + + if(result != DB_E_SUCCESS) { + if(result == DB_E_NOROWS) { /* Override generic error */ + free(*pe); + db_get_error(pe,DB_E_INVALID_PLAYLIST); + return DB_E_INVALID_PLAYLIST; + } + return result; + } + + if(playlist_type == 1) { /* can't add to smart playlists, or static */ + db_get_error(pe,DB_E_INVALIDTYPE); + return DB_E_INVALIDTYPE; + } + + /* make sure the songid is valid */ + result=db_sql_fetch_int(pe,&count,"select count(*) from songs where " + "id=%d",songid); + + if(result != DB_E_SUCCESS) { + if(result == DB_E_NOROWS) { /* Override generic error */ + free(*pe); + db_get_error(pe,DB_E_INVALID_SONGID); + return DB_E_INVALID_SONGID; + } + return result; + } + + /* looks valid, so lets add the item */ + result=db_sqlite2_exec(pe,E_DBG,"insert into playlistitems " + "(playlistid, songid) values (%d,%d)", + playlistid,songid); + return result; +} + + +/** + * add a database item + * + * @param pmp3 mp3 file to add + */ +int db_sql_add(char **pe, MP3FILE *pmp3) { + int err; + int count; + + DPRINTF(E_SPAM,L_DB,"Entering db_sql_add\n"); + + if(!pmp3->time_added) + pmp3->time_added = (int)time(NULL); + + if(!pmp3->time_modified) + pmp3->time_modified = (int)time(NULL); + + pmp3->db_timestamp = (int)time(NULL); + + + if(!db_sql_reload) { /* if we are in a reload, then no need to check */ + err=db_sql_fetch_int(NULL,&count,"select count(*) from songs where " + "path='%q'",pmp3->path); + + if((err == DB_E_SUCCESS) && (count == 1)) { /* we should update */ + return db_sql_update(pe,pmp3); + } + } + + pmp3->play_count=0; + pmp3->time_played=0; + + err=db_sqlite2_exec(pe,E_DBG,"INSERT INTO songs VALUES " + "(NULL," // id + "'%q'," // path + "'%q'," // fname + "'%q'," // title + "'%q'," // artist + "'%q'," // album + "'%q'," // genre + "'%q'," // comment + "'%q'," // type + "'%q'," // composer + "'%q'," // orchestra + "'%q'," // conductor + "'%q'," // grouping + "'%q'," // url + "%d," // bitrate + "%d," // samplerate + "%d," // song_length + "%d," // file_size + "%d," // year + "%d," // track + "%d," // total_tracks + "%d," // disc + "%d," // total_discs + "%d," // bpm + "%d," // compilation + "%d," // rating + "0," // play_count + "%d," // data_kind + "%d," // item_kind + "'%q'," // description + "%d," // time_added + "%d," // time_modified + "%d," // time_played + "%d," // db_timestamp + "%d," // disabled + "%d," // sample_count + "0," // force_update + "'%q'," // codectype + "%d)", // index + STR(pmp3->path), + STR(pmp3->fname), + STR(pmp3->title), + STR(pmp3->artist), + STR(pmp3->album), + STR(pmp3->genre), + STR(pmp3->comment), + STR(pmp3->type), + STR(pmp3->composer), + STR(pmp3->orchestra), + STR(pmp3->conductor), + STR(pmp3->grouping), + STR(pmp3->url), + pmp3->bitrate, + pmp3->samplerate, + pmp3->song_length, + pmp3->file_size, + pmp3->year, + pmp3->track, + pmp3->total_tracks, + pmp3->disc, + pmp3->total_discs, + pmp3->bpm, + pmp3->compilation, + pmp3->rating, + pmp3->data_kind, + pmp3->item_kind, + STR(pmp3->description), + pmp3->time_added, + pmp3->time_modified, + pmp3->time_played, + pmp3->db_timestamp, + pmp3->disabled, + pmp3->sample_count, + STR(pmp3->codectype), + pmp3->index); + + if(err != DB_E_SUCCESS) + DPRINTF(E_FATAL,L_DB,"Error inserting file %s in database\n",pmp3->fname); + + if((db_sql_in_scan)&&(!db_sql_reload)) { + /* FIXME: this is sqlite-specific */ + db_sqlite2_exec(NULL,E_FATAL,"insert into updated values (last_insert_rowid())"); + } + + if((!db_sql_in_scan) && (!db_sql_in_playlist_scan)) + db_sql_update_playlists(NULL); + + DPRINTF(E_SPAM,L_DB,"Exiting db_sql_add\n"); + return DB_E_SUCCESS; +} + +/** + * update a database item + * + * @param pmp3 mp3 file to update + */ +int db_sql_update(char **pe, MP3FILE *pmp3) { + int err; + + if(!pmp3->time_modified) + pmp3->time_modified = (int)time(NULL); + + pmp3->db_timestamp = (int)time(NULL); + + err=db_sqlite2_exec(pe,E_LOG,"UPDATE songs SET " + "title='%q'," // title + "artist='%q'," // artist + "album='%q'," // album + "genre='%q'," // genre + "comment='%q'," // comment + "type='%q'," // type + "composer='%q'," // composer + "orchestra='%q'," // orchestra + "conductor='%q'," // conductor + "grouping='%q'," // grouping + "url='%q'," // url + "bitrate=%d," // bitrate + "samplerate=%d," // samplerate + "song_length=%d," // song_length + "file_size=%d," // file_size + "year=%d," // year + "track=%d," // track + "total_tracks=%d," // total_tracks + "disc=%d," // disc + "total_discs=%d," // total_discs + "time_modified=%d," // time_modified + "db_timestamp=%d," // db_timestamp + "bpm=%d," // bpm + "disabled=%d," // disabled + "compilation=%d," // compilation + "rating=%d," // rating + "sample_count=%d," // sample_count + "codectype='%q'" // codec + " WHERE path='%q'", + STR(pmp3->title), + STR(pmp3->artist), + STR(pmp3->album), + STR(pmp3->genre), + STR(pmp3->comment), + STR(pmp3->type), + STR(pmp3->composer), + STR(pmp3->orchestra), + STR(pmp3->conductor), + STR(pmp3->grouping), + STR(pmp3->url), + pmp3->bitrate, + pmp3->samplerate, + pmp3->song_length, + pmp3->file_size, + pmp3->year, + pmp3->track, + pmp3->total_tracks, + pmp3->disc, + pmp3->total_discs, + pmp3->time_modified, + pmp3->db_timestamp, + pmp3->bpm, + pmp3->disabled, + pmp3->compilation, + pmp3->rating, + pmp3->sample_count, + STR(pmp3->codectype), + pmp3->path); + + + if(err != DB_E_SUCCESS) + DPRINTF(E_FATAL,L_DB,"Error updating file: %s\n",pmp3->fname); + + if((db_sql_in_scan) && (!db_sql_reload)) { + db_sqlite2_exec(NULL,E_FATAL,"INSERT INTO updated (id) select id from songs where path='%q'", + pmp3->path); + } + + if((!db_sql_in_scan) && (!db_sql_in_playlist_scan)) + db_sql_update_playlists(NULL); + + return 0; +} + + +/** + * Update the playlist item counts + */ +int db_sql_update_playlists(char **pe) { + typedef struct tag_plinfo { + char *plid; + char *type; + char *clause; + } PLINFO; + + PLINFO *pinfo; + int playlists; + int err; + int index; + SQL_ROW row; + char *where_clause; + + /* FIXME: There is a race here for externally added playlists */ + + err = db_sql_fetch_int(pe,&playlists,"select count(*) from playlists"); + + if(err != DB_E_SUCCESS) { + return err; + } + + pinfo = (PLINFO*)malloc(sizeof(PLINFO) * playlists); + if(!pinfo) { + DPRINTF(E_FATAL,L_DB,"Malloc error\n"); + } + + /* now, let's walk through the table */ + err = db_sqlite2_enum_begin(pe,"select * from playlistitems"); + if(err != DB_E_SUCCESS) + return err; + + /* otherwise, walk the table */ + index=0; + while((db_sqlite2_enum_fetch(pe, &row) == DB_E_SUCCESS) && (row) && + (index < playlists)) + { + /* process row */ + pinfo[index].plid=strdup(STR(row[0])); + pinfo[index].type=strdup(STR(row[1])); + pinfo[index].clause=strdup(STR(row[2])); + index++; + } + db_sqlite2_enum_end(pe); + if(index != playlists) { + DPRINTF(E_FATAL,L_DB,"Playlist count mismatch -- transaction problem?\n"); + } + + /* Now, update the playlists */ + for(index=0;index < playlists; index++) { + if(atoi(pinfo[index].type) == 1) { + /* smart */ + where_clause = db_sql_parse_smart(pinfo[index].clause); + db_sqlite2_exec(NULL,E_FATAL,"update playlists set items=(" + "select count(*) from songs where %s) " + "where id=%s",where_clause,pinfo[index].plid); + } else { + db_sqlite2_exec(NULL,E_FATAL,"update playlists set items=(" + "select count(*) from playlistitems where " + "playlistid=%s) where id=%s", + pinfo[index].plid, pinfo[index].plid); + } + + if(pinfo[index].plid) free(pinfo[index].plid); + if(pinfo[index].type) free(pinfo[index].type); + if(pinfo[index].clause) free(pinfo[index].clause); + } + + free(pinfo); + return DB_E_SUCCESS; +} + + +/** + * start enum based on the DBQUERYINFO struct passed + * + * @param pinfo DBQUERYINFO struct detailing what to enum + */ +int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) { + char scratch[4096]; + char query[4096]; + char query_select[255]; + char query_count[255]; + char query_rest[4096]; + char *where_clause; + + int is_smart; + int have_clause=0; + int err; + int browse=0; + int results=0; + SQL_ROW temprow; + + query[0] = '\0'; + query_select[0] = '\0'; + query_count[0] = '\0'; + query_rest[0] = '\0'; + + switch(pinfo->query_type) { + case queryTypeItems: + strcpy(query_select,"SELECT * FROM songs "); + strcpy(query_count,"SELECT COUNT(*) FROM songs "); + break; + + case queryTypePlaylists: + strcpy(query_select,"SELECT * FROM playlists "); + strcpy(query_count,"SELECT COUNT (*) FROM playlists "); + break; + + case queryTypePlaylistItems: /* Figure out if it's smart or dull */ + err = db_sqlite2_enum_begin(pe, "select type,query from playlists " + "where id=%d",pinfo->playlist_id); + + if(err != DB_E_SUCCESS) + return err; + + err = db_sqlite2_enum_fetch(pe,&temprow); + + if(err != DB_E_SUCCESS) { + db_sqlite2_enum_end(NULL); + return err; + } + + if(!temprow) { /* bad playlist */ + db_get_error(pe,DB_E_INVALID_PLAYLIST); + db_sqlite2_enum_end(NULL); + return DB_E_INVALID_PLAYLIST; + } + + is_smart=(atoi(temprow[0]) == 1); + have_clause=1; + if(is_smart) { + where_clause=db_sql_parse_smart(temprow[1]); + if(!where_clause) { + db_sqlite2_enum_end(NULL); + db_get_error(pe,DB_E_PARSE); + return DB_E_PARSE; + } + sprintf(query_select,"SELECT * FROM songs "); + sprintf(query_count,"SELECT COUNT(id) FROM songs "); + sprintf(query_rest,"WHERE (%s)",where_clause); + free(where_clause); + } else { + sprintf(query_count,"SELECT COUNT(id) FROM songs "); + + /* We need to fix playlist queries to stop + * pulling the whole song db... the performance + * of these playlist queries sucks. + */ +#if 1 + sprintf(query_select,"select * from songs "); + sprintf(query_rest,"where songs.id in (select songid from " + "playlistitems where playlistid=%d)", + pinfo->playlist_id); +#else + sprintf(query_select,"select * from songs,playlistitems "); + sprintf(query_rest,"where (songs.id=playlistitems.songid and " + "playlistitems.playlistid=%d) order by " + "playlistitems.id",pinfo->playlist_id); +#endif + } + + db_sqlite2_enum_end(NULL); + break; + + /* Note that sqlite doesn't support COUNT(DISTINCT x) */ + case queryTypeBrowseAlbums: + strcpy(query_select,"select distinct album from songs "); + strcpy(query_count,"select count(album) from (select " + "distinct album from songs "); + browse=1; + break; + + case queryTypeBrowseArtists: + strcpy(query_select,"select distinct artist from songs "); + strcpy(query_count,"select count(artist) from (select " + "distinct artist from songs "); + browse=1; + break; + + case queryTypeBrowseGenres: + strcpy(query_select,"select distinct genre from songs "); + strcpy(query_count,"select count(genre) from (select " + "distinct genre from songs "); + browse=1; + break; + + case queryTypeBrowseComposers: + strcpy(query_select,"select distinct composer from songs "); + strcpy(query_count,"select count(composer) from (select " + "distinct composer from songs "); + browse=1; + break; + default: + DPRINTF(E_LOG,L_DB|L_DAAP,"Unknown query type\n"); + return -1; + } + + /* Apply the query/filter */ + if(pinfo->whereclause) { + if(have_clause) + strcat(query_rest," AND "); + else + strcpy(query_rest," WHERE "); + + strcat(query_rest,"("); + strcat(query_rest,pinfo->whereclause); + strcat(query_rest,")"); + } + + + if(pinfo->index_type == indexTypeLast) { + /* We don't really care how many items unless we are + * doing a "last n items" query */ + strcpy(scratch,query_count); + strcat(scratch,query_rest); + if(browse) + strcat(scratch,")"); + + + err = db_sql_fetch_int(pe,&results,scratch); + if(err != DB_E_SUCCESS) + return err; + + DPRINTF(E_DBG,L_DB,"Number of results: %d\n",results); + } + + strcpy(query,query_select); + strcat(query,query_rest); + + /* FIXME: sqlite specific */ + /* Apply any index */ + switch(pinfo->index_type) { + case indexTypeFirst: + sprintf(scratch," LIMIT %d",pinfo->index_high); + break; + case indexTypeLast: + if(pinfo->index_low >= results) { + sprintf(scratch," LIMIT %d",pinfo->index_low); /* unnecessary */ + } else { + sprintf(scratch," LIMIT %d OFFSET %d",pinfo->index_low, results-pinfo->index_low); + } + break; + case indexTypeSub: + sprintf(scratch," LIMIT %d OFFSET %d",pinfo->index_high - pinfo->index_low, + pinfo->index_low); + break; + case indexTypeNone: + break; + default: + DPRINTF(E_LOG,L_DB,"Bad indexType: %d\n",(int)pinfo->index_type); + scratch[0]='\0'; + break; + } + + if(pinfo->index_type != indexTypeNone) + strcat(query,scratch); + + /* start fetching... */ + err=db_sqlite2_enum_begin(pe,query); + return err; +} + +/** + * find the size of the response by walking through the query and + * sizing it + * + * @returns DB_E_SUCCESS on success, error code otherwise + */ +int db_sql_enum_size(char **pe, DBQUERYINFO *pinfo, int *count, int *total_size) { + int err; + int record_size; + SQL_ROW row; + + DPRINTF(E_DBG,L_DB,"Enumerating size\n"); + + *count=0; + *total_size = 0; + + while(((err=db_sqlite2_enum_fetch(pe,&row)) == DB_E_SUCCESS) && (row)) { + if((record_size = db_sql_get_size(pinfo,row))) { + *total_size += record_size; + *count = *count + 1; + } + } + + if(err != DB_E_SUCCESS) { + db_sqlite2_enum_end(NULL); + return err; + } + + err=db_sqlite2_enum_restart(pe); + + DPRINTF(E_DBG,L_DB,"Got size: %d\n",*total_size); + return err; +} + + +/** + * fetch the next record from the enum + */ +int db_sql_enum_fetch(char **pe, DBQUERYINFO *pinfo, int *size, unsigned char **pdmap) { + int err; + int result_size=0; + unsigned char *presult; + SQL_ROW row; + + err=db_sqlite2_enum_fetch(pe, &row); + if(err != DB_E_SUCCESS) { + db_sqlite2_enum_end(NULL); + return err; + } + + if(row) { + result_size = db_sql_get_size(pinfo,row); + if(result_size) { + presult = (unsigned char*)malloc(result_size); + if(!presult) { + DPRINTF(E_FATAL,L_DB,"Malloc error\n"); + } + + db_sql_build_dmap(pinfo,row,presult,result_size); + *pdmap=presult; + *size = result_size; + } + } else { + *size = 0; + } + + return DB_E_SUCCESS; +} + +/** + * start the enum again + */ +int db_sql_enum_reset(char **pe, DBQUERYINFO *pinfo) { + return db_sqlite2_enum_restart(pe); +} + + +/** + * stop the enum + */ +int db_sql_enum_end(char **pe) { + return db_sqlite2_enum_end(pe); +} + +/** + * get the size of the generated dmap, given a specific meta + */ +int db_sql_get_size(DBQUERYINFO *pinfo, SQL_ROW valarray) { + int size; + int transcode; + + switch(pinfo->query_type) { + case queryTypeBrowseArtists: /* simple 'mlit' entry */ + case queryTypeBrowseAlbums: + case queryTypeBrowseGenres: + case queryTypeBrowseComposers: + return valarray[0] ? (8 + strlen(valarray[0])) : 0; + case queryTypePlaylists: + size = 8; /* mlit */ + size += 12; /* mimc - you get it whether you want it or not */ + if(db_wantsmeta(pinfo->meta, metaItemId)) + size += 12; /* miid */ + if(db_wantsmeta(pinfo->meta, metaItunesSmartPlaylist)) { + if(valarray[plType] && (atoi(valarray[plType])==1)) + size += 9; /* aeSP */ + } + if(db_wantsmeta(pinfo->meta, metaItemName)) + size += (8 + strlen(valarray[plTitle])); /* minm */ + if(valarray[plType] && (atoi(valarray[plType])==1) && + db_wantsmeta(pinfo->meta, metaMPlaylistSpec)) + size += (8 + strlen(valarray[plQuery])); /* MSPS */ + if(db_wantsmeta(pinfo->meta, metaMPlaylistType)) + size += 9; /* MPTY */ + return size; + break; + case queryTypeItems: + case queryTypePlaylistItems: /* essentially the same query */ + /* see if this is going to be transcoded */ + transcode = server_side_convert(valarray[37]); + + /* Items that get changed by transcode: + * + * type: item 8: changes to 'wav' + * description: item 29: changes to 'wav audio file' + * bitrate: item 15: guestimated, based on item 15, samplerate + * + * probably file size should change as well, but currently doesn't + */ + + size = 8; /* mlit */ + if(db_wantsmeta(pinfo->meta, metaItemKind)) + /* mikd */ + size += 9; + if(db_wantsmeta(pinfo->meta, metaSongDataKind)) + /* asdk */ + size += 9; + if(ISSTR(valarray[13]) && db_wantsmeta(pinfo->meta, metaSongDataURL)) + /* asul */ + size += (8 + strlen(valarray[13])); + if(ISSTR(valarray[5]) && db_wantsmeta(pinfo->meta, metaSongAlbum)) + /* asal */ + size += (8 + strlen(valarray[5])); + if(ISSTR(valarray[4]) && db_wantsmeta(pinfo->meta, metaSongArtist)) + /* asar */ + size += (8 + strlen(valarray[4])); + if(valarray[23] && atoi(valarray[23]) && db_wantsmeta(pinfo->meta, metaSongBPM)) + /* asbt */ + size += 10; + if(db_wantsmeta(pinfo->meta, metaSongBitRate)) { + /* asbr */ + if(transcode) { + if(valarray[15] && atoi(valarray[15])) + size += 10; + } else { + if(valarray[14] && atoi(valarray[14])) + size += 10; + } + } + if(ISSTR(valarray[7]) && db_wantsmeta(pinfo->meta, metaSongComment)) + /* ascm */ + size += (8 + strlen(valarray[7])); + if(valarray[24] && atoi(valarray[24]) && db_wantsmeta(pinfo->meta,metaSongCompilation)) + /* asco */ + size += 9; + if(ISSTR(valarray[9]) && db_wantsmeta(pinfo->meta, metaSongComposer)) + /* ascp */ + size += (8 + strlen(valarray[9])); + if(ISSTR(valarray[12]) && db_wantsmeta(pinfo->meta, metaSongGrouping)) + /* agrp */ + size += (8 + strlen(valarray[12])); + if(valarray[30] && atoi(valarray[30]) && db_wantsmeta(pinfo->meta, metaSongDateAdded)) + /* asda */ + size += 12; + if(valarray[31] && atoi(valarray[31]) && db_wantsmeta(pinfo->meta,metaSongDateModified)) + /* asdm */ + size += 12; + if(valarray[22] && atoi(valarray[22]) && db_wantsmeta(pinfo->meta, metaSongDiscCount)) + /* asdc */ + size += 10; + if(valarray[21] && atoi(valarray[21]) && db_wantsmeta(pinfo->meta, metaSongDiscNumber)) + size += 10; + /* asdn */ + if(ISSTR(valarray[6]) && db_wantsmeta(pinfo->meta, metaSongGenre)) + /* asgn */ + size += (8 + strlen(valarray[6])); + if(db_wantsmeta(pinfo->meta,metaItemId)) + /* miid */ + size += 12; + if(ISSTR(valarray[8]) && db_wantsmeta(pinfo->meta,metaSongFormat)) { + /* asfm */ + if(transcode) { + size += 11; /* 'wav' */ + } else { + size += (8 + strlen(valarray[8])); + } + } + if(ISSTR(valarray[29]) && db_wantsmeta(pinfo->meta,metaSongDescription)) { + /* asdt */ + if(transcode) { + size += 22; /* 'wav audio file' */ + } else { + size += (8 + strlen(valarray[29])); + } + } + if(ISSTR(valarray[3]) && db_wantsmeta(pinfo->meta,metaItemName)) + /* minm */ + size += (8 + strlen(valarray[3])); + if(valarray[34] && atoi(valarray[34]) && db_wantsmeta(pinfo->meta,metaSongDisabled)) + /* asdb */ + size += 9; + if(valarray[15] && atoi(valarray[15]) && db_wantsmeta(pinfo->meta,metaSongSampleRate)) + /* assr */ + size += 12; + if(valarray[17] && atoi(valarray[17]) && db_wantsmeta(pinfo->meta,metaSongSize)) + /* assz */ + size += 12; + + /* In the old daap code, we always returned 0 for asst and assp + * (song start time, song stop time). I don't know if this + * is required, so I'm going to disabled it + */ + + if(valarray[16] && atoi(valarray[16]) && db_wantsmeta(pinfo->meta, metaSongTime)) + /* astm */ + size += 12; + if(valarray[20] && atoi(valarray[20]) && db_wantsmeta(pinfo->meta, metaSongTrackCount)) + /* astc */ + size += 10; + if(valarray[19] && atoi(valarray[19]) && db_wantsmeta(pinfo->meta, metaSongTrackNumber)) + /* astn */ + size += 10; + if(valarray[25] && atoi(valarray[25]) && db_wantsmeta(pinfo->meta, metaSongUserRating)) + /* asur */ + size += 9; + if(valarray[18] && atoi(valarray[18]) && db_wantsmeta(pinfo->meta, metaSongYear)) + /* asyr */ + size += 10; + if(db_wantsmeta(pinfo->meta, metaContainerItemId)) + /* mcti */ + size += 12; + if(ISSTR(valarray[37]) && db_wantsmeta(pinfo->meta, metaSongCodecType)) + /* ascd */ + size += 12; + return size; + break; + + default: + DPRINTF(E_LOG,L_DB|L_DAAP,"Unknown query type: %d\n",(int)pinfo->query_type); + return 0; + } + return 0; +} + +int db_sql_build_dmap(DBQUERYINFO *pinfo, char **valarray, unsigned char *presult, int len) { + unsigned char *current = presult; + int transcode; + int samplerate=0; + + switch(pinfo->query_type) { + case queryTypeBrowseArtists: /* simple 'mlit' entry */ + case queryTypeBrowseAlbums: + case queryTypeBrowseGenres: + case queryTypeBrowseComposers: + return db_dmap_add_string(current,"mlit",valarray[0]); + case queryTypePlaylists: + /* do I want to include the mlit? */ + current += db_dmap_add_container(current,"mlit",len - 8); + if(db_wantsmeta(pinfo->meta,metaItemId)) + current += db_dmap_add_int(current,"miid",atoi(valarray[plID])); + current += db_dmap_add_int(current,"mimc",atoi(valarray[plItems])); + if(db_wantsmeta(pinfo->meta,metaItunesSmartPlaylist)) { + if(valarray[plType] && (atoi(valarray[plType]) == 1)) + current += db_dmap_add_char(current,"aeSP",1); + } + if(db_wantsmeta(pinfo->meta,metaItemName)) + current += db_dmap_add_string(current,"minm",valarray[plTitle]); + if((valarray[plType]) && (atoi(valarray[plType])==1) && + db_wantsmeta(pinfo->meta, metaMPlaylistSpec)) + current += db_dmap_add_string(current,"MSPS",valarray[plQuery]); + if(db_wantsmeta(pinfo->meta, metaMPlaylistType)) + current += db_dmap_add_char(current,"MPTY",atoi(valarray[plType])); + break; + case queryTypeItems: + case queryTypePlaylistItems: /* essentially the same query */ + /* see if this is going to be transcoded */ + transcode = server_side_convert(valarray[37]); + + /* Items that get changed by transcode: + * + * type: item 8: changes to 'wav' + * description: item 29: changes to 'wav audio file' + * bitrate: item 15: guestimated, but doesn't change file size + * + * probably file size should change as well, but currently doesn't + */ + + current += db_dmap_add_container(current,"mlit",len-8); + if(db_wantsmeta(pinfo->meta, metaItemKind)) + current += db_dmap_add_char(current,"mikd",(char)atoi(valarray[28])); + if(db_wantsmeta(pinfo->meta, metaSongDataKind)) + current += db_dmap_add_char(current,"asdk",(char)atoi(valarray[27])); + if(ISSTR(valarray[13]) && db_wantsmeta(pinfo->meta, metaSongDataURL)) + current += db_dmap_add_string(current,"asul",valarray[13]); + if(ISSTR(valarray[5]) && db_wantsmeta(pinfo->meta, metaSongAlbum)) + current += db_dmap_add_string(current,"asal",valarray[5]); + if(ISSTR(valarray[4]) && db_wantsmeta(pinfo->meta, metaSongArtist)) + current += db_dmap_add_string(current,"asar",valarray[4]); + if(valarray[23] && atoi(valarray[23]) && db_wantsmeta(pinfo->meta, metaSongBPM)) + current += db_dmap_add_short(current,"asbt",(short)atoi(valarray[23])); + if(valarray[14] && atoi(valarray[14]) && db_wantsmeta(pinfo->meta, metaSongBitRate)) { + if(transcode) { + if(valarray[15]) samplerate=atoi(valarray[15]); + if(samplerate) { + current += db_dmap_add_short(current,"asbr", + (short)(samplerate * 4 * 8)/1000); + } + } else { + current += db_dmap_add_short(current,"asbr",(short)atoi(valarray[14])); + } + } + if(ISSTR(valarray[7]) && db_wantsmeta(pinfo->meta, metaSongComment)) + current += db_dmap_add_string(current,"ascm",valarray[7]); + if(valarray[24] && atoi(valarray[24]) && db_wantsmeta(pinfo->meta,metaSongCompilation)) + current += db_dmap_add_char(current,"asco",(char)atoi(valarray[24])); + if(ISSTR(valarray[9]) && db_wantsmeta(pinfo->meta, metaSongComposer)) + current += db_dmap_add_string(current,"ascp",valarray[9]); + if(ISSTR(valarray[12]) && db_wantsmeta(pinfo->meta, metaSongGrouping)) + current += db_dmap_add_string(current,"agrp",valarray[12]); + if(valarray[30] && atoi(valarray[30]) && db_wantsmeta(pinfo->meta, metaSongDateAdded)) + current += db_dmap_add_int(current,"asda",(int)atoi(valarray[30])); + if(valarray[31] && atoi(valarray[31]) && db_wantsmeta(pinfo->meta,metaSongDateModified)) + current += db_dmap_add_int(current,"asdm",(int)atoi(valarray[31])); + if(valarray[22] && atoi(valarray[22]) && db_wantsmeta(pinfo->meta, metaSongDiscCount)) + current += db_dmap_add_short(current,"asdc",(short)atoi(valarray[22])); + if(valarray[21] && atoi(valarray[21]) && db_wantsmeta(pinfo->meta, metaSongDiscNumber)) + current += db_dmap_add_short(current,"asdn",(short)atoi(valarray[21])); + if(ISSTR(valarray[6]) && db_wantsmeta(pinfo->meta, metaSongGenre)) + current += db_dmap_add_string(current,"asgn",valarray[6]); + if(db_wantsmeta(pinfo->meta,metaItemId)) + current += db_dmap_add_int(current,"miid",(int)atoi(valarray[0])); + if(ISSTR(valarray[8]) && db_wantsmeta(pinfo->meta,metaSongFormat)) { + if(transcode) { + current += db_dmap_add_string(current,"asfm","wav"); + } else { + current += db_dmap_add_string(current,"asfm",valarray[8]); + } + } + if(ISSTR(valarray[29]) && db_wantsmeta(pinfo->meta,metaSongDescription)) { + if(transcode) { + current += db_dmap_add_string(current,"asdt","wav audio file"); + } else { + current += db_dmap_add_string(current,"asdt",valarray[29]); + } + } + if(ISSTR(valarray[3]) && db_wantsmeta(pinfo->meta,metaItemName)) + current += db_dmap_add_string(current,"minm",valarray[3]); + if(valarray[34] && atoi(valarray[34]) && db_wantsmeta(pinfo->meta,metaSongDisabled)) + current += db_dmap_add_char(current,"asdb",(char)atoi(valarray[34])); + if(valarray[15] && atoi(valarray[15]) && db_wantsmeta(pinfo->meta,metaSongSampleRate)) + current += db_dmap_add_int(current,"assr",atoi(valarray[15])); + if(valarray[17] && atoi(valarray[17]) && db_wantsmeta(pinfo->meta,metaSongSize)) + current += db_dmap_add_int(current,"assz",atoi(valarray[17])); + if(valarray[16] && atoi(valarray[16]) && db_wantsmeta(pinfo->meta, metaSongTime)) + current += db_dmap_add_int(current,"astm",atoi(valarray[16])); + if(valarray[20] && atoi(valarray[20]) && db_wantsmeta(pinfo->meta, metaSongTrackCount)) + current += db_dmap_add_short(current,"astc",(short)atoi(valarray[20])); + if(valarray[19] && atoi(valarray[19]) && db_wantsmeta(pinfo->meta, metaSongTrackNumber)) + current += db_dmap_add_short(current,"astn",(short)atoi(valarray[19])); + if(valarray[25] && atoi(valarray[25]) && db_wantsmeta(pinfo->meta, metaSongUserRating)) + current += db_dmap_add_char(current,"asur",(char)atoi(valarray[25])); + if(valarray[18] && atoi(valarray[18]) && db_wantsmeta(pinfo->meta, metaSongYear)) + current += db_dmap_add_short(current,"asyr",(short)atoi(valarray[18])); + if(ISSTR(valarray[37]) && db_wantsmeta(pinfo->meta, metaSongCodecType)) + current += db_dmap_add_literal(current,"ascd",valarray[37],4); + if(db_wantsmeta(pinfo->meta, metaContainerItemId)) + current += db_dmap_add_int(current,"mcti",atoi(valarray[0])); + return 0; + break; + + default: + DPRINTF(E_LOG,L_DB|L_DAAP,"Unknown query type: %d\n",(int)pinfo->query_type); + return 0; + } + return 0; +} + +int db_sql_atoi(const char *what) { + return what ? atoi(what) : 0; +} +char *db_sql_strdup(const char *what) { + return what ? (strlen(what) ? strdup(what) : NULL) : NULL; +} + +void db_sql_build_m3ufile(SQL_ROW valarray, M3UFILE *pm3u) { + memset(pm3u,0x00,sizeof(M3UFILE)); + + pm3u->id=db_sql_atoi(valarray[0]); + pm3u->title=db_sql_strdup(valarray[1]); + pm3u->type=db_sql_atoi(valarray[2]); + pm3u->items=db_sql_atoi(valarray[3]); + pm3u->query=db_sql_strdup(valarray[4]); + pm3u->db_timestamp=db_sql_atoi(valarray[5]); + pm3u->path=db_sql_strdup(valarray[6]); + pm3u->index=db_sql_atoi(valarray[7]); + return; +} + +void db_sql_build_mp3file(SQL_ROW valarray, MP3FILE *pmp3) { + memset(pmp3,0x00,sizeof(MP3FILE)); + pmp3->id=db_sql_atoi(valarray[0]); + pmp3->path=db_sql_strdup(valarray[1]); + pmp3->fname=db_sql_strdup(valarray[2]); + pmp3->title=db_sql_strdup(valarray[3]); + pmp3->artist=db_sql_strdup(valarray[4]); + pmp3->album=db_sql_strdup(valarray[5]); + pmp3->genre=db_sql_strdup(valarray[6]); + pmp3->comment=db_sql_strdup(valarray[7]); + pmp3->type=db_sql_strdup(valarray[8]); + pmp3->composer=db_sql_strdup(valarray[9]); + pmp3->orchestra=db_sql_strdup(valarray[10]); + pmp3->conductor=db_sql_strdup(valarray[11]); + pmp3->grouping=db_sql_strdup(valarray[12]); + pmp3->url=db_sql_strdup(valarray[13]); + pmp3->bitrate=db_sql_atoi(valarray[14]); + pmp3->samplerate=db_sql_atoi(valarray[15]); + pmp3->song_length=db_sql_atoi(valarray[16]); + pmp3->file_size=db_sql_atoi(valarray[17]); + pmp3->year=db_sql_atoi(valarray[18]); + pmp3->track=db_sql_atoi(valarray[19]); + pmp3->total_tracks=db_sql_atoi(valarray[20]); + pmp3->disc=db_sql_atoi(valarray[21]); + pmp3->total_discs=db_sql_atoi(valarray[22]); + pmp3->bpm=db_sql_atoi(valarray[23]); + pmp3->compilation=db_sql_atoi(valarray[24]); + pmp3->rating=db_sql_atoi(valarray[25]); + pmp3->play_count=db_sql_atoi(valarray[26]); + pmp3->data_kind=db_sql_atoi(valarray[27]); + pmp3->item_kind=db_sql_atoi(valarray[28]); + pmp3->description=db_sql_strdup(valarray[29]); + pmp3->time_added=db_sql_atoi(valarray[30]); + pmp3->time_modified=db_sql_atoi(valarray[31]); + pmp3->time_played=db_sql_atoi(valarray[32]); + pmp3->db_timestamp=db_sql_atoi(valarray[33]); + pmp3->disabled=db_sql_atoi(valarray[34]); + pmp3->sample_count=db_sql_atoi(valarray[35]); + pmp3->force_update=db_sql_atoi(valarray[36]); + pmp3->codectype=db_sql_strdup(valarray[37]); + pmp3->index=db_sql_atoi(valarray[38]); +} + +/** + * fetch a playlist by path and index + * + * @param path path to fetch + */ +M3UFILE *db_sql_fetch_playlist(char **pe, char *path, int index) { + int result; + M3UFILE *pm3u=NULL; + SQL_ROW row; + + result = db_sqlite2_enum_begin(pe,"select * from playlists where " + "path='%q' and idx=%d",path,index); + + if(result != DB_E_SUCCESS) + return NULL; + + result = db_sqlite2_enum_fetch(pe, &row); + if(result != DB_E_SUCCESS) { + db_sqlite2_enum_end(NULL); + return NULL; + } + + if(!row) { + db_sqlite2_enum_end(NULL); + db_get_error(pe,DB_E_INVALID_PLAYLIST); + return NULL; + } + + pm3u=(M3UFILE*)malloc(sizeof(M3UFILE)); + if(!pm3u) + DPRINTF(E_FATAL,L_MISC,"malloc error: db_sql_fetch_playlist\n"); + + db_sql_build_m3ufile(row,pm3u); + + if((db_sql_in_playlist_scan) && (!db_sql_reload)) { + db_sqlite2_exec(NULL,E_FATAL,"insert into plupdated values (%d)", + pm3u->id); + } + + db_sqlite2_enum_end(NULL); + return pm3u; +} + + +/* FIXME: bad error handling -- not like the rest */ + +/** + * fetch a MP3FILE for a specific id + * + * @param id id to fetch + */ +MP3FILE *db_sql_fetch_item(char **pe, int id) { + SQL_ROW row; + MP3FILE *pmp3=NULL; + int err; + + err=db_sql_fetch_row(pe,&row,"select * from songs where id=%d",id); + if(err != DB_E_SUCCESS) { + if(err == DB_E_NOROWS) { /* Override generic error */ + free(*pe); + db_get_error(pe,DB_E_INVALID_SONGID); + return NULL; + } + return NULL; + } + + pmp3=(MP3FILE*)malloc(sizeof(MP3FILE)); + if(!pmp3) + DPRINTF(E_FATAL,L_MISC,"Malloc error in db_sql_fetch_item\n"); + + db_sql_build_mp3file(row,pmp3); + + db_sql_dispose_row(); + + if ((db_sql_in_scan) && (!db_sql_reload)) { + db_sqlite2_exec(pe,E_FATAL,"INSERT INTO updated VALUES (%d)",id); + } + + return pmp3; +} + +/** + * retrieve a MP3FILE struct for the song with a given path + * + * @param path path of the file to retreive + */ +MP3FILE *db_sql_fetch_path(char **pe, char *path, int index) { + SQL_ROW row; + MP3FILE *pmp3=NULL; + int err; + + err=db_sql_fetch_row(pe,&row,"select * from songs where path='%q'",path); + if(err != DB_E_SUCCESS) { + if(err == DB_E_NOROWS) { /* Override generic error */ + free(*pe); + db_get_error(pe,DB_E_INVALID_SONGID); + return NULL; + } + return NULL; + } + + pmp3=(MP3FILE*)malloc(sizeof(MP3FILE)); + if(!pmp3) + DPRINTF(E_FATAL,L_MISC,"Malloc error in db_sql_fetch_path\n"); + + db_sql_build_mp3file(row,pmp3); + + db_sql_dispose_row(); + + if ((db_sql_in_scan) && (!db_sql_reload)) { + db_sqlite2_exec(pe,E_FATAL,"INSERT INTO updated VALUES (%d)",pmp3->id); + } + + return pmp3; +} + +/** + * dispose of a MP3FILE struct that was obtained + * from db_sql_fetch_item + * + * @param pmp3 item obtained from db_sql_fetch_item + */ +void db_sql_dispose_item(MP3FILE *pmp3) { + if(!pmp3) + return; + + MAYBEFREE(pmp3->path); + MAYBEFREE(pmp3->fname); + MAYBEFREE(pmp3->title); + MAYBEFREE(pmp3->artist); + MAYBEFREE(pmp3->album); + MAYBEFREE(pmp3->genre); + MAYBEFREE(pmp3->comment); + MAYBEFREE(pmp3->type); + MAYBEFREE(pmp3->composer); + MAYBEFREE(pmp3->orchestra); + MAYBEFREE(pmp3->conductor); + MAYBEFREE(pmp3->grouping); + MAYBEFREE(pmp3->description); + MAYBEFREE(pmp3->url); + MAYBEFREE(pmp3->codectype); + free(pmp3); +} + +void db_sql_dispose_playlist(M3UFILE *pm3u) { + if(!pm3u) + return; + + MAYBEFREE(pm3u->title); + MAYBEFREE(pm3u->query); + MAYBEFREE(pm3u->path); + free(pm3u); +} + +/** + * count either the number of playlists, or the number of + * songs + * + * @param type either countPlaylists or countSongs (type to count) + */ +int db_sql_get_count(char **pe, int *count, CountType_t type) { + char *table; + int err; + + switch(type) { + case countPlaylists: + table="playlists"; + break; + + case countSongs: + default: + table="songs"; + break; + } + + err=db_sql_fetch_int(pe,count,"SELECT COUNT(*) FROM '%q'", table); + return err; +} + diff --git a/src/db-sql.h b/src/db-sql.h new file mode 100644 index 00000000..53d9f7c6 --- /dev/null +++ b/src/db-sql.h @@ -0,0 +1,114 @@ +/* + * $Id$ + * sql-specific db implementation + * + * Copyright (C) 2005 Ron Pedde (ron@pedde.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _DB_SQL_H_ +#define _DB_SQL_H_ + +typedef char** SQL_ROW; + +extern int db_sql_open(char **pe, char *parameters); +extern int db_sql_init(int reload); +extern int db_sql_deinit(void); +extern int db_sql_add(char **pe, MP3FILE *pmp3); +extern int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo); +extern int db_sql_enum_size(char **pe, DBQUERYINFO *pinfo, int *count, int *total_size); +extern int db_sql_enum_fetch(char **pe, DBQUERYINFO *pinfo, int *size, unsigned char **pdmap); +extern int db_sql_enum_reset(char **pe, DBQUERYINFO *pinfo); +extern int db_sql_enum_end(char **pe); +extern int db_sql_start_scan(void); +extern int db_sql_end_song_scan(void); +extern int db_sql_end_scan(void); +extern int db_sql_get_count(char **pe, int *count, CountType_t type); +extern MP3FILE *db_sql_fetch_item(char **pe, int id); +extern MP3FILE *db_sql_fetch_path(char **pe, char *path,int index); +extern M3UFILE *db_sql_fetch_playlist(char **pe, char *path, int index); +extern void db_sql_dispose_item(MP3FILE *pmp3); +extern void db_sql_dispose_playlist(M3UFILE *pm3u); +extern int db_sql_add_playlist(char **pe, char *name, int type, char *clause, char *path, int index, int *playlistid); +extern int db_sql_add_playlist_item(char **pe, int playlistid, int songid); +extern int db_sql_edit_playlist(char **pe, int id, char *name, char *clause); +extern int db_sql_delete_playlist(char **pe, int playlistid); +extern int db_sql_delete_playlist_item(char **pe, int playlistid, int songid); + +extern int db_sql_fetch_row(char **pe, SQL_ROW *row, char *fmt, ...); +extern int db_sql_fetch_int(char **pe, int *result, char *fmt, ...); +extern int db_sql_fetch_char(char **pe, char **result, char *fmt, ...); +extern int db_sql_dispose_row(void); + +typedef enum { + songID, + songPath, + songFname, + songTitle, + songArtist, + songAlbum, + songGenre, + songComment, + songType, + songComposer, + songOrchestra, + songGrouping, + songURL, + songBitrate, + songSampleRate, + songLength, + songFilesize, + songYear, + songTrack, + songTotalTracks, + songDisc, + songTotalDiscs, + songBPM, + songCompilation, + songRating, + songPlayCount, + songDataKind, + songItemKind, + songDescription, + songTimeAdded, + songTimeModified, + songTimePlayed, + songDBTimestamp, + songDisabled, + songSampleCount, + songForceUpdate, + songCodecType +} SongField_t; + +typedef enum { + plID, + plTitle, + plType, + plItems, + plQuery, + plDBTimestamp, + plPath +} PlaylistField_t; + +#define DB_SQL_EVENT_STARTUP 0 +#define DB_SQL_EVENT_SONGSCANSTART 1 +#define DB_SQL_EVENT_SONGSCANEND 2 +#define DB_SQL_EVENT_PLSCANSTART 3 +#define DB_SQL_EVENT_PLSCANEND 4 +#define DB_SQL_EVENT_FULLRELOAD 5 + + +#endif /* _DB_SQL_H_ */ diff --git a/src/dbs-sqlite.c b/src/dbs-sqlite.c deleted file mode 100644 index 83f60a9b..00000000 --- a/src/dbs-sqlite.c +++ /dev/null @@ -1,1935 +0,0 @@ -/* - * $Id$ - * sqlite-specific db implementation - * - * Copyright (C) 2005 Ron Pedde (ron@pedde.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#define _XOPEN_SOURCE 500 - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "err.h" -#include "mp3-scanner.h" -#include "db-generic.h" -#include "dbs-sqlite.h" -#include "restart.h" -#include "ssc.h" -#include "smart-parser.h" - -/* Globals */ -static sqlite *db_sqlite_songs; /**< Database that holds the mp3 info */ -static pthread_mutex_t db_sqlite_mutex = PTHREAD_MUTEX_INITIALIZER; /**< sqlite not reentrant */ -static sqlite_vm *db_sqlite_pvm; -static int db_sqlite_in_scan=0; -static int db_sqlite_reload=0; -static int db_sqlite_in_playlist_scan=0; - -static char db_path[PATH_MAX + 1]; - -/* Forwards */ -int db_sqlite_get_size(DBQUERYINFO *pinfo, char **valarray); -int db_sqlite_build_dmap(DBQUERYINFO *pinfo, char **valarray, unsigned char *presult, int len); -void db_sqlite_build_mp3file(char **valarray, MP3FILE *pmp3); -int db_sqlite_exec(int fatal, char *fmt, ...); -int db_sqlite_get_table(int fatal, char ***resarray, int *rows, int *cols, char *fmt, ...); -int db_sqlite_free_table(char **resarray); -int db_sqlite_get_int(int loglevel, int *result, char *fmt, ...); -int db_sqlite_update(MP3FILE *pmp3); -int db_sqlite_update_version(int from_version); -int db_sqlite_get_version(void); -int db_sqlite_update_playlists(void); -char *db_sqlite_parse_smart(char *phrase); - -#define STR(a) (a) ? (a) : "" -#define ISSTR(a) ((a) && strlen((a))) -#define MAYBEFREE(a) { if((a)) free((a)); }; - -/** - * get the sql where clause for a smart playlist spec. This - * where clause must be freed by the caller - * - * @param phrase playlist spec to be converted - * @returns sql where clause if successful, NULL otherwise - */ -char *db_sqlite_parse_smart(char *phrase) { - PARSETREE pt; - char *result = NULL; - - if(strcmp(phrase,"1") == 0) - return strdup("1"); - - pt=sp_init(); - if(!pt) - return NULL; - - if(!sp_parse(pt,phrase)) { - DPRINTF(E_LOG,L_DB,"Error parsing smart playlist: %s",sp_get_error(pt)); - } else { - result = sp_sql_clause(pt); - } - - sp_dispose(pt); - return result; -} - -/** - * lock the db_mutex - */ -void db_sqlite_lock(void) { - int err; - - if((err=pthread_mutex_lock(&db_sqlite_mutex))) { - DPRINTF(E_FATAL,L_DB,"cannot lock sqlite lock: %s\n",strerror(err)); - } -} - -/** - * unlock the db_mutex - */ -int db_sqlite_unlock(void) { - return pthread_mutex_unlock(&db_sqlite_mutex); -} - -/** - * exec a simple statement - * - * \param what level to log a failure of this sql statement to exec - */ -int db_sqlite_exec(int loglevel, char *fmt, ...) { - va_list ap; - char *query; - int err; - char *perr; - - va_start(ap,fmt); - query=sqlite_vmprintf(fmt,ap); - va_end(ap); - - DPRINTF(E_DBG,L_DB,"Executing: %s\n",query); - - db_sqlite_lock(); - err=sqlite_exec(db_sqlite_songs,query,NULL,NULL,&perr); - if(err != SQLITE_OK) { - DPRINTF(loglevel == E_FATAL ? E_LOG : loglevel,L_DB,"Query: %s\n", - query); - DPRINTF(loglevel,L_DB,"Error: %s\n",perr); - sqlite_freemem(perr); - } else { - DPRINTF(E_DBG,L_DB,"Rows: %d\n",sqlite_changes(db_sqlite_songs)); - } - sqlite_freemem(query); - - db_sqlite_unlock(); - return err; -} - -/** - * get a sqlite table - * - */ -int db_sqlite_get_table(int loglevel, char ***resarray, int *rows, int *cols, char *fmt, ...) { - va_list ap; - char *query; - int err; - char *perr; - - va_start(ap,fmt); - query=sqlite_vmprintf(fmt,ap); - va_end(ap); - - DPRINTF(E_DBG,L_DB,"Executing: %s\n",query); - - db_sqlite_lock(); - err=sqlite_get_table(db_sqlite_songs,query,resarray,rows,cols,&perr); - if(err == SQLITE_OK) - sqlite_freemem(query); - db_sqlite_unlock(); - - if(err != SQLITE_OK) { - DPRINTF(loglevel == E_FATAL ? E_LOG : loglevel,L_DB,"Query: %s\n",query); - DPRINTF(loglevel,L_DB,"Error: %s\n",perr); - db_sqlite_lock(); - sqlite_freemem(query); - sqlite_freemem(perr); - db_sqlite_unlock(); - return err; - } - - return 0; -} - -int db_sqlite_free_table(char **resarray) { - db_sqlite_lock(); - sqlite_free_table(resarray); - db_sqlite_unlock(); - return 0; -} - -/** - * db_sqlite_get_int - */ -int db_sqlite_get_int(int loglevel, int *result, char *fmt, ...) { - int rows, cols; - char **resarray; - va_list ap; - char *query; - int err; - char *perr; - - va_start(ap,fmt); - query=sqlite_vmprintf(fmt,ap); - va_end(ap); - - DPRINTF(E_DBG,L_DB,"Executing: %s\n",query); - - db_sqlite_lock(); - err=sqlite_get_table(db_sqlite_songs,query,&resarray,&rows,&cols,&perr); - if(err == SQLITE_OK) - sqlite_freemem(query); - db_sqlite_unlock(); - - if(err != SQLITE_OK) { - DPRINTF(loglevel == E_FATAL ? E_LOG : loglevel,L_DB,"Query: %s\n",query); - DPRINTF(loglevel,L_DB,"Error: %s\n",perr); - db_sqlite_lock(); - sqlite_freemem(query); - sqlite_freemem(perr); - db_sqlite_unlock(); - return DB_E_SQL_ERROR; - } - - if(rows==0) { - sqlite_free_table(resarray); - return DB_E_NOROWS; - } - - *result=atoi(resarray[cols]); - - sqlite_free_table(resarray); - return DB_E_SUCCESS; -} - - -/** - * open sqlite database - */ -int db_sqlite_open(char *parameters) { - char *perr; - - snprintf(db_path,sizeof(db_path),"%s/songs.db",parameters); - - db_sqlite_lock(); - db_sqlite_songs=sqlite_open(db_path,0666,&perr); - if(!db_sqlite_songs) { - DPRINTF(E_FATAL,L_DB,"db_sqlite_open: %s (%s)\n",perr,db_path); - sqlite_freemem(perr); - } - - sqlite_busy_timeout(db_sqlite_songs,30000); /* 30 seconds */ - - db_sqlite_unlock(); - return 0; -} - -/** - * initialize the sqlite database, reloading if requested - * - * \param reload whether or not to do a full reload on the db - */ -int db_sqlite_init(int reload) { - int items; - int rescan=0; - - /* make sure we have an index... might not if aborted during scan */ - db_sqlite_exec(E_DBG,"CREATE INDEX idx_path ON songs(path)"); - db_sqlite_update_version(db_sqlite_get_version()); - db_sqlite_get_int(E_DBG,&rescan,"SELECT value FROM config WHERE term='rescan'"); - - if(rescan) - reload=1; - - items=db_sqlite_get_count(countSongs); - - if((reload) || (!items)) { - DPRINTF(E_LOG,L_DB,"Full reload...\n"); - /* this may or may not fail, depending if the index is already in place */ - db_sqlite_reload=1; - db_sqlite_exec(E_DBG,"DROP INDEX idx_path"); - db_sqlite_exec(E_FATAL,"DELETE FROM songs"); - } else { - db_sqlite_exec(E_FATAL,"VACUUM"); - } - return 0; -} - -/** - * close the database - */ -int db_sqlite_deinit(void) { - db_sqlite_lock(); - sqlite_close(db_sqlite_songs); - db_sqlite_unlock(); - - return 0; -} - - -/** - * start a background scan - */ -int db_sqlite_start_scan(void) { - DPRINTF(E_DBG,L_DB,"Starting db scan\n"); - - if(db_sqlite_reload) { - db_sqlite_exec(E_FATAL,"PRAGMA synchronous = OFF"); - db_sqlite_exec(E_FATAL,"BEGIN TRANSACTION"); - } else { - /* if not a full reload, we'll be doing update checks */ - db_sqlite_exec(E_DBG,"drop table updated"); - db_sqlite_exec(E_FATAL,"create temp table updated (id int)"); - db_sqlite_exec(E_DBG,"drop table plupdated"); - db_sqlite_exec(E_FATAL,"create temp table plupdated(id int)"); - } - - db_sqlite_in_scan=1; - db_sqlite_in_playlist_scan=0; - return 0; -} - -/** - * end song scan -- start playlist scan - */ -int db_sqlite_end_song_scan(void) { - if(db_sqlite_reload) { - db_sqlite_exec(E_FATAL,"commit transaction"); - db_sqlite_exec(E_FATAL,"create index idx_path on songs(path)"); - db_sqlite_exec(E_DBG,"delete from config where term='rescan'"); - } else { - db_sqlite_exec(E_FATAL,"delete from songs where id not in (select id from updated)"); - db_sqlite_exec(E_FATAL,"update songs set force_update=0"); - db_sqlite_exec(E_FATAL,"drop table updated"); - } - - db_sqlite_exec(E_FATAL,"begin transaction"); - - db_sqlite_in_scan=0; - db_sqlite_in_playlist_scan=1; - - return 0; -} - -/** - * stop a db scan - */ -int db_sqlite_end_scan(void) { - db_sqlite_exec(E_FATAL,"end transaction"); - - if(db_sqlite_reload) { - db_sqlite_exec(E_FATAL,"pragma synchronous=normal"); - } else { - db_sqlite_exec(E_FATAL,"delete from playlists where ((type=%d) OR (type=%d)) and " - "id not in (select id from plupdated)",PL_STATICFILE,PL_STATICXML); - db_sqlite_exec(E_FATAL,"delete from playlistitems where playlistid not in (select distinct " - "id from playlists)"); - db_sqlite_exec(E_FATAL,"drop table plupdated"); - } - - db_sqlite_update_playlists(); - db_sqlite_reload=0; - db_sqlite_in_playlist_scan=0; - - return 0; -} - -/** - * delete a playlist - * - * \param playlistid playlist to delete - */ -int db_sqlite_delete_playlist(int playlistid) { - int type; - int result; - - result=db_sqlite_get_int(E_DBG,&type,"select type from playlists where id=%d",playlistid); - if(result != DB_E_SUCCESS) { - if(result == DB_E_NOROWS) - return DB_E_INVALID_PLAYLIST; - return result; - } - - /* got a good playlist, now do what we need to do */ - db_sqlite_exec(E_FATAL,"delete from playlists where id=%d",playlistid); - db_sqlite_exec(E_FATAL,"delete from playlistitems where playlistid=%d",playlistid); - - return DB_E_SUCCESS; -} - -/** - * delete a song from a playlist - * - * \param playlistid playlist to delete item from - * \param songid song to delete from playlist - */ -int db_sqlite_delete_playlist_item(int playlistid, int songid) { - int result; - int playlist_type; - int count; - - /* first, check the playlist */ - result=db_sqlite_get_int(E_DBG,&playlist_type, - "select type from playlists where id=%d",playlistid); - - if(result != DB_E_SUCCESS) { - if(result == DB_E_NOROWS) - return DB_E_INVALID_PLAYLIST; - return result; - } - - if(playlist_type == PL_SMART) /* can't delete from a smart playlist */ - return DB_E_INVALIDTYPE; - - /* make sure the songid is valid */ - result=db_sqlite_get_int(E_DBG,&count,"select count(*) from playlistitems where playlistid=%d " - "and songid=%d",playlistid,songid); - if(result != DB_E_SUCCESS) { - if(result == DB_E_NOROWS) - return DB_E_INVALID_SONGID; - return result; - } - - /* looks valid, so lets add the item */ - result=db_sqlite_exec(E_DBG,"delete from playlistitems where playlistid=%d and songid=%d", - playlistid,songid); - return result; -} - -/** - * edit a playlist. The only things worth changing are the name - * and the "where" clause. - * - * @param id id of the playlist to alter - * @param name new name of the playlist - * @param where new where clause - */ -int db_sqlite_edit_playlist(int id, char *name, char *clause) { - int result; - - result = db_sqlite_exec(E_LOG,"update playlists set title='%q',query='%q' where id=%d", - name, clause, id); - - return result; -} - - - -/** - * add a playlist - * - * \param name name of the playlist - * \param type playlist type: 0 - static, 1 - smart, 2 - m3u - * \param clause: "where" clause for smart playlist - */ -int db_sqlite_add_playlist(char *name, int type, char *clause, char *path, int index, int *playlistid) { - int cnt=0; - int result=DB_E_SUCCESS; - char *criteria; - - db_sqlite_get_int(E_DBG,&cnt,"select count(*) from playlists where " - "upper(title)=upper('%q')",name); - - if(cnt) return DB_E_DUPLICATE_PLAYLIST; - if((type == PL_SMART) && (!clause)) return DB_E_NOCLAUSE; - - /* Let's throw it in */ - switch(type) { - case PL_STATICWEB: /* static, maintained in web interface */ - case PL_STATICFILE: /* static, from file */ - case PL_STATICXML: /* from iTunes XML file */ - result = db_sqlite_exec(E_LOG,"insert into playlists " - "(title,type,items,query,db_timestamp,path,idx) " - "values ('%q',%d,0,NULL,%d,'%q',%d)",name,type,time(NULL),path,index); - break; - case PL_SMART: /* smart */ - criteria = db_sqlite_parse_smart(clause); - if(!criteria) - return DB_E_PARSE; - result = db_sqlite_exec(E_LOG,"insert into playlists " - "(title,type,items,query,db_timestamp,idx) " - "values ('%q',%d,%d,'%q',%d,0)",name,PL_SMART,cnt,clause,time(NULL)); - free(criteria); - break; - } - - if(result) - return result; - - result = db_sqlite_get_int(E_LOG,playlistid, - "select id from playlists where title='%q'", name); - - if(((type==PL_STATICFILE)||(type==PL_STATICXML)) - && (db_sqlite_in_playlist_scan) && (!db_sqlite_reload)) { - db_sqlite_exec(E_FATAL,"insert into plupdated values (%d)",*playlistid); - } - - return result; -} - -/** - * add a song to a static playlist - * - * \param playlistid playlist to add song to - * \param songid song to add - * \returns 0 on success, otherwise a DB_E error code - */ -int db_sqlite_add_playlist_item(int playlistid, int songid) { - int result; - int playlist_type; - int count; - - /* first, check the playlist */ - result=db_sqlite_get_int(E_DBG,&playlist_type, - "select type from playlists where id=%d",playlistid); - - if(result != DB_E_SUCCESS) { - if(result == DB_E_NOROWS) - return DB_E_INVALID_PLAYLIST; - return result; - } - - if(playlist_type == 1) /* can't add to smart playlists, or static */ - return DB_E_INVALIDTYPE; - - /* make sure the songid is valid */ - result=db_sqlite_get_int(E_DBG,&count,"select count(*) from songs where id=%d",songid); - if(result != DB_E_SUCCESS) { - if(result == DB_E_NOROWS) - return DB_E_INVALID_SONGID; - return result; - } - - /* looks valid, so lets add the item */ - result=db_sqlite_exec(E_DBG,"insert into playlistitems (playlistid, songid) values (%d,%d)",playlistid,songid); - return result; -} - - -/** - * add a database item - * - * \param pmp3 mp3 file to add - */ -int db_sqlite_add(MP3FILE *pmp3) { - int err; - - DPRINTF(E_SPAM,L_DB,"Entering db_sqlite_add\n"); - - if(!pmp3->time_added) - pmp3->time_added = (int)time(NULL); - - if(!pmp3->time_modified) - pmp3->time_modified = (int)time(NULL); - - pmp3->db_timestamp = (int)time(NULL); - pmp3->play_count=0; - pmp3->time_played=0; - - err=db_sqlite_exec(E_DBG,"INSERT INTO songs VALUES " - "(NULL," // id - "'%q'," // path - "'%q'," // fname - "'%q'," // title - "'%q'," // artist - "'%q'," // album - "'%q'," // genre - "'%q'," // comment - "'%q'," // type - "'%q'," // composer - "'%q'," // orchestra - "'%q'," // conductor - "'%q'," // grouping - "'%q'," // url - "%d," // bitrate - "%d," // samplerate - "%d," // song_length - "%d," // file_size - "%d," // year - "%d," // track - "%d," // total_tracks - "%d," // disc - "%d," // total_discs - "%d," // bpm - "%d," // compilation - "%d," // rating - "0," // play_count - "%d," // data_kind - "%d," // item_kind - "'%q'," // description - "%d," // time_added - "%d," // time_modified - "%d," // time_played - "%d," // db_timestamp - "%d," // disabled - "%d," // sample_count - "0," // force_update - "'%q'," // codectype - "%d)", // index - STR(pmp3->path), - STR(pmp3->fname), - STR(pmp3->title), - STR(pmp3->artist), - STR(pmp3->album), - STR(pmp3->genre), - STR(pmp3->comment), - STR(pmp3->type), - STR(pmp3->composer), - STR(pmp3->orchestra), - STR(pmp3->conductor), - STR(pmp3->grouping), - STR(pmp3->url), - pmp3->bitrate, - pmp3->samplerate, - pmp3->song_length, - pmp3->file_size, - pmp3->year, - pmp3->track, - pmp3->total_tracks, - pmp3->disc, - pmp3->total_discs, - pmp3->bpm, - pmp3->compilation, - pmp3->rating, - pmp3->data_kind, - pmp3->item_kind, - STR(pmp3->description), - pmp3->time_added, - pmp3->time_modified, - pmp3->time_played, - pmp3->db_timestamp, - pmp3->disabled, - pmp3->sample_count, - STR(pmp3->codectype), - pmp3->index); - - if(err == SQLITE_CONSTRAINT) { - /* probably because the path already exists... */ - DPRINTF(E_DBG,L_DB,"Could not add mp3 file: %s... updating instead\n",pmp3->path); - return db_sqlite_update(pmp3); - } - - if(err != SQLITE_OK) - DPRINTF(E_FATAL,L_DB,"Error inserting file %s in database\n",pmp3->fname); - - if((db_sqlite_in_scan)&&(!db_sqlite_reload)) { - db_sqlite_exec(E_FATAL,"INSERT INTO updated VALUES (last_insert_rowid())"); - } - - if((!db_sqlite_in_scan) && (!db_sqlite_in_playlist_scan)) - db_sqlite_update_playlists(); - - DPRINTF(E_SPAM,L_DB,"Exiting db_sqlite_add\n"); - return 0; -} - -/** - * update a database item - * - * \param pmp3 mp3 file to update - */ -int db_sqlite_update(MP3FILE *pmp3) { - int err; - - if(!pmp3->time_modified) - pmp3->time_modified = (int)time(NULL); - - pmp3->db_timestamp = (int)time(NULL); - - err=db_sqlite_exec(E_FATAL,"UPDATE songs SET " - "title='%q'," // title - "artist='%q'," // artist - "album='%q'," // album - "genre='%q'," // genre - "comment='%q'," // comment - "type='%q'," // type - "composer='%q'," // composer - "orchestra='%q'," // orchestra - "conductor='%q'," // conductor - "grouping='%q'," // grouping - "url='%q'," // url - "bitrate=%d," // bitrate - "samplerate=%d," // samplerate - "song_length=%d," // song_length - "file_size=%d," // file_size - "year=%d," // year - "track=%d," // track - "total_tracks=%d," // total_tracks - "disc=%d," // disc - "total_discs=%d," // total_discs - "time_modified=%d," // time_modified - "db_timestamp=%d," // db_timestamp - "bpm=%d," // bpm - "disabled=%d," // disabled - "compilation=%d," // compilation - "rating=%d," // rating - "sample_count=%d," // sample_count - "codectype='%q'" // codec - " WHERE path='%q'", - STR(pmp3->title), - STR(pmp3->artist), - STR(pmp3->album), - STR(pmp3->genre), - STR(pmp3->comment), - STR(pmp3->type), - STR(pmp3->composer), - STR(pmp3->orchestra), - STR(pmp3->conductor), - STR(pmp3->grouping), - STR(pmp3->url), - pmp3->bitrate, - pmp3->samplerate, - pmp3->song_length, - pmp3->file_size, - pmp3->year, - pmp3->track, - pmp3->total_tracks, - pmp3->disc, - pmp3->total_discs, - pmp3->time_modified, - pmp3->db_timestamp, - pmp3->bpm, - pmp3->disabled, - pmp3->compilation, - pmp3->rating, - pmp3->sample_count, - STR(pmp3->codectype), - pmp3->path); - - if((db_sqlite_in_scan) && (!db_sqlite_reload)) { - db_sqlite_exec(E_FATAL,"INSERT INTO updated (id) select id from songs where path='%q'", - pmp3->path); - } - - if((!db_sqlite_in_scan) && (!db_sqlite_in_playlist_scan)) - db_sqlite_update_playlists(); - - return 0; -} - - -/** - * Update the playlist item counts - */ -int db_sqlite_update_playlists(void) { - char **resarray; - int rows, cols, index; - char *where_clause; - - db_sqlite_get_table(E_FATAL,&resarray, &rows, &cols, "SELECT * FROM playlists"); - - for(index=1;index <= rows; index ++) { - DPRINTF(E_DBG,L_DB,"Updating playlist counts for %s\n",resarray[cols * index + 1]); - if(atoi(resarray[cols * index + 2]) == 1) { // is a smart playlist - where_clause=db_sqlite_parse_smart(resarray[cols * index + 4]); - if(where_clause) { - db_sqlite_exec(E_FATAL,"UPDATE playlists SET items=(SELECT COUNT(*) " - "FROM songs WHERE %s) WHERE id=%s",where_clause, - resarray[cols * index]); - free(where_clause); - } - } else { - db_sqlite_exec(E_FATAL,"UPDATE playlists SET items=(SELECT COUNT(*) " - "FROM playlistitems WHERE playlistid=%s) WHERE id=%s", - resarray[cols * index], resarray[cols * index]); - } - } - - db_sqlite_free_table(resarray); - - return 0; -} - - -/** - * start enum based on the DBQUERYINFO struct passed - * - * \param pinfo DBQUERYINFO struct detailing what to enum - */ -int db_sqlite_enum_start(DBQUERYINFO *pinfo) { - char scratch[4096]; - char query[4096]; - char query_select[255]; - char query_count[255]; - char query_rest[4096]; - char *where_clause; - - int is_smart; - int have_clause=0; - int err; - char *perr; - char **resarray; - int rows, cols; - int browse=0; - int results=0; - - const char *ptail; - - query[0] = '\0'; - query_select[0] = '\0'; - query_count[0] = '\0'; - query_rest[0] = '\0'; - - switch(pinfo->query_type) { - case queryTypeItems: - strcpy(query_select,"SELECT * FROM songs "); - strcpy(query_count,"SELECT COUNT(*) FROM songs "); - break; - - case queryTypePlaylists: - strcpy(query_select,"SELECT * FROM playlists "); - strcpy(query_count,"SELECT COUNT (*) FROM playlists "); - break; - - case queryTypePlaylistItems: /* Figure out if it's smart or dull */ - db_sqlite_lock(); - sprintf(scratch,"SELECT type,query FROM playlists WHERE id=%d",pinfo->playlist_id); - DPRINTF(E_DBG,L_DB,"Executing %s\n",scratch); - err=sqlite_get_table(db_sqlite_songs,scratch,&resarray,&rows,&cols,&perr); - if(err != SQLITE_OK) { - DPRINTF(E_LOG,L_DB|L_DAAP,"Error: %s\n",perr); - sqlite_freemem(perr); - db_sqlite_unlock(); - return -1; - } - if(!rows) { - DPRINTF(E_LOG,L_DB|L_DAAP,"Could not find playlist %d\n",pinfo->playlist_id); - return -1; - } - - is_smart=(atoi(resarray[2]) == 1); - have_clause=1; - if(is_smart) { - where_clause=db_sqlite_parse_smart(resarray[3]); - if(!where_clause) { - return -1; - } - sprintf(query_select,"SELECT * FROM songs "); - sprintf(query_count,"SELECT COUNT(id) FROM songs "); - sprintf(query_rest,"WHERE (%s)",where_clause); - free(where_clause); - } else { - sprintf(query_count,"SELECT COUNT(id) FROM songs "); - - /* We need to fix playlist queries to stop - * pulling the whole song db... the performance - * of these playlist queries sucks. - */ -#if 1 - sprintf(query_select,"select * from songs "); - sprintf(query_rest,"where songs.id in (select songid from playlistitems where playlistid=%d)",pinfo->playlist_id); -#else - sprintf(query_select,"SELECT * FROM songs,playlistitems "); - sprintf(query_rest,"WHERE (songs.id=playlistitems.songid and playlistitems.playlistid=%d) ORDER BY playlistitems.id", - pinfo->playlist_id); -#endif - } - sqlite_free_table(resarray); - db_sqlite_unlock(); - break; - - /* Note that sqlite doesn't support COUNT(DISTINCT x) */ - case queryTypeBrowseAlbums: - strcpy(query_select,"SELECT DISTINCT album FROM songs "); - strcpy(query_count,"SELECT COUNT(album) FROM (SELECT DISTINCT album FROM songs "); - browse=1; - break; - - case queryTypeBrowseArtists: - strcpy(query_select,"SELECT DISTINCT artist FROM songs "); - strcpy(query_count,"SELECT COUNT(artist) FROM (SELECT DISTINCT artist FROM songs "); - browse=1; - break; - - case queryTypeBrowseGenres: - strcpy(query_select,"SELECT DISTINCT genre FROM songs "); - strcpy(query_count,"SELECT COUNT(genre) FROM (SELECT DISTINCT genre FROM songs "); - browse=1; - break; - - case queryTypeBrowseComposers: - strcpy(query_select,"SELECT DISTINCT composer FROM songs "); - strcpy(query_count,"SELECT COUNT(composer) FROM (SELECT DISTINCT composer FROM songs "); - browse=1; - break; - default: - DPRINTF(E_LOG,L_DB|L_DAAP,"Unknown query type\n"); - return -1; - } - - /* Apply the query/filter */ - if(pinfo->whereclause) { - if(have_clause) - strcat(query_rest," AND "); - else - strcpy(query_rest," WHERE "); - - strcat(query_rest,"("); - strcat(query_rest,pinfo->whereclause); - strcat(query_rest,")"); - } - - - if(pinfo->index_type == indexTypeLast) { - /* We don't really care how many items unless we are - * doing a "last n items" query */ - strcpy(scratch,query_count); - strcat(scratch,query_rest); - if(browse) - strcat(scratch,")"); - - DPRINTF(E_DBG,L_DB,"result count query: %s\n",scratch); - - db_sqlite_lock(); - err=sqlite_get_table(db_sqlite_songs,scratch,&resarray,&rows,&cols,&perr); - if(err != SQLITE_OK) { - db_sqlite_unlock(); - DPRINTF(E_LOG,L_DB,"Error in results query: %s\n",perr); - sqlite_freemem(perr); - return -1; - } - - - results=atoi(resarray[1]); - sqlite_free_table(resarray); - db_sqlite_unlock(); - - DPRINTF(E_DBG,L_DB,"Number of results: %d\n",results); - } - - strcpy(query,query_select); - strcat(query,query_rest); - - /* Apply any index */ - switch(pinfo->index_type) { - case indexTypeFirst: - sprintf(scratch," LIMIT %d",pinfo->index_high); - break; - case indexTypeLast: - if(pinfo->index_low >= results) { - sprintf(scratch," LIMIT %d",pinfo->index_low); /* unnecessary */ - } else { - sprintf(scratch," LIMIT %d OFFSET %d",pinfo->index_low, results-pinfo->index_low); - } - break; - case indexTypeSub: - sprintf(scratch," LIMIT %d OFFSET %d",pinfo->index_high - pinfo->index_low, - pinfo->index_low); - break; - case indexTypeNone: - break; - default: - DPRINTF(E_LOG,L_DB,"Bad indexType: %d\n",(int)pinfo->index_type); - scratch[0]='\0'; - break; - } - - if(pinfo->index_type != indexTypeNone) - strcat(query,scratch); - - /* start fetching... */ - db_sqlite_lock(); - err=sqlite_compile(db_sqlite_songs,query,&ptail,&db_sqlite_pvm,&perr); - db_sqlite_unlock(); - - DPRINTF(E_DBG,L_DB,"Enum query: %s\n",query); - - if(err != SQLITE_OK) { - DPRINTF(E_LOG,L_DB,"Could not compile query: %s\n",query); - sqlite_freemem(perr); - return -1; - } - - return 0; -} - -int db_sqlite_enum_size(DBQUERYINFO *pinfo, int *count) { - const char **valarray; - const char **colarray; - int err; - char *perr; - int cols; - int total_size=0; - int record_size; - - DPRINTF(E_DBG,L_DB,"Enumerating size\n"); - - *count=0; - - db_sqlite_lock(); - while((err=sqlite_step(db_sqlite_pvm,&cols,&valarray,&colarray)) == SQLITE_ROW) { - if((record_size = db_sqlite_get_size(pinfo,(char**)valarray))) { - total_size += record_size; - *count = *count + 1; - } - } - - if(err != SQLITE_DONE) { - sqlite_finalize(db_sqlite_pvm,&perr); - DPRINTF(E_FATAL,L_DB,"sqlite_step: %s\n",perr); - sqlite_freemem(perr); - db_sqlite_unlock(); - } - - db_sqlite_unlock(); - db_sqlite_enum_reset(pinfo); - - DPRINTF(E_DBG,L_DB,"Got size: %d\n",total_size); - return total_size; -} - - -/** - * fetch the next record from the enum - */ -int db_sqlite_enum_fetch(DBQUERYINFO *pinfo, unsigned char **pdmap) { - const char **valarray; - const char **colarray; - int err; - char *perr; - int cols; - int result_size=-1; - unsigned char *presult; - - db_sqlite_lock(); - err=sqlite_step(db_sqlite_pvm,&cols,&valarray,&colarray); - db_sqlite_unlock(); - - while((err == SQLITE_ROW) && (result_size)) { - result_size=db_sqlite_get_size(pinfo,(char**)valarray); - if(result_size) { - presult=(unsigned char*)malloc(result_size); - if(!presult) - return 0; - db_sqlite_build_dmap(pinfo,(char**)valarray,presult,result_size); - *pdmap = presult; - return result_size; - } - } - - if(err == SQLITE_DONE) { - return -1; - } - - db_sqlite_lock(); - sqlite_finalize(db_sqlite_pvm,&perr); - DPRINTF(E_FATAL,L_DB,"sqlite_step: %s\n",perr); - sqlite_freemem(perr); - db_sqlite_unlock(); - - return 0; -} - -/** - * start the enum again - */ -int db_sqlite_enum_reset(DBQUERYINFO *pinfo) { - db_sqlite_enum_end(); - return db_sqlite_enum_start(pinfo); -} - - -/** - * stop the enum - */ -int db_sqlite_enum_end(void) { - char *perr=NULL; - - db_sqlite_lock(); - sqlite_finalize(db_sqlite_pvm,&perr); - if(perr) { - sqlite_freemem(perr); - } - db_sqlite_unlock(); - - return 0; -} - -int db_sqlite_get_size(DBQUERYINFO *pinfo, char **valarray) { - int size; - int transcode; - - switch(pinfo->query_type) { - case queryTypeBrowseArtists: /* simple 'mlit' entry */ - case queryTypeBrowseAlbums: - case queryTypeBrowseGenres: - case queryTypeBrowseComposers: - return valarray[0] ? (8 + strlen(valarray[0])) : 0; - case queryTypePlaylists: - size = 8; /* mlit */ - size += 12; /* mimc - you get it whether you want it or not */ - if(db_wantsmeta(pinfo->meta, metaItemId)) - size += 12; /* miid */ - if(db_wantsmeta(pinfo->meta, metaItunesSmartPlaylist)) { - if(valarray[plType] && (atoi(valarray[plType])==1)) - size += 9; /* aeSP */ - } - if(db_wantsmeta(pinfo->meta, metaItemName)) - size += (8 + strlen(valarray[plTitle])); /* minm */ - if(valarray[plType] && (atoi(valarray[plType])==1) && - db_wantsmeta(pinfo->meta, metaMPlaylistSpec)) - size += (8 + strlen(valarray[plQuery])); /* MSPS */ - if(db_wantsmeta(pinfo->meta, metaMPlaylistType)) - size += 9; /* MPTY */ - return size; - break; - case queryTypeItems: - case queryTypePlaylistItems: /* essentially the same query */ - /* see if this is going to be transcoded */ - transcode = server_side_convert(valarray[37]); - - /* Items that get changed by transcode: - * - * type: item 8: changes to 'wav' - * description: item 29: changes to 'wav audio file' - * bitrate: item 15: guestimated, based on item 15, samplerate - * - * probably file size should change as well, but currently doesn't - */ - - size = 8; /* mlit */ - if(db_wantsmeta(pinfo->meta, metaItemKind)) - /* mikd */ - size += 9; - if(db_wantsmeta(pinfo->meta, metaSongDataKind)) - /* asdk */ - size += 9; - if(ISSTR(valarray[13]) && db_wantsmeta(pinfo->meta, metaSongDataURL)) - /* asul */ - size += (8 + strlen(valarray[13])); - if(ISSTR(valarray[5]) && db_wantsmeta(pinfo->meta, metaSongAlbum)) - /* asal */ - size += (8 + strlen(valarray[5])); - if(ISSTR(valarray[4]) && db_wantsmeta(pinfo->meta, metaSongArtist)) - /* asar */ - size += (8 + strlen(valarray[4])); - if(valarray[23] && atoi(valarray[23]) && db_wantsmeta(pinfo->meta, metaSongBPM)) - /* asbt */ - size += 10; - if(db_wantsmeta(pinfo->meta, metaSongBitRate)) { - /* asbr */ - if(transcode) { - if(valarray[15] && atoi(valarray[15])) - size += 10; - } else { - if(valarray[14] && atoi(valarray[14])) - size += 10; - } - } - if(ISSTR(valarray[7]) && db_wantsmeta(pinfo->meta, metaSongComment)) - /* ascm */ - size += (8 + strlen(valarray[7])); - if(valarray[24] && atoi(valarray[24]) && db_wantsmeta(pinfo->meta,metaSongCompilation)) - /* asco */ - size += 9; - if(ISSTR(valarray[9]) && db_wantsmeta(pinfo->meta, metaSongComposer)) - /* ascp */ - size += (8 + strlen(valarray[9])); - if(ISSTR(valarray[12]) && db_wantsmeta(pinfo->meta, metaSongGrouping)) - /* agrp */ - size += (8 + strlen(valarray[12])); - if(valarray[30] && atoi(valarray[30]) && db_wantsmeta(pinfo->meta, metaSongDateAdded)) - /* asda */ - size += 12; - if(valarray[31] && atoi(valarray[31]) && db_wantsmeta(pinfo->meta,metaSongDateModified)) - /* asdm */ - size += 12; - if(valarray[22] && atoi(valarray[22]) && db_wantsmeta(pinfo->meta, metaSongDiscCount)) - /* asdc */ - size += 10; - if(valarray[21] && atoi(valarray[21]) && db_wantsmeta(pinfo->meta, metaSongDiscNumber)) - size += 10; - /* asdn */ - if(ISSTR(valarray[6]) && db_wantsmeta(pinfo->meta, metaSongGenre)) - /* asgn */ - size += (8 + strlen(valarray[6])); - if(db_wantsmeta(pinfo->meta,metaItemId)) - /* miid */ - size += 12; - if(ISSTR(valarray[8]) && db_wantsmeta(pinfo->meta,metaSongFormat)) { - /* asfm */ - if(transcode) { - size += 11; /* 'wav' */ - } else { - size += (8 + strlen(valarray[8])); - } - } - if(ISSTR(valarray[29]) && db_wantsmeta(pinfo->meta,metaSongDescription)) { - /* asdt */ - if(transcode) { - size += 22; /* 'wav audio file' */ - } else { - size += (8 + strlen(valarray[29])); - } - } - if(ISSTR(valarray[3]) && db_wantsmeta(pinfo->meta,metaItemName)) - /* minm */ - size += (8 + strlen(valarray[3])); - if(valarray[34] && atoi(valarray[34]) && db_wantsmeta(pinfo->meta,metaSongDisabled)) - /* asdb */ - size += 9; - if(valarray[15] && atoi(valarray[15]) && db_wantsmeta(pinfo->meta,metaSongSampleRate)) - /* assr */ - size += 12; - if(valarray[17] && atoi(valarray[17]) && db_wantsmeta(pinfo->meta,metaSongSize)) - /* assz */ - size += 12; - - /* In the old daap code, we always returned 0 for asst and assp - * (song start time, song stop time). I don't know if this - * is required, so I'm going to disabled it - */ - - if(valarray[16] && atoi(valarray[16]) && db_wantsmeta(pinfo->meta, metaSongTime)) - /* astm */ - size += 12; - if(valarray[20] && atoi(valarray[20]) && db_wantsmeta(pinfo->meta, metaSongTrackCount)) - /* astc */ - size += 10; - if(valarray[19] && atoi(valarray[19]) && db_wantsmeta(pinfo->meta, metaSongTrackNumber)) - /* astn */ - size += 10; - if(valarray[25] && atoi(valarray[25]) && db_wantsmeta(pinfo->meta, metaSongUserRating)) - /* asur */ - size += 9; - if(valarray[18] && atoi(valarray[18]) && db_wantsmeta(pinfo->meta, metaSongYear)) - /* asyr */ - size += 10; - if(db_wantsmeta(pinfo->meta, metaContainerItemId)) - /* mcti */ - size += 12; - if(ISSTR(valarray[37]) && db_wantsmeta(pinfo->meta, metaSongCodecType)) - /* ascd */ - size += 12; - return size; - break; - - default: - DPRINTF(E_LOG,L_DB|L_DAAP,"Unknown query type: %d\n",(int)pinfo->query_type); - return 0; - } - return 0; -} - -int db_sqlite_build_dmap(DBQUERYINFO *pinfo, char **valarray, unsigned char *presult, int len) { - unsigned char *current = presult; - int transcode; - int samplerate=0; - - switch(pinfo->query_type) { - case queryTypeBrowseArtists: /* simple 'mlit' entry */ - case queryTypeBrowseAlbums: - case queryTypeBrowseGenres: - case queryTypeBrowseComposers: - return db_dmap_add_string(current,"mlit",valarray[0]); - case queryTypePlaylists: - /* do I want to include the mlit? */ - current += db_dmap_add_container(current,"mlit",len - 8); - if(db_wantsmeta(pinfo->meta,metaItemId)) - current += db_dmap_add_int(current,"miid",atoi(valarray[plID])); - current += db_dmap_add_int(current,"mimc",atoi(valarray[plItems])); - if(db_wantsmeta(pinfo->meta,metaItunesSmartPlaylist)) { - if(valarray[plType] && (atoi(valarray[plType]) == 1)) - current += db_dmap_add_char(current,"aeSP",1); - } - if(db_wantsmeta(pinfo->meta,metaItemName)) - current += db_dmap_add_string(current,"minm",valarray[plTitle]); - if((valarray[plType]) && (atoi(valarray[plType])==1) && - db_wantsmeta(pinfo->meta, metaMPlaylistSpec)) - current += db_dmap_add_string(current,"MSPS",valarray[plQuery]); - if(db_wantsmeta(pinfo->meta, metaMPlaylistType)) - current += db_dmap_add_char(current,"MPTY",atoi(valarray[plType])); - break; - case queryTypeItems: - case queryTypePlaylistItems: /* essentially the same query */ - /* see if this is going to be transcoded */ - transcode = server_side_convert(valarray[37]); - - /* Items that get changed by transcode: - * - * type: item 8: changes to 'wav' - * description: item 29: changes to 'wav audio file' - * bitrate: item 15: guestimated, but doesn't change file size - * - * probably file size should change as well, but currently doesn't - */ - - current += db_dmap_add_container(current,"mlit",len-8); - if(db_wantsmeta(pinfo->meta, metaItemKind)) - current += db_dmap_add_char(current,"mikd",(char)atoi(valarray[28])); - if(db_wantsmeta(pinfo->meta, metaSongDataKind)) - current += db_dmap_add_char(current,"asdk",(char)atoi(valarray[27])); - if(ISSTR(valarray[13]) && db_wantsmeta(pinfo->meta, metaSongDataURL)) - current += db_dmap_add_string(current,"asul",valarray[13]); - if(ISSTR(valarray[5]) && db_wantsmeta(pinfo->meta, metaSongAlbum)) - current += db_dmap_add_string(current,"asal",valarray[5]); - if(ISSTR(valarray[4]) && db_wantsmeta(pinfo->meta, metaSongArtist)) - current += db_dmap_add_string(current,"asar",valarray[4]); - if(valarray[23] && atoi(valarray[23]) && db_wantsmeta(pinfo->meta, metaSongBPM)) - current += db_dmap_add_short(current,"asbt",(short)atoi(valarray[23])); - if(valarray[14] && atoi(valarray[14]) && db_wantsmeta(pinfo->meta, metaSongBitRate)) { - if(transcode) { - if(valarray[15]) samplerate=atoi(valarray[15]); - if(samplerate) { - current += db_dmap_add_short(current,"asbr", - (short)(samplerate * 4 * 8)/1000); - } - } else { - current += db_dmap_add_short(current,"asbr",(short)atoi(valarray[14])); - } - } - if(ISSTR(valarray[7]) && db_wantsmeta(pinfo->meta, metaSongComment)) - current += db_dmap_add_string(current,"ascm",valarray[7]); - if(valarray[24] && atoi(valarray[24]) && db_wantsmeta(pinfo->meta,metaSongCompilation)) - current += db_dmap_add_char(current,"asco",(char)atoi(valarray[24])); - if(ISSTR(valarray[9]) && db_wantsmeta(pinfo->meta, metaSongComposer)) - current += db_dmap_add_string(current,"ascp",valarray[9]); - if(ISSTR(valarray[12]) && db_wantsmeta(pinfo->meta, metaSongGrouping)) - current += db_dmap_add_string(current,"agrp",valarray[12]); - if(valarray[30] && atoi(valarray[30]) && db_wantsmeta(pinfo->meta, metaSongDateAdded)) - current += db_dmap_add_int(current,"asda",(int)atoi(valarray[30])); - if(valarray[31] && atoi(valarray[31]) && db_wantsmeta(pinfo->meta,metaSongDateModified)) - current += db_dmap_add_int(current,"asdm",(int)atoi(valarray[31])); - if(valarray[22] && atoi(valarray[22]) && db_wantsmeta(pinfo->meta, metaSongDiscCount)) - current += db_dmap_add_short(current,"asdc",(short)atoi(valarray[22])); - if(valarray[21] && atoi(valarray[21]) && db_wantsmeta(pinfo->meta, metaSongDiscNumber)) - current += db_dmap_add_short(current,"asdn",(short)atoi(valarray[21])); - if(ISSTR(valarray[6]) && db_wantsmeta(pinfo->meta, metaSongGenre)) - current += db_dmap_add_string(current,"asgn",valarray[6]); - if(db_wantsmeta(pinfo->meta,metaItemId)) - current += db_dmap_add_int(current,"miid",(int)atoi(valarray[0])); - if(ISSTR(valarray[8]) && db_wantsmeta(pinfo->meta,metaSongFormat)) { - if(transcode) { - current += db_dmap_add_string(current,"asfm","wav"); - } else { - current += db_dmap_add_string(current,"asfm",valarray[8]); - } - } - if(ISSTR(valarray[29]) && db_wantsmeta(pinfo->meta,metaSongDescription)) { - if(transcode) { - current += db_dmap_add_string(current,"asdt","wav audio file"); - } else { - current += db_dmap_add_string(current,"asdt",valarray[29]); - } - } - if(ISSTR(valarray[3]) && db_wantsmeta(pinfo->meta,metaItemName)) - current += db_dmap_add_string(current,"minm",valarray[3]); - if(valarray[34] && atoi(valarray[34]) && db_wantsmeta(pinfo->meta,metaSongDisabled)) - current += db_dmap_add_char(current,"asdb",(char)atoi(valarray[34])); - if(valarray[15] && atoi(valarray[15]) && db_wantsmeta(pinfo->meta,metaSongSampleRate)) - current += db_dmap_add_int(current,"assr",atoi(valarray[15])); - if(valarray[17] && atoi(valarray[17]) && db_wantsmeta(pinfo->meta,metaSongSize)) - current += db_dmap_add_int(current,"assz",atoi(valarray[17])); - if(valarray[16] && atoi(valarray[16]) && db_wantsmeta(pinfo->meta, metaSongTime)) - current += db_dmap_add_int(current,"astm",atoi(valarray[16])); - if(valarray[20] && atoi(valarray[20]) && db_wantsmeta(pinfo->meta, metaSongTrackCount)) - current += db_dmap_add_short(current,"astc",(short)atoi(valarray[20])); - if(valarray[19] && atoi(valarray[19]) && db_wantsmeta(pinfo->meta, metaSongTrackNumber)) - current += db_dmap_add_short(current,"astn",(short)atoi(valarray[19])); - if(valarray[25] && atoi(valarray[25]) && db_wantsmeta(pinfo->meta, metaSongUserRating)) - current += db_dmap_add_char(current,"asur",(char)atoi(valarray[25])); - if(valarray[18] && atoi(valarray[18]) && db_wantsmeta(pinfo->meta, metaSongYear)) - current += db_dmap_add_short(current,"asyr",(short)atoi(valarray[18])); - if(ISSTR(valarray[37]) && db_wantsmeta(pinfo->meta, metaSongCodecType)) - current += db_dmap_add_literal(current,"ascd",valarray[37],4); - if(db_wantsmeta(pinfo->meta, metaContainerItemId)) - current += db_dmap_add_int(current,"mcti",atoi(valarray[0])); - return 0; - break; - - default: - DPRINTF(E_LOG,L_DB|L_DAAP,"Unknown query type: %d\n",(int)pinfo->query_type); - return 0; - } - return 0; -} - -int db_sqlite_atoi(const char *what) { - return what ? atoi(what) : 0; -} -char *db_sqlite_strdup(const char *what) { - return what ? (strlen(what) ? strdup(what) : NULL) : NULL; -} - -void db_sqlite_build_m3ufile(char **valarray, M3UFILE *pm3u) { - memset(pm3u,0x00,sizeof(M3UFILE)); - - pm3u->id=db_sqlite_atoi(valarray[0]); - pm3u->title=db_sqlite_strdup(valarray[1]); - pm3u->type=db_sqlite_atoi(valarray[2]); - pm3u->items=db_sqlite_atoi(valarray[3]); - pm3u->query=db_sqlite_strdup(valarray[4]); - pm3u->db_timestamp=db_sqlite_atoi(valarray[5]); - pm3u->path=db_sqlite_strdup(valarray[6]); - pm3u->index=db_sqlite_atoi(valarray[7]); - return; -} - -void db_sqlite_build_mp3file(char **valarray, MP3FILE *pmp3) { - memset(pmp3,0x00,sizeof(MP3FILE)); - pmp3->id=db_sqlite_atoi(valarray[0]); - pmp3->path=db_sqlite_strdup(valarray[1]); - pmp3->fname=db_sqlite_strdup(valarray[2]); - pmp3->title=db_sqlite_strdup(valarray[3]); - pmp3->artist=db_sqlite_strdup(valarray[4]); - pmp3->album=db_sqlite_strdup(valarray[5]); - pmp3->genre=db_sqlite_strdup(valarray[6]); - pmp3->comment=db_sqlite_strdup(valarray[7]); - pmp3->type=db_sqlite_strdup(valarray[8]); - pmp3->composer=db_sqlite_strdup(valarray[9]); - pmp3->orchestra=db_sqlite_strdup(valarray[10]); - pmp3->conductor=db_sqlite_strdup(valarray[11]); - pmp3->grouping=db_sqlite_strdup(valarray[12]); - pmp3->url=db_sqlite_strdup(valarray[13]); - pmp3->bitrate=db_sqlite_atoi(valarray[14]); - pmp3->samplerate=db_sqlite_atoi(valarray[15]); - pmp3->song_length=db_sqlite_atoi(valarray[16]); - pmp3->file_size=db_sqlite_atoi(valarray[17]); - pmp3->year=db_sqlite_atoi(valarray[18]); - pmp3->track=db_sqlite_atoi(valarray[19]); - pmp3->total_tracks=db_sqlite_atoi(valarray[20]); - pmp3->disc=db_sqlite_atoi(valarray[21]); - pmp3->total_discs=db_sqlite_atoi(valarray[22]); - pmp3->bpm=db_sqlite_atoi(valarray[23]); - pmp3->compilation=db_sqlite_atoi(valarray[24]); - pmp3->rating=db_sqlite_atoi(valarray[25]); - pmp3->play_count=db_sqlite_atoi(valarray[26]); - pmp3->data_kind=db_sqlite_atoi(valarray[27]); - pmp3->item_kind=db_sqlite_atoi(valarray[28]); - pmp3->description=db_sqlite_strdup(valarray[29]); - pmp3->time_added=db_sqlite_atoi(valarray[30]); - pmp3->time_modified=db_sqlite_atoi(valarray[31]); - pmp3->time_played=db_sqlite_atoi(valarray[32]); - pmp3->db_timestamp=db_sqlite_atoi(valarray[33]); - pmp3->disabled=db_sqlite_atoi(valarray[34]); - pmp3->sample_count=db_sqlite_atoi(valarray[35]); - pmp3->force_update=db_sqlite_atoi(valarray[36]); - pmp3->codectype=db_sqlite_strdup(valarray[37]); - pmp3->index=db_sqlite_atoi(valarray[38]); -} - -/** - * fetch a playlist by path - * - * \param path path to fetch - */ -M3UFILE *db_sqlite_fetch_playlist(char *path, int index) { - int rows,cols; - char **resarray; - int result; - M3UFILE *pm3u=NULL; - - result = db_sqlite_get_table(E_DBG,&resarray,&rows,&cols, - "select * from playlists where path='%q' and idx=%d", - path,index); - - if(result != DB_E_SUCCESS) - return NULL; - - if(rows != 0) { - pm3u=(M3UFILE*)malloc(sizeof(M3UFILE)); - if(!pm3u) - DPRINTF(E_FATAL,L_MISC,"malloc error: db_sqlite_fetch_playlist\n"); - - db_sqlite_build_m3ufile((char**)&resarray[cols],pm3u); - } - - db_sqlite_free_table(resarray); - - if((rows) && (db_sqlite_in_playlist_scan) && (!db_sqlite_reload)) { - db_sqlite_exec(E_FATAL,"insert into plupdated values (%d)",pm3u->id); - } - - return pm3u; -} - -/** - * fetch a MP3FILE for a specific id - * - * \param id id to fetch - */ -MP3FILE *db_sqlite_fetch_item(int id) { - int rows,cols; - char **resarray; - MP3FILE *pmp3=NULL; - - db_sqlite_get_table(E_DBG,&resarray,&rows,&cols, - "SELECT * FROM songs WHERE id=%d",id); - - if(rows != 0) { - pmp3=(MP3FILE*)malloc(sizeof(MP3FILE)); - if(!pmp3) - DPRINTF(E_FATAL,L_MISC,"Malloc error in db_sqlite_fetch_item\n"); - - db_sqlite_build_mp3file((char **)&resarray[cols],pmp3); - } - - db_sqlite_free_table(resarray); - - if ((rows) && (db_sqlite_in_scan) && (!db_sqlite_reload)) { - db_sqlite_exec(E_FATAL,"INSERT INTO updated VALUES (%d)",id); - } - - return pmp3; -} - -/** - * retrieve a MP3FILE struct for the song with a give path - * - * \param path path of the file to retreive - */ -MP3FILE *db_sqlite_fetch_path(char *path, int index) { - int rows,cols; - char **resarray; - MP3FILE *pmp3=NULL; - - db_sqlite_get_table(E_DBG,&resarray,&rows,&cols, - "SELECT * FROM songs WHERE path='%q' and idx=%d", - path,index); - - if(rows != 0) { - pmp3=(MP3FILE*)malloc(sizeof(MP3FILE)); - if(!pmp3) - DPRINTF(E_FATAL,L_MISC,"Malloc error in db_sqlite_fetch_item\n"); - - db_sqlite_build_mp3file((char **)&resarray[cols],pmp3); - } - - db_sqlite_free_table(resarray); - - if ((rows) && (db_sqlite_in_scan) && (!db_sqlite_reload)) { - db_sqlite_exec(E_FATAL,"INSERT INTO updated VALUES (%d)",pmp3->id); - } - - return pmp3; -} - -/** - * dispose of a MP3FILE struct that was obtained - * from db_sqlite_fetch_item - * - * \param pmp3 item obtained from db_sqlite_fetch_item - */ -void db_sqlite_dispose_item(MP3FILE *pmp3) { - if(!pmp3) - return; - - MAYBEFREE(pmp3->path); - MAYBEFREE(pmp3->fname); - MAYBEFREE(pmp3->title); - MAYBEFREE(pmp3->artist); - MAYBEFREE(pmp3->album); - MAYBEFREE(pmp3->genre); - MAYBEFREE(pmp3->comment); - MAYBEFREE(pmp3->type); - MAYBEFREE(pmp3->composer); - MAYBEFREE(pmp3->orchestra); - MAYBEFREE(pmp3->conductor); - MAYBEFREE(pmp3->grouping); - MAYBEFREE(pmp3->description); - MAYBEFREE(pmp3->url); - MAYBEFREE(pmp3->codectype); - free(pmp3); -} - -void db_sqlite_dispose_playlist(M3UFILE *pm3u) { - if(!pm3u) - return; - - MAYBEFREE(pm3u->title); - MAYBEFREE(pm3u->query); - MAYBEFREE(pm3u->path); - free(pm3u); -} - -/** - * count either the number of playlists, or the number of - * songs - * - * \param type either countPlaylists or countSongs (type to count) - */ -int db_sqlite_get_count(CountType_t type) { - char *table; - int rows, cols; - char **resarray; - int retval=0; - - switch(type) { - case countPlaylists: - table="playlists"; - break; - - case countSongs: - default: - table="songs"; - break; - } - - db_sqlite_get_table(E_DBG,&resarray, &rows, &cols, - "SELECT COUNT(*) FROM %q", table); - - if(rows != 0) { - retval=atoi(resarray[cols]); - } - - db_sqlite_free_table(resarray); - return retval; -} - -/** - * get the database version of the currently opened database - */ -int db_sqlite_get_version(void) { - int rows, cols; - char **resarray; - int retval=0; - - db_sqlite_get_table(E_DBG,&resarray, &rows, &cols, - "select value from config where term='version'"); - - if(rows != 0) { - retval=atoi(resarray[cols]); - } - - db_sqlite_free_table(resarray); - return retval; -} - - -char *db_sqlite_upgrade_scripts[] = { - /* version 0 -> version 1 -- initial update */ - "CREATE TABLE songs (\n" - " id INTEGER PRIMARY KEY NOT NULL,\n" - " path VARCHAR(4096) UNIQUE NOT NULL,\n" - " fname VARCHAR(255) NOT NULL,\n" - " title VARCHAR(1024) DEFAULT NULL,\n" - " artist VARCHAR(1024) DEFAULT NULL,\n" - " album VARCHAR(1024) DEFAULT NULL,\n" - " genre VARCHAR(255) DEFAULT NULL,\n" - " comment VARCHAR(4096) DEFAULT NULL,\n" - " type VARCHAR(255) DEFAULT NULL,\n" - " composer VARCHAR(1024) DEFAULT NULL,\n" - " orchestra VARCHAR(1024) DEFAULT NULL,\n" - " conductor VARCHAR(1024) DEFAULT NULL,\n" - " grouping VARCHAR(1024) DEFAULT NULL,\n" - " url VARCHAR(1024) DEFAULT NULL,\n" - " bitrate INTEGER DEFAULT 0,\n" - " samplerate INTEGER DEFAULT 0,\n" - " song_length INTEGER DEFAULT 0,\n" - " file_size INTEGER DEFAULT 0,\n" - " year INTEGER DEFAULT 0,\n" - " track INTEGER DEFAULT 0,\n" - " total_tracks INTEGER DEFAULT 0,\n" - " disc INTEGER DEFAULT 0,\n" - " total_discs INTEGER DEFAULT 0,\n" - " bpm INTEGER DEFAULT 0,\n" - " compilation INTEGER DEFAULT 0,\n" - " rating INTEGER DEFAULT 0,\n" - " play_count INTEGER DEFAULT 0,\n" - " data_kind INTEGER DEFAULT 0,\n" - " item_kind INTEGER DEFAULT 0,\n" - " description INTEGER DEFAULT 0,\n" - " time_added INTEGER DEFAULT 0,\n" - " time_modified INTEGER DEFAULT 0,\n" - " time_played INTEGER DEFAULT 0,\n" - " db_timestamp INTEGER DEFAULT 0,\n" - " disabled INTEGER DEFAULT 0,\n" - " sample_count INTEGER DEFAULT 0,\n" - " force_update INTEGER DEFAULT 0\n" - ");\n" - "CREATE INDEX idx_path ON songs(path);\n" - "CREATE TABLE config (\n" - " term VARCHAR(255) NOT NULL,\n" - " subterm VARCHAR(255) DEFAULT NULL,\n" - " value VARCHAR(1024) NOT NULL\n" - ");\n" - "CREATE TABLE playlists (\n" - " id INTEGER PRIMARY KEY NOT NULL,\n" - " title VARCHAR(255) NOT NULL,\n" - " smart INTEGER NOT NULL,\n" - " items INTEGER NOT NULL,\n" - " query VARCHAR(1024)\n" - ");\n" - "CREATE TABLE playlistitems (\n" - " id INTEGER NOT NULL,\n" - " songid INTEGER NOT NULL\n" - ");\n" - "INSERT INTO config VALUES ('version','','1');\n" - "INSERT INTO playlists VALUES (1,'Library',1,0,'1');\n", - - /* version 1 -> version 2 */ - /* force rescan for invalid utf-8 data */ - "REPLACE INTO config VALUES('rescan',NULL,1);\n" - "UPDATE config SET value=2 WHERE term='version';\n", - - /* version 2 -> version 3 */ - /* add daap.songcodectype, normalize daap.songformat and daap.songdescription */ - "drop index idx_path;\n" - "create temp table tempsongs as select * from songs;\n" - "drop table songs;\n" - "CREATE TABLE songs (\n" - " id INTEGER PRIMARY KEY NOT NULL,\n" - " path VARCHAR(4096) UNIQUE NOT NULL,\n" - " fname VARCHAR(255) NOT NULL,\n" - " title VARCHAR(1024) DEFAULT NULL,\n" - " artist VARCHAR(1024) DEFAULT NULL,\n" - " album VARCHAR(1024) DEFAULT NULL,\n" - " genre VARCHAR(255) DEFAULT NULL,\n" - " comment VARCHAR(4096) DEFAULT NULL,\n" - " type VARCHAR(255) DEFAULT NULL,\n" - " composer VARCHAR(1024) DEFAULT NULL,\n" - " orchestra VARCHAR(1024) DEFAULT NULL,\n" - " conductor VARCHAR(1024) DEFAULT NULL,\n" - " grouping VARCHAR(1024) DEFAULT NULL,\n" - " url VARCHAR(1024) DEFAULT NULL,\n" - " bitrate INTEGER DEFAULT 0,\n" - " samplerate INTEGER DEFAULT 0,\n" - " song_length INTEGER DEFAULT 0,\n" - " file_size INTEGER DEFAULT 0,\n" - " year INTEGER DEFAULT 0,\n" - " track INTEGER DEFAULT 0,\n" - " total_tracks INTEGER DEFAULT 0,\n" - " disc INTEGER DEFAULT 0,\n" - " total_discs INTEGER DEFAULT 0,\n" - " bpm INTEGER DEFAULT 0,\n" - " compilation INTEGER DEFAULT 0,\n" - " rating INTEGER DEFAULT 0,\n" - " play_count INTEGER DEFAULT 0,\n" - " data_kind INTEGER DEFAULT 0,\n" - " item_kind INTEGER DEFAULT 0,\n" - " description INTEGER DEFAULT 0,\n" - " time_added INTEGER DEFAULT 0,\n" - " time_modified INTEGER DEFAULT 0,\n" - " time_played INTEGER DEFAULT 0,\n" - " db_timestamp INTEGER DEFAULT 0,\n" - " disabled INTEGER DEFAULT 0,\n" - " sample_count INTEGER DEFAULT 0,\n" - " force_update INTEGER DEFAULT 0,\n" - " codectype VARCHAR(5) DEFAULT NULL\n" - ");\n" - "begin transaction;\n" - "insert into songs select *,NULL from tempsongs;\n" - "commit transaction;\n" - "update songs set type=lower(type);\n" - "update songs set type='m4a' where type='aac' or type='mp4';\n" - "update songs set type='flac' where type='fla';\n" - "update songs set type='mpc' where type='mpp';\n" - "update songs set type='mpc' where type='mp+';\n" - "update songs set description='AAC audio file' where type='m4a';\n" - "update songs set description='MPEG audio file' where type='mp3';\n" - "update songs set description='WAV audio file' where type='wav';\n" - "update songs set description='Playlist URL' where type='pls';\n" - "update songs set description='Ogg Vorbis audio file' where type='ogg';\n" - "update songs set description='FLAC audio file' where type='flac';\n" - "update songs set description='Musepack audio file' where type='mpc';\n" - "update songs set codectype='mp4a' where type='m4a' or type='m4p';\n" - "update songs set codectype='mpeg' where type='mp3';\n" - "update songs set codectype='ogg' where type='ogg';\n" - "update songs set codectype='flac' where type='flac';\n" - "update songs set codectype='mpc' where type='mpc';\n" - "update songs set force_update=1 where type='m4a';\n" /* look for alac */ - "create index idx_path on songs(path);\n" - "drop table tempsongs;\n" - "update config set value=3 where term='version';\n", - - /* version 3 -> version 4 */ - /* add db_timestamp and path to playlist table */ - "create temp table tempplaylists as select * from playlists;\n" - "drop table playlists;\n" - "CREATE TABLE playlists (\n" - " id INTEGER PRIMARY KEY NOT NULL,\n" - " title VARCHAR(255) NOT NULL,\n" - " type INTEGER NOT NULL,\n" - " items INTEGER NOT NULL,\n" - " query VARCHAR(1024),\n" - " db_timestamp INTEGER NOT NULL,\n" - " path VARCHAR(4096)\n" - ");\n" - "insert into playlists select *,0,NULL from tempplaylists;\n" - "drop table tempplaylists;\n" - "update config set value=4 where term='version';\n", - - /* version 4 -> version 5 */ - /* add index to playlist table */ - "create temp table tempplaylists as select * from playlists;\n" - "drop table playlists;\n" - "CREATE TABLE playlists (\n" - " id INTEGER PRIMARY KEY NOT NULL,\n" - " title VARCHAR(255) NOT NULL,\n" - " type INTEGER NOT NULL,\n" - " items INTEGER NOT NULL,\n" - " query VARCHAR(1024),\n" - " db_timestamp INTEGER NOT NULL,\n" - " path VARCHAR(4096),\n" - " idx INTEGER NOT NULL\n" - ");\n" - "insert into playlists select *,0 from tempplaylists;\n" - "drop table tempplaylists;\n" - "update config set value=5 where term='version';\n", - - /* version 5 -> version 6 */ - "drop index idx_path;\n" - "create temp table tempsongs as select * from songs;\n" - "drop table songs;\n" - "CREATE TABLE songs (\n" - " id INTEGER PRIMARY KEY NOT NULL,\n" - " path VARCHAR(4096) UNIQUE NOT NULL,\n" - " fname VARCHAR(255) NOT NULL,\n" - " title VARCHAR(1024) DEFAULT NULL,\n" - " artist VARCHAR(1024) DEFAULT NULL,\n" - " album VARCHAR(1024) DEFAULT NULL,\n" - " genre VARCHAR(255) DEFAULT NULL,\n" - " comment VARCHAR(4096) DEFAULT NULL,\n" - " type VARCHAR(255) DEFAULT NULL,\n" - " composer VARCHAR(1024) DEFAULT NULL,\n" - " orchestra VARCHAR(1024) DEFAULT NULL,\n" - " conductor VARCHAR(1024) DEFAULT NULL,\n" - " grouping VARCHAR(1024) DEFAULT NULL,\n" - " url VARCHAR(1024) DEFAULT NULL,\n" - " bitrate INTEGER DEFAULT 0,\n" - " samplerate INTEGER DEFAULT 0,\n" - " song_length INTEGER DEFAULT 0,\n" - " file_size INTEGER DEFAULT 0,\n" - " year INTEGER DEFAULT 0,\n" - " track INTEGER DEFAULT 0,\n" - " total_tracks INTEGER DEFAULT 0,\n" - " disc INTEGER DEFAULT 0,\n" - " total_discs INTEGER DEFAULT 0,\n" - " bpm INTEGER DEFAULT 0,\n" - " compilation INTEGER DEFAULT 0,\n" - " rating INTEGER DEFAULT 0,\n" - " play_count INTEGER DEFAULT 0,\n" - " data_kind INTEGER DEFAULT 0,\n" - " item_kind INTEGER DEFAULT 0,\n" - " description INTEGER DEFAULT 0,\n" - " time_added INTEGER DEFAULT 0,\n" - " time_modified INTEGER DEFAULT 0,\n" - " time_played INTEGER DEFAULT 0,\n" - " db_timestamp INTEGER DEFAULT 0,\n" - " disabled INTEGER DEFAULT 0,\n" - " sample_count INTEGER DEFAULT 0,\n" - " force_update INTEGER DEFAULT 0,\n" - " codectype VARCHAR(5) DEFAULT NULL,\n" - " idx INTEGER NOT NULL\n" - ");\n" - "begin transaction;\n" - "insert into songs select *,0 from tempsongs;\n" - "commit transaction;\n" - "create index idx_path on songs(path);\n" - "drop table tempsongs;\n" - "update config set value=6 where term='version';\n", - - /* version 6 -> version 7 */ - "create temp table tempitems as select * from playlistitems;\n" - "drop table playlistitems;\n" - "CREATE TABLE playlistitems (\n" - " id INTEGER PRIMARY KEY NOT NULL,\n" - " playlistid INTEGER NOT NULL,\n" - " songid INTEGER NOT NULL\n" - ");\n" - "insert into playlistitems (playlistid, songid) select * from tempitems;\n" - "drop table tempitems;\n" - "update config set value=7 where term='version';\n", - - /* version 7 -> version 8 */ - "create index idx_songid on playlistitems(songid);\n" - "create index idx_playlistid on playlistitems(playlistid);\n" - "update config set value=8 where term='version';\n", - - NULL /* No more versions! */ -}; - -/** - * Upgrade database from an older version of the database to the newest - * - * \param from_version the current version of the database - */ -int db_sqlite_update_version(int from_version) { - char db_new_path[PATH_MAX + 1]; - int from_fd, to_fd; - int copied=0; - int result; - - if(from_version > (sizeof(db_sqlite_upgrade_scripts)/sizeof(char*))) { - DPRINTF(E_FATAL,L_DB,"Database version too new (time machine, maybe?)\n"); - } - - while(db_sqlite_upgrade_scripts[from_version]) { - DPRINTF(E_LOG,L_DB,"Upgrading database from version %d to version %d\n",from_version, - from_version+1); - - if(!copied) { - /* copy original version */ - sprintf(db_new_path,"%s.version-%02d",db_path,from_version); - from_fd=r_open2(db_path,O_RDONLY); - to_fd=r_open3(db_new_path,O_RDWR | O_CREAT,0666); - - if((from_fd == -1) || (to_fd == -1)) { - DPRINTF(E_FATAL,L_DB,"Could not make backup copy of database " - "(%s). Check write permissions for runas user.\n", - db_new_path); - } - - while((result=readwrite(from_fd, to_fd) > 0)); - - if(result == -1) { - DPRINTF(E_FATAL,L_DB,"Could not make db backup (%s)\n", - strerror(errno)); - } - - r_close(from_fd); - r_close(to_fd); - - copied=1; - } - - if(db_sqlite_exec(E_LOG,db_sqlite_upgrade_scripts[from_version]) != DB_E_SUCCESS) { - DPRINTF(E_FATAL,L_DB,"Error upgrading database. A backup copy of " - "you original database is located at %s. Please save it " - " somewhere and report to the forums at www.mt-daapd.org. " - " Thanks.\n", - db_new_path); - } - from_version++; - } - - /* removed our backup file */ - if(copied) - unlink(db_new_path); - - return 0; -} - diff --git a/src/dbs-sqlite.h b/src/dbs-sqlite.h deleted file mode 100644 index 3db1aefe..00000000 --- a/src/dbs-sqlite.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * $Id$ - * sqlite-specific db implementation - * - * Copyright (C) 2005 Ron Pedde (ron@pedde.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _DBS_SQLITE_H_ -#define _DBS_SQLITE_H_ - -extern int db_sqlite_open(char *parameters); -extern int db_sqlite_init(int reload); -extern int db_sqlite_deinit(void); -extern int db_sqlite_add(MP3FILE *pmp3); -extern int db_sqlite_enum_start(DBQUERYINFO *pinfo); -extern int db_sqlite_enum_size(DBQUERYINFO *pinfo, int *count); -extern int db_sqlite_enum_fetch(DBQUERYINFO *pinfo, unsigned char **pdmap); -extern int db_sqlite_enum_reset(DBQUERYINFO *pinfo); -extern int db_sqlite_enum_end(void); -extern int db_sqlite_start_scan(void); -extern int db_sqlite_end_song_scan(void); -extern int db_sqlite_end_scan(void); -extern int db_sqlite_get_count(CountType_t type); -extern MP3FILE *db_sqlite_fetch_item(int id); -extern MP3FILE *db_sqlite_fetch_path(char *path,int index); -extern M3UFILE *db_sqlite_fetch_playlist(char *path, int index); -extern void db_sqlite_dispose_item(MP3FILE *pmp3); -extern void db_sqlite_dispose_playlist(M3UFILE *pm3u); -extern int db_sqlite_add_playlist(char *name, int type, char *clause, char *path, int index, int *playlistid); -extern int db_sqlite_add_playlist_item(int playlistid, int songid); -extern int db_sqlite_edit_playlist(int id, char *name, char *clause); -extern int db_sqlite_delete_playlist(int playlistid); -extern int db_sqlite_delete_playlist_item(int playlistid, int songid); - -typedef enum { - songID, - songPath, - songFname, - songTitle, - songArtist, - songAlbum, - songGenre, - songComment, - songType, - songComposer, - songOrchestra, - songGrouping, - songURL, - songBitrate, - songSampleRate, - songLength, - songFilesize, - songYear, - songTrack, - songTotalTracks, - songDisc, - songTotalDiscs, - songBPM, - songCompilation, - songRating, - songPlayCount, - songDataKind, - songItemKind, - songDescription, - songTimeAdded, - songTimeModified, - songTimePlayed, - songDBTimestamp, - songDisabled, - songSampleCount, - songForceUpdate, - songCodecType -} SongField_t; - -typedef enum { - plID, - plTitle, - plType, - plItems, - plQuery, - plDBTimestamp, - plPath -} PlaylistField_t; - - -#endif /* _DBS_SQLITE_H_ */ diff --git a/src/dispatch.c b/src/dispatch.c index 4cd0c6b0..303ead69 100644 --- a/src/dispatch.c +++ b/src/dispatch.c @@ -70,7 +70,7 @@ static char *dispatch_xml_encode(char *original, int len); static int dispatch_output_xml_write(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, unsigned char *block, int len); -/** +/** * Hold the inf for the output serializer */ typedef struct tag_xml_stack { @@ -90,11 +90,11 @@ typedef struct tag_output_info { /** * Handles authentication for the daap server. This isn't the - * authenticator for the web admin page, but rather the iTunes + * authenticator for the web admin page, but rather the iTunes * authentication when trying to connect to the server. Note that most * of this is actually handled in the web server registration, which * decides when to apply the authentication or not. If you mess with - * when and where the webserver applies auth or not, you'll likely + * when and where the webserver applies auth or not, you'll likely * break something. It seems that some requests must be authed, and others * not. If you apply authentication somewhere that iTunes doesn't expect * it, it happily disconnects. @@ -104,7 +104,7 @@ typedef struct tag_output_info { * \returns 1 if auth successful, 0 otherwise */ int daap_auth(char *username, char *password) { - if((password == NULL) && + if((password == NULL) && ((config.readpassword == NULL) || (strlen(config.readpassword)==0))) return 1; @@ -151,24 +151,24 @@ void daap_handler(WS_CONNINFO *pwsc) { /* nm... backing this out. Really do need a "quirks" mode pwsc->close=0; if(ws_testrequestheader(pwsc,"Connection","Close")) { - pwsc->close = 1; + pwsc->close = 1; } */ - + if(ws_getvar(pwsc,"session-id")) pqi->session_id = atoi(ws_getvar(pwsc,"session-id")); - + /* tokenize the uri for easier decoding */ string=(pwsc->uri)+1; while((token=strtok_r(string,"/",&save))) { string=NULL; pqi->uri_sections[pqi->uri_count++] = token; } - + /* Start dispatching */ if(!strcasecmp(pqi->uri_sections[0],"server-info")) return dispatch_server_info(pwsc,pqi); - + if(!strcasecmp(pqi->uri_sections[0],"content-codes")) return dispatch_content_codes(pwsc,pqi); @@ -194,13 +194,13 @@ void daap_handler(WS_CONNINFO *pwsc) { } pqi->db_id=atoi(pqi->uri_sections[1]); if(pqi->uri_count == 3) { - if(!strcasecmp(pqi->uri_sections[2],"items")) + if(!strcasecmp(pqi->uri_sections[2],"items")) /* /databases/id/items */ return dispatch_items(pwsc,pqi); if(!strcasecmp(pqi->uri_sections[2],"containers")) - /* /databases/id/containers */ + /* /databases/id/containers */ return dispatch_playlists(pwsc,pqi); - + pwsc->close=1; free(pqi); ws_returnerror(pwsc,404,"Page not found"); @@ -252,7 +252,7 @@ void daap_handler(WS_CONNINFO *pwsc) { } } } - + pwsc->close=1; free(pqi); ws_returnerror(pwsc,404,"Page not found"); @@ -319,7 +319,7 @@ int dispatch_output_write(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, unsigned char *bl OUTPUT_INFO *poi=(pqi->output_info); int result; - if(poi->xml_output) + if(poi->xml_output) return dispatch_output_xml_write(pwsc, pqi, block, len); result=r_write(pwsc->fd,block,len); @@ -375,7 +375,7 @@ int dispatch_output_xml_write(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, unsigned char /* lookup and serialize */ DPRINTF(E_SPAM,L_DAAP,"%*s %s: %d\n",poi->stack_height,"",block_tag,block_len); pitem=dispatch_xml_lookup_tag(block_tag); - if(poi->readable) + if(poi->readable) r_fdprintf(pwsc->fd,"%*s",poi->stack_height,""); r_fdprintf(pwsc->fd,"<%s>",pitem->description); switch(pitem->type) { @@ -497,11 +497,11 @@ int dispatch_output_xml_write(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, unsigned char if(poi->stack[stack_ptr].bytes_left < 0) { DPRINTF(E_FATAL,L_DAAP,"negative container\n"); } - + if(!poi->stack[stack_ptr].bytes_left) { poi->stack_height--; pitem=dispatch_xml_lookup_tag(poi->stack[stack_ptr].tag); - if(poi->readable) + if(poi->readable) r_fdprintf(pwsc->fd,"%*s",poi->stack_height,""); r_fdprintf(pwsc->fd,"",pitem->description); if(poi->readable) @@ -518,8 +518,8 @@ int dispatch_output_xml_write(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, unsigned char /** * finish streaming output to the client, freeing any allocated * memory, and cleaning up - * - * \param pwsc current conninfo struct + * + * \param pwsc current conninfo struct * \param pqi current dbquery struct */ int dispatch_output_end(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { @@ -629,11 +629,12 @@ void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { item=atoi(pqi->uri_sections[3]); - if(ws_getrequestheader(pwsc,"range")) { + if(ws_getrequestheader(pwsc,"range")) { offset=(off_t)atol(ws_getrequestheader(pwsc,"range") + 6); } - pmp3=db_fetch_item(item); + /* FIXME: error handling */ + pmp3=db_fetch_item(NULL,item); if(!pmp3) { DPRINTF(E_LOG,L_DAAP|L_WS|L_DB,"Could not find requested item %lu\n",item); ws_returnerror(pwsc,404,"File Not Found"); @@ -646,7 +647,7 @@ void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { file_ptr = server_side_convert_open(pmp3->path, offset, pmp3->song_length, - pmp3->codectype); + pmp3->codectype); if (file_ptr) { file_fd = fileno(file_ptr); } else { @@ -699,7 +700,7 @@ void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { DPRINTF(E_LOG,L_WS, "Session %d: Streaming file '%s' to %s (offset %ld)\n", pqi->session_id,pmp3->fname, pwsc->hostname,(long)offset); - + if(!offset) config.stats.songs_served++; /* FIXME: remove stat races */ if((bytes_copied=copyfile(file_fd,pwsc->fd)) == -1) { @@ -736,33 +737,33 @@ void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { fstat(img_fd, &sb); img_size = sb.st_size; r_close(img_fd); - + if (strncasecmp(pmp3->type,"mp3",4) ==0) { /*PENDING*/ } else if (strncasecmp(pmp3->type, "m4a", 4) == 0) { real_len += img_size + 24; - + if (offset > img_size + 24) { offset -= img_size + 24; } } } - + file_len = real_len - offset; - + DPRINTF(E_DBG,L_WS,"Thread %d: Length of file (remaining) is %ld\n", pwsc->threadno,(long)file_len); - + // DWB: fix content-type to correctly reflect data // content type (dmap tagged) should only be used on // dmap protocol requests, not the actually song data - if(pmp3->type) + if(pmp3->type) ws_addresponseheader(pwsc,"Content-Type","audio/%s",pmp3->type); - + ws_addresponseheader(pwsc,"Content-Length","%ld",(long)file_len); ws_addresponseheader(pwsc,"Connection","Close"); - - + + if(!offset) ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); else { @@ -771,16 +772,16 @@ void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { (long)real_len+1); ws_writefd(pwsc,"HTTP/1.1 206 Partial Content\r\n"); } - + ws_emitheaders(pwsc); - + config_set_status(pwsc,pqi->session_id,"Streaming file '%s'",pmp3->fname); DPRINTF(E_LOG,L_WS,"Session %d: Streaming file '%s' to %s (offset %d)\n", pqi->session_id,pmp3->fname, pwsc->hostname,(long)offset); - + if(!offset) config.stats.songs_served++; /* FIXME: remove stat races */ - + if((config.artfilename) && (!offset) && ((img_fd=da_get_image_fd(pmp3->path)) != -1)) { @@ -789,7 +790,7 @@ void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { pmp3->fname, img_fd); da_attach_image(img_fd, pwsc->fd, file_fd, offset); } else if (strncasecmp(pmp3->type, "m4a", 4) == 0) { - DPRINTF(E_INF,L_WS|L_ART,"Dynamic add artwork to %s (fd %d)\n", + DPRINTF(E_INF,L_WS|L_ART,"Dynamic add artwork to %s (fd %d)\n", pmp3->fname, img_fd); da_aac_attach_image(img_fd, pwsc->fd, file_fd, offset); } @@ -797,7 +798,7 @@ void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { DPRINTF(E_INF,L_WS,"Seeking to offset %ld\n",(long)offset); lseek(file_fd,offset,SEEK_SET); } - + if((bytes_copied=copyfile(file_fd,pwsc->fd)) == -1) { DPRINTF(E_INF,L_WS,"Error copying file to remote... %s\n", strerror(errno)); @@ -805,7 +806,7 @@ void dispatch_stream(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { DPRINTF(E_INF,L_WS,"Finished streaming file to remote: %d bytes\n", bytes_copied); } - + config_set_status(pwsc,pqi->session_id,NULL); r_close(file_fd); db_dispose_item(pmp3); @@ -836,10 +837,11 @@ void dispatch_addplaylistitems(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { while((token=strsep((char**)¤t,","))) { if(token) { - db_add_playlist_item(pqi->playlist_id,atoi(token)); + /* FIXME: error handling */ + db_add_playlist_item(NULL,pqi->playlist_id,atoi(token)); } } - + free(tempstring); /* success(ish)... spool out a dmap block */ @@ -869,7 +871,8 @@ void dispatch_deleteplaylist(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { return; } - db_delete_playlist(atoi(ws_getvar(pwsc,"dmap.itemid"))); + /* FIXME: error handling */ + db_delete_playlist(NULL,atoi(ws_getvar(pwsc,"dmap.itemid"))); /* success(ish)... spool out a dmap block */ current = playlist_response; @@ -905,10 +908,11 @@ void dispatch_deleteplaylistitems(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { while((token=strsep((char**)¤t,","))) { if(token) { - db_delete_playlist_item(pqi->playlist_id,atoi(token)); + /* FIXME: Error handling */ + db_delete_playlist_item(NULL,pqi->playlist_id,atoi(token)); } } - + free(tempstring); /* success(ish)... spool out a dmap block */ @@ -946,7 +950,8 @@ void dispatch_addplaylist(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { name=ws_getvar(pwsc,"dmap.itemname"); query=ws_getvar(pwsc,"org.mt-daapd.smart-playlist-spec"); - retval=db_add_playlist(name,type,query,NULL,0,&playlistid); + /* FIXME: Error handling */ + retval=db_add_playlist(NULL,name,type,query,NULL,0,&playlistid); if(retval != DB_E_SUCCESS) { DPRINTF(E_LOG,L_DAAP,"error adding playlist. aborting\n"); ws_returnerror(pwsc,500,"error adding playlist"); @@ -973,37 +978,38 @@ void dispatch_addplaylist(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { void dispatch_editplaylist(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { unsigned char edit_response[20]; unsigned char *current = edit_response; - + char *name, *query; int id; - + int retval; - + if((!ws_getvar(pwsc,"dmap.itemname")) || (!ws_getvar(pwsc,"dmap.itemid"))) { DPRINTF(E_LOG,L_DAAP,"Missing name on playlist edit"); ws_returnerror(pwsc,500,"missing playlist name"); return; } - + name=ws_getvar(pwsc,"dmap.itemname"); query=ws_getvar(pwsc,"org.mt-daapd.smart-playlist-spec"); id=atoi(ws_getvar(pwsc,"dmap.itemid")); - - retval=db_edit_playlist(id,name,query); + + /* FIXME: Error handling */ + retval=db_edit_playlist(NULL,id,name,query); if(retval != DB_E_SUCCESS) { DPRINTF(E_LOG,L_DAAP,"error editing playlist."); ws_returnerror(pwsc,500,"Error editing playlist"); return; } - - current += db_dmap_add_container(current,"MEPR",12); + + current += db_dmap_add_container(current,"MEPR",12); current += db_dmap_add_int(current,"mstt",200); /* 12 */ - + dispatch_output_start(pwsc,pqi,20); dispatch_output_write(pwsc,pqi,edit_response,20); dispatch_output_end(pwsc,pqi); - + pwsc->close=1; return; } @@ -1031,13 +1037,16 @@ void dispatch_playlistitems(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { pqi->query_type = queryTypePlaylistItems; pqi->index_type=indexTypeNone; - if(db_enum_start(pqi)) { + + /* FIXME: Error handling */ + if(db_enum_start(NULL,pqi)) { DPRINTF(E_LOG,L_DAAP,"Could not start enum\n"); ws_returnerror(pwsc,500,"Internal server error: out of memory!"); return; } - - list_length=db_enum_size(pqi,&song_count); + + /* FIXME: Error handling */ + db_enum_size(NULL,pqi,&song_count,&list_length); DPRINTF(E_DBG,L_DAAP,"Item enum: got %d songs, dmap size: %d\n",song_count,list_length); @@ -1046,12 +1055,14 @@ void dispatch_playlistitems(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { current += db_dmap_add_char(current,"muty",0); /* 9 */ current += db_dmap_add_int(current,"mtco",song_count); /* 12 */ current += db_dmap_add_int(current,"mrco",song_count); /* 12 */ - current += db_dmap_add_container(current,"mlcl",list_length); + current += db_dmap_add_container(current,"mlcl",list_length); dispatch_output_start(pwsc,pqi,61+list_length); dispatch_output_write(pwsc,pqi,items_response,61); - while((list_length=db_enum_fetch(pqi,&block)) > 0) { + while((db_enum_fetch(NULL,pqi,&list_length,&block) == DB_E_SUCCESS) && + (list_length)) + { DPRINTF(E_SPAM,L_DAAP,"Got block of size %d\n",list_length); dispatch_output_write(pwsc,pqi,block,list_length); free(block); @@ -1059,7 +1070,7 @@ void dispatch_playlistitems(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { DPRINTF(E_DBG,L_DAAP,"Done enumerating.\n"); - db_enum_end(); + db_enum_end(NULL); dispatch_output_end(pwsc,pqi); return; @@ -1095,15 +1106,16 @@ void dispatch_browse(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { pqi->index_type = indexTypeNone; - if(db_enum_start(pqi)) { + if(db_enum_start(NULL,pqi)) { DPRINTF(E_LOG,L_DAAP|L_BROW,"Could not start enum\n"); ws_returnerror(pwsc,500,"Internal server error: out of memory!\n"); return; } - + DPRINTF(E_DBG,L_DAAP|L_BROW,"Getting enum size.\n"); - list_length=db_enum_size(pqi,&item_count); + /* FIXME: Error handling */ + db_enum_size(NULL,pqi,&item_count,&list_length); DPRINTF(E_DBG,L_DAAP|L_BROW,"Item enum: got %d items, dmap size: %d\n", item_count,list_length); @@ -1117,15 +1129,17 @@ void dispatch_browse(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { dispatch_output_start(pwsc,pqi,52+list_length); dispatch_output_write(pwsc,pqi,browse_response,52); - while((list_length=db_enum_fetch(pqi,&block)) > 0) { + while((db_enum_fetch(NULL,pqi,&list_length,&block) == DB_E_SUCCESS) && + (list_length)) + { DPRINTF(E_SPAM,L_DAAP|L_BROW,"Got block of size %d\n",list_length); dispatch_output_write(pwsc,pqi,block,list_length); free(block); } DPRINTF(E_DBG,L_DAAP|L_BROW,"Done enumerating\n"); - - db_enum_end(); + + db_enum_end(NULL); dispatch_output_end(pwsc,pqi); return; @@ -1151,13 +1165,14 @@ void dispatch_playlists(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { pqi->query_type = queryTypePlaylists; pqi->index_type = indexTypeNone; - if(db_enum_start(pqi)) { + if(db_enum_start(NULL,pqi)) { DPRINTF(E_LOG,L_DAAP,"Could not start enum\n"); ws_returnerror(pwsc,500,"Internal server error: out of memory!\n"); return; } - - list_length=db_enum_size(pqi,&pl_count); + + /* FIXME: Error handling */ + db_enum_size(NULL,pqi,&pl_count,&list_length); DPRINTF(E_DBG,L_DAAP,"Item enum: got %d playlists, dmap size: %d\n",pl_count,list_length); @@ -1166,12 +1181,14 @@ void dispatch_playlists(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { current += db_dmap_add_char(current,"muty",0); /* 9 */ current += db_dmap_add_int(current,"mtco",pl_count); /* 12 */ current += db_dmap_add_int(current,"mrco",pl_count); /* 12 */ - current += db_dmap_add_container(current,"mlcl",list_length); + current += db_dmap_add_container(current,"mlcl",list_length); dispatch_output_start(pwsc,pqi,61+list_length); dispatch_output_write(pwsc,pqi,playlist_response,61); - while((list_length=db_enum_fetch(pqi,&block)) > 0) { + while((db_enum_fetch(NULL,pqi,&list_length,&block) == DB_E_SUCCESS) && + (list_length)) + { DPRINTF(E_SPAM,L_DAAP,"Got block of size %d\n",list_length); dispatch_output_write(pwsc,pqi,block,list_length); free(block); @@ -1179,7 +1196,7 @@ void dispatch_playlists(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { DPRINTF(E_DBG,L_DAAP,"Done enumerating.\n"); - db_enum_end(); + db_enum_end(NULL); dispatch_output_end(pwsc,pqi); return; @@ -1200,13 +1217,14 @@ void dispatch_items(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { pqi->query_type = queryTypeItems; pqi->index_type=indexTypeNone; - if(db_enum_start(pqi)) { + if(db_enum_start(NULL,pqi)) { DPRINTF(E_LOG,L_DAAP,"Could not start enum\n"); ws_returnerror(pwsc,500,"Internal server error: out of memory!"); return; } - - list_length=db_enum_size(pqi,&song_count); + + /* FIXME: Error handling */ + db_enum_size(NULL,pqi,&song_count,&list_length); DPRINTF(E_DBG,L_DAAP,"Item enum: got %d songs, dmap size: %d\n",song_count,list_length); @@ -1215,12 +1233,14 @@ void dispatch_items(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { current += db_dmap_add_char(current,"muty",0); /* 9 */ current += db_dmap_add_int(current,"mtco",song_count); /* 12 */ current += db_dmap_add_int(current,"mrco",song_count); /* 12 */ - current += db_dmap_add_container(current,"mlcl",list_length); + current += db_dmap_add_container(current,"mlcl",list_length); dispatch_output_start(pwsc,pqi,61+list_length); dispatch_output_write(pwsc,pqi,items_response,61); - while((list_length=db_enum_fetch(pqi,&block)) > 0) { + while((db_enum_fetch(NULL,pqi,&list_length,&block) == DB_E_SUCCESS) && + (list_length)) + { DPRINTF(E_SPAM,L_DAAP,"Got block of size %d\n",list_length); dispatch_output_write(pwsc,pqi,block,list_length); free(block); @@ -1228,7 +1248,7 @@ void dispatch_items(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { DPRINTF(E_DBG,L_DAAP,"Done enumerating.\n"); - db_enum_end(); + db_enum_end(NULL); dispatch_output_end(pwsc,pqi); return; @@ -1259,7 +1279,7 @@ void dispatch_update(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { if(FD_ISSET(pwsc->fd,&rset)) { /* can't be ready for read, must be error */ DPRINTF(E_DBG,L_DAAP,"Socket closed?\n"); - + return; } } @@ -1280,6 +1300,7 @@ void dispatch_dbinfo(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { unsigned char dbinfo_response[255]; /* FIXME */ unsigned char *current = dbinfo_response; int namelen; + int count; namelen=strlen(config.servername); @@ -1289,11 +1310,13 @@ void dispatch_dbinfo(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { current += db_dmap_add_int(current,"mtco",1); /* 12 */ current += db_dmap_add_int(current,"mrco",1); /* 12 */ current += db_dmap_add_container(current,"mlcl",52 + namelen); - current += db_dmap_add_container(current,"mlit",44 + namelen); + current += db_dmap_add_container(current,"mlit",44 + namelen); current += db_dmap_add_int(current,"miid",1); /* 12 */ current += db_dmap_add_string(current,"minm",config.servername); /* 8 + namelen */ - current += db_dmap_add_int(current,"mimc",db_get_song_count()); /* 12 */ - current += db_dmap_add_int(current,"mctc",db_get_playlist_count()); /* 12 */ + db_get_song_count(NULL,&count); + current += db_dmap_add_int(current,"mimc",count); /* 12 */ + db_get_playlist_count(NULL,&count); + current += db_dmap_add_int(current,"mctc",count); /* 12 */ dispatch_output_start(pwsc,pqi,113+namelen); dispatch_output_write(pwsc,pqi,dbinfo_response,113+namelen); @@ -1313,7 +1336,7 @@ void dispatch_login(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { unsigned char login_response[32]; unsigned char *current = login_response; int session; - + session = config_get_next_session(); current += db_dmap_add_container(current,"mlog",24); @@ -1342,7 +1365,7 @@ void dispatch_content_codes(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { current += db_dmap_add_container(current,"mccr",len + 12); current += db_dmap_add_int(current,"mstt",200); - + dispatch_output_start(pwsc,pqi,len+20); dispatch_output_write(pwsc,pqi,content_codes,20); @@ -1357,7 +1380,7 @@ void dispatch_content_codes(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { dispatch_output_write(pwsc,pqi,mdcl,len+8); dicurrent++; } - + dispatch_output_end(pwsc,pqi); return; } @@ -1376,7 +1399,7 @@ void dispatch_server_info(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { } client_version=ws_getrequestheader(pwsc,"Client-DAAP-Version"); - + current += db_dmap_add_container(current,"msrv",actual_length - 8); current += db_dmap_add_int(current,"mstt",200); /* 12 */ @@ -1396,7 +1419,7 @@ void dispatch_server_info(WS_CONNINFO *pwsc, DBQUERYINFO *pqi) { current += db_dmap_add_string(current,"minm",config.servername); /* 8 + strlen(name) */ current += db_dmap_add_char(current,"msau", /* 9 */ - config.readpassword != NULL ? 2 : 0); + config.readpassword != NULL ? 2 : 0); current += db_dmap_add_char(current,"msex",0); /* 9 */ current += db_dmap_add_char(current,"msix",0); /* 9 */ current += db_dmap_add_char(current,"msbr",0); /* 9 */ diff --git a/src/main.c b/src/main.c index 25224a0a..42149239 100644 --- a/src/main.c +++ b/src/main.c @@ -117,7 +117,7 @@ */ CONFIG config; /**< Main configuration structure, as read from configfile */ -/* +/* * Forwards */ static int daemon_start(void); @@ -158,7 +158,7 @@ int daemon_start(void) { if((fd = open("/dev/null", O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); + dup2(fd, STDERR_FILENO); if (fd > 2) close(fd); } @@ -219,7 +219,7 @@ int drop_privs(char *user) { } if(pw) { - if(initgroups(user,pw->pw_gid) != 0 || + if(initgroups(user,pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0 || setuid(pw->pw_uid) != 0) { err=errno; @@ -353,13 +353,14 @@ int main(int argc, char *argv[]) { int start_time; int end_time; int rescan_counter=0; - int old_song_count; + int old_song_count, song_count; int force_non_root=0; int skip_initial=0; pthread_t signal_tid; int pid_fd; FILE *pid_fp=NULL; + char *perr; config.use_mdns=1; err_debuglevel=1; @@ -482,8 +483,8 @@ int main(int argc, char *argv[]) { } /* this will require that the db be readable by the runas user */ - if(db_open(config.dbdir)) - DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_open: %s\n",strerror(errno)); + if(db_open(&perr,config.dbdir)) + DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_open: %s\n",perr); /* Initialize the database before starting */ DPRINTF(E_LOG,L_MAIN|L_DB,"Initializing database\n"); @@ -529,7 +530,8 @@ int main(int argc, char *argv[]) { end_time=time(NULL); - DPRINTF(E_LOG,L_MAIN,"Scanned %d songs in %d seconds\n",db_get_song_count(), + db_get_song_count(NULL,&song_count); + DPRINTF(E_LOG,L_MAIN,"Scanned %d songs in %d seconds\n",song_count, end_time-start_time); while(!config.stop) { @@ -543,7 +545,7 @@ int main(int argc, char *argv[]) { } if(config.reload) { - old_song_count = db_get_song_count(); + old_song_count = song_count; start_time=time(NULL); DPRINTF(E_LOG,L_MAIN|L_DB|L_SCAN,"Rescanning database\n"); @@ -552,8 +554,10 @@ int main(int argc, char *argv[]) { config.stop=1; } config.reload=0; - DPRINTF(E_INF,L_MAIN|L_DB|L_SCAN,"Scanned %d songs (was %d) in %d seconds\n", - db_get_song_count(),old_song_count,time(NULL)-start_time); + db_get_song_count(NULL,&song_count); + DPRINTF(E_INF,L_MAIN|L_DB|L_SCAN,"Scanned %d songs (was %d) in " + "%d seconds\n",song_count,old_song_count, + time(NULL)-start_time); } sleep(MAIN_SLEEP_INTERVAL); diff --git a/src/mp3-scanner.c b/src/mp3-scanner.c index 7d9516ca..644c00b3 100644 --- a/src/mp3-scanner.c +++ b/src/mp3-scanner.c @@ -6,7 +6,7 @@ * but the name is the same for historical purposes, not to mention * the fact that sf.net makes it virtually impossible to manage a cvs * root reasonably. Perhaps one day soon they will move to subversion. - * + * * /me crosses his fingers * * Copyright (C) 2003 Ron Pedde (ron@pedde.com) @@ -107,20 +107,20 @@ static int scan_static_playlist(char *path); /* For known types, I'm gong to use the "official" apple * daap.songformat, daap.songdescription, and daap.songcodecsubtype. * If I we don't have "official" ones, we can make them up the - * way we currently are: using extension or whatver. + * way we currently are: using extension or whatver. * * This means that you can test to see if something is, say, an un-drmed * aac file by just testing for ->type "m4a", rather than checking every * different flavor of file extension. - * + * * NOTE: Although they are represented here as strings, the codectype is - * *really* an unsigned short. So when it gets serialized, it gets - * serialized as a short int. If you put something other than 3 or 4 + * *really* an unsigned short. So when it gets serialized, it gets + * serialized as a short int. If you put something other than 3 or 4 * characters as your codectype, you'll see strange results. * * FIXME: url != pls -- this method of dispatching handlers based on file type - * is completely wrong. There needs to be a separate type that gets carried - * around with it, at least outside the database that says where the info + * is completely wrong. There needs to be a separate type that gets carried + * around with it, at least outside the database that says where the info * CAME FROM. * * This system is broken, and won't work with something like a .cue file @@ -160,8 +160,8 @@ static PLAYLISTLIST scan_playlistlist = { NULL, NULL }; /** * add a playlist to the playlistlist. The playlistlist is a * list of playlists that need to be processed once the current - * scan is done. THIS IS NOT REENTRANT, and it meant to be - * called only inside the rescan loop. + * scan is done. THIS IS NOT REENTRANT, and it meant to be + * called only inside the rescan loop. * * \param path path of the playlist to add */ @@ -243,7 +243,7 @@ int scan_init(char *path) { return -1; scan_process_playlistlist(); - + if(db_end_scan()) return -1; @@ -317,7 +317,7 @@ int scan_path(char *path) { if(!pde) break; - + if(pde->d_name[0] == '.') /* skip hidden and directories */ continue; @@ -344,9 +344,9 @@ int scan_path(char *path) { (strcasestr(config.extensions, ext))) { /* only scan if it's been changed, or empty db */ modified_time=sb.st_mtime; - pmp3=db_fetch_path(mp3_path,0); + pmp3=db_fetch_path(NULL,mp3_path,0); - if((!pmp3) || (pmp3->db_timestamp < modified_time) || + if((!pmp3) || (pmp3->db_timestamp < modified_time) || (pmp3->force_update)) { scan_music_file(path,pde,&sb,is_compdir); } else { @@ -380,6 +380,7 @@ int scan_static_playlist(char *path) { MP3FILE *pmp3; struct stat sb; char *current; + char *perr; DPRINTF(E_WARN,L_SCAN|L_PL,"Processing static playlist: %s\n",path); if(stat(path,&sb)) { @@ -399,7 +400,7 @@ int scan_static_playlist(char *path) { *current='\x0'; } - pm3u = db_fetch_playlist(path,0); + pm3u = db_fetch_playlist(NULL,path,0); if(pm3u && (pm3u->db_timestamp > sb.st_mtime)) { /* already up-to-date */ DPRINTF(E_DBG,L_SCAN,"Playlist already up-to-date\n"); @@ -407,13 +408,15 @@ int scan_static_playlist(char *path) { return TRUE; } - if(pm3u) - db_delete_playlist(pm3u->id); + if(pm3u) + db_delete_playlist(NULL,pm3u->id); fd=open(path,O_RDONLY); if(fd != -1) { - if(db_add_playlist(base_path,PL_STATICFILE,NULL,path,0,&playlistid) != DB_E_SUCCESS) { - DPRINTF(E_LOG,L_SCAN,"Error adding m3u playlist %s\n",path); + if(db_add_playlist(&perr,base_path,PL_STATICFILE,NULL,path, + 0,&playlistid) != DB_E_SUCCESS) { + DPRINTF(E_LOG,L_SCAN,"Error adding m3u %s: %s\n",path,perr); + free(perr); db_dispose_playlist(pm3u); return FALSE; } @@ -428,7 +431,7 @@ int scan_static_playlist(char *path) { memset(linebuffer,0x00,sizeof(linebuffer)); while(readline(fd,linebuffer,sizeof(linebuffer)) > 0) { while((linebuffer[strlen(linebuffer)-1] == '\n') || - (linebuffer[strlen(linebuffer)-1] == '\r')) + (linebuffer[strlen(linebuffer)-1] == '\r')) linebuffer[strlen(linebuffer)-1] = '\0'; if((linebuffer[0] == ';') || (linebuffer[0] == '#')) @@ -447,12 +450,14 @@ int scan_static_playlist(char *path) { DPRINTF(E_DBG,L_SCAN|L_PL,"Checking %s\n",real_path); // might be valid, might not... - if((pmp3=db_fetch_path(real_path,0))) { - db_add_playlist_item(playlistid,pmp3->id); + if((pmp3=db_fetch_path(&perr,real_path,0))) { + /* FIXME: better error handling */ + db_add_playlist_item(NULL,playlistid,pmp3->id); db_dispose_item(pmp3); } else { DPRINTF(E_WARN,L_SCAN|L_PL,"Playlist entry %s bad: %s\n", - path,strerror(errno)); + path,perr); + free(perr); } } close(fd); @@ -469,7 +474,7 @@ int scan_static_playlist(char *path) { * * scan a particular file as a music file */ -void scan_music_file(char *path, struct dirent *pde, +void scan_music_file(char *path, struct dirent *pde, struct stat *psb, int is_compdir) { MP3FILE mp3file; char mp3_path[PATH_MAX]; @@ -482,7 +487,7 @@ void scan_music_file(char *path, struct dirent *pde, /* we found an mp3 file */ DPRINTF(E_INF,L_SCAN,"Found music file: %s\n",pde->d_name); - + memset((void*)&mp3file,0,sizeof(mp3file)); mp3file.path=strdup(mp3_path); mp3file.fname=strdup(pde->d_name); @@ -511,14 +516,14 @@ void scan_music_file(char *path, struct dirent *pde, *current=tolower(*current); current++; } - + sprintf(fdescr,"%s audio file",mp3file.type); mp3file.description = strdup(fdescr); /* we'll just dodge the codectype */ } } } - + /* Do the tag lookup here */ if(scan_get_info(mp3file.path,&mp3file)) { make_composite_tags(&mp3file); @@ -538,11 +543,12 @@ void scan_music_file(char *path, struct dirent *pde, if(is_compdir) mp3file.compilation = 1; - db_add(&mp3file); + /* FIXME: error handling */ + db_add(NULL,&mp3file); } else { DPRINTF(E_WARN,L_SCAN,"Skipping %s - scan failed\n",mp3file.path); } - + scan_freetags(&mp3file); } @@ -624,7 +630,7 @@ int scan_get_info(char *file, MP3FILE *pmp3) { /** * Manually build tags. Set artist to computer/orchestra - * if there is already no artist. Perhaps this could be + * if there is already no artist. Perhaps this could be * done better, but I'm not sure what else to do here. * * @param song MP3FILE of the file to build composite tags for diff --git a/src/scan-xml.c b/src/scan-xml.c index b10052c8..aeecf0fd 100644 --- a/src/scan-xml.c +++ b/src/scan-xml.c @@ -131,16 +131,16 @@ int scan_xml_datedecode(char *string) { /** * comparison for the red-black tree. @see redblack.c - * + * * @param pa one node to compare * @param pb other node to compare * @param cfg opaque pointer I'm not using */ int scan_xml_rb_compare(const void *pa, const void *pb, const void *cfg) { - if(((SCAN_XML_RB*)pa)->itunes_index < ((SCAN_XML_RB*)pb)->itunes_index) - return -1; + if(((SCAN_XML_RB*)pa)->itunes_index < ((SCAN_XML_RB*)pb)->itunes_index) + return -1; if(((SCAN_XML_RB*)pb)->itunes_index < ((SCAN_XML_RB*)pa)->itunes_index) - return 1; + return 1; return 0; } @@ -156,10 +156,10 @@ int scan_xml_is_file(char *path) { struct stat sb; if(stat(path,&sb)) - return 0; + return 0; if(sb.st_mode & S_IFREG) - return 1; + return 1; return 0; } @@ -182,35 +182,35 @@ int scan_xml_translate_path(char *pold, char *pnew) { char *pbase; if((!pold)||(!strlen(pold))) - return FALSE; + return FALSE; if(!path_found) { - strcpy(working_path,pold); + strcpy(working_path,pold); - DPRINTF(E_DBG,L_SCAN,"Translating %s\n",pold); + DPRINTF(E_DBG,L_SCAN,"Translating %s\n",pold); - /* let's try to find the path by brute force. - * We'll assume that it is under the xml file somewhere - */ - while(!path_found && ((current = strrchr(working_path,'/')))) { - strcpy(base_path,scan_xml_file); - pbase = strrchr(base_path,'/'); - if(!pbase) return FALSE; + /* let's try to find the path by brute force. + * We'll assume that it is under the xml file somewhere + */ + while(!path_found && ((current = strrchr(working_path,'/')))) { + strcpy(base_path,scan_xml_file); + pbase = strrchr(base_path,'/'); + if(!pbase) return FALSE; - strcpy(pbase,pold + (current-working_path)); - if(base_path[strlen(base_path)-1] == '/') - base_path[strlen(base_path)-1] = '\0'; + strcpy(pbase,pold + (current-working_path)); + if(base_path[strlen(base_path)-1] == '/') + base_path[strlen(base_path)-1] = '\0'; - DPRINTF(E_DBG,L_SCAN,"Trying %s\n",base_path); - if(scan_xml_is_file(base_path)) { - path_found=1; - discard = (current - working_path); - DPRINTF(E_DBG,L_SCAN,"Found it!\n"); - } - *current='\0'; - } - if(!current) - return FALSE; + DPRINTF(E_DBG,L_SCAN,"Trying %s\n",base_path); + if(scan_xml_is_file(base_path)) { + path_found=1; + discard = (current - working_path); + DPRINTF(E_DBG,L_SCAN,"Found it!\n"); + } + *current='\0'; + } + if(!current) + return FALSE; } strcpy(base_path,scan_xml_file); @@ -219,7 +219,7 @@ int scan_xml_translate_path(char *pold, char *pnew) { strcpy(pbase,pold + discard); if(base_path[strlen(base_path)-1] == '/') - base_path[strlen(base_path)-1] = '\0'; + base_path[strlen(base_path)-1] = '\0'; realpath(base_path,pnew); @@ -241,16 +241,16 @@ void scan_xml_add_lookup(int itunes_index, int mtd_index) { pnew=(SCAN_XML_RB*)malloc(sizeof(SCAN_XML_RB)); if(!pnew) - DPRINTF(E_FATAL,L_SCAN,"malloc error in scan_xml_add_lookup\n"); - + DPRINTF(E_FATAL,L_SCAN,"malloc error in scan_xml_add_lookup\n"); + pnew->itunes_index = itunes_index; pnew->mtd_index = mtd_index; val = rbsearch((const void*)pnew,scan_xml_db); if(!val) { - /* couldn't alloc the rb tree structure -- if we don't - * die now, we are going to soon enough*/ - DPRINTF(E_FATAL,L_SCAN,"redblack tree insert error\n"); + /* couldn't alloc the rb tree structure -- if we don't + * die now, we are going to soon enough*/ + DPRINTF(E_FATAL,L_SCAN,"redblack tree insert error\n"); } } @@ -268,9 +268,9 @@ int scan_xml_get_index(int itunes_index, int *mtd_index) { rb.itunes_index = itunes_index; prb = (SCAN_XML_RB*) rbfind((void*)&rb,scan_xml_db); if(prb) { - *mtd_index = prb->mtd_index; - DPRINTF(E_SPAM,L_SCAN,"Matching %d to %d\n",itunes_index,*mtd_index); - return TRUE; + *mtd_index = prb->mtd_index; + DPRINTF(E_SPAM,L_SCAN,"Matching %d to %d\n",itunes_index,*mtd_index); + return TRUE; } return FALSE; @@ -287,12 +287,12 @@ int scan_xml_get_tagindex(char *tag) { int index=0; while(*ptag && (strcasecmp(tag,*ptag) != 0)) { - ptag++; - index++; + ptag++; + index++; } - if(*ptag) - return index; + if(*ptag) + return index; return SCAN_XML_T_UNKNOWN; } @@ -311,45 +311,45 @@ char *scan_xml_urldecode(char *string, int space_as_plus) { pnew=(char*)malloc(strlen(string)+1); if(!pnew) - return NULL; + return NULL; src=string; dst=pnew; while(*src) { - switch(*src) { - case '+': - if(space_as_plus) { - *dst++=' '; - } else { - *dst++=*src; - } - src++; - break; - case '%': - /* this is hideous */ - src++; - if(*src) { - if((*src <= '9') && (*src >='0')) - val=(*src - '0'); - else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a')) - val=10+(tolower(*src) - 'a'); - src++; - } - if(*src) { - val *= 16; - if((*src <= '9') && (*src >='0')) - val+=(*src - '0'); - else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a')) - val+=(10+(tolower(*src) - 'a')); - src++; - } - *dst++=val; - break; - default: - *dst++=*src++; - break; - } + switch(*src) { + case '+': + if(space_as_plus) { + *dst++=' '; + } else { + *dst++=*src; + } + src++; + break; + case '%': + /* this is hideous */ + src++; + if(*src) { + if((*src <= '9') && (*src >='0')) + val=(*src - '0'); + else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a')) + val=10+(tolower(*src) - 'a'); + src++; + } + if(*src) { + val *= 16; + if((*src <= '9') && (*src >='0')) + val+=(*src - '0'); + else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a')) + val+=(10+(tolower(*src) - 'a')); + src++; + } + *dst++=val; + break; + default: + *dst++=*src++; + break; + } } *dst='\0'; @@ -381,31 +381,31 @@ int scan_xml_playlist(char *filename) { /* initialize the redblack tree */ if((scan_xml_db = rbinit(scan_xml_rb_compare,NULL)) == NULL) { - DPRINTF(E_LOG,L_SCAN,"Could not initialize red/black tree\n"); - return FALSE; + DPRINTF(E_LOG,L_SCAN,"Could not initialize red/black tree\n"); + return FALSE; } /* find the base dir of the itunes playlist itself */ working_base = strdup(filename); if(strrchr(working_base,'/')) { - *(strrchr(working_base,'/') + 1) = '\x0'; - scan_xml_real_base_path = strdup(working_base); + *(strrchr(working_base,'/') + 1) = '\x0'; + scan_xml_real_base_path = strdup(working_base); } else { - scan_xml_real_base_path = strdup("/"); + scan_xml_real_base_path = strdup("/"); } free(working_base); DPRINTF(E_SPAM,L_SCAN,"Parsing xml file: %s\n",filename); if(!rxml_open(&xml_handle,filename,scan_xml_handler,NULL)) { - DPRINTF(E_LOG,L_SCAN,"Error opening xml file %s: %s\n", - filename,rxml_errorstring(xml_handle)); + DPRINTF(E_LOG,L_SCAN,"Error opening xml file %s: %s\n", + filename,rxml_errorstring(xml_handle)); } else { - if(!rxml_parse(xml_handle)) { - retval=FALSE; - DPRINTF(E_LOG,L_SCAN,"Error parsing xml file %s: %s\n", - filename,rxml_errorstring(xml_handle)); - } + if(!rxml_parse(xml_handle)) { + retval=FALSE; + DPRINTF(E_LOG,L_SCAN,"Error parsing xml file %s: %s\n", + filename,rxml_errorstring(xml_handle)); + } } rxml_close(xml_handle); @@ -413,11 +413,11 @@ int scan_xml_playlist(char *filename) { /* destroy the redblack tree */ val = rblookup(RB_LUFIRST,NULL,scan_xml_db); while(val) { - lookup_val.itunes_index = ((SCAN_XML_RB*)val)->itunes_index; - lookup_ptr = (SCAN_XML_RB *)rbdelete((void*)&lookup_val,scan_xml_db); - if(lookup_ptr) - free(lookup_ptr); - val = rblookup(RB_LUFIRST,NULL,scan_xml_db); + lookup_val.itunes_index = ((SCAN_XML_RB*)val)->itunes_index; + lookup_ptr = (SCAN_XML_RB *)rbdelete((void*)&lookup_val,scan_xml_db); + if(lookup_ptr) + free(lookup_ptr); + val = rblookup(RB_LUFIRST,NULL,scan_xml_db); } rbdestroy(scan_xml_db); @@ -445,32 +445,32 @@ void scan_xml_handler(int action,void* puser,char* info) { switch(action) { case RXML_EVT_OPEN: /* file opened */ - state = XML_STATE_PREAMBLE; - /* send this event to all dispatches to allow them - * to reset - */ - scan_xml_preamble_section(action,info); - scan_xml_tracks_section(action,info); - scan_xml_playlists_section(action,info); - break; + state = XML_STATE_PREAMBLE; + /* send this event to all dispatches to allow them + * to reset + */ + scan_xml_preamble_section(action,info); + scan_xml_tracks_section(action,info); + scan_xml_playlists_section(action,info); + break; case RXML_EVT_BEGIN: case RXML_EVT_END: case RXML_EVT_TEXT: - switch(state) { - case XML_STATE_PREAMBLE: - state=scan_xml_preamble_section(action,info); - break; - case XML_STATE_TRACKS: - state=scan_xml_tracks_section(action,info); - break; - case XML_STATE_PLAYLISTS: - state=scan_xml_playlists_section(action,info); - break; - default: - break; - } + switch(state) { + case XML_STATE_PREAMBLE: + state=scan_xml_preamble_section(action,info); + break; + case XML_STATE_TRACKS: + state=scan_xml_tracks_section(action,info); + break; + case XML_STATE_PLAYLISTS: + state=scan_xml_playlists_section(action,info); + break; + default: + break; + } default: - break; + break; } } @@ -489,62 +489,62 @@ void scan_xml_handler(int action,void* puser,char* info) { int scan_xml_preamble_section(int action, char *info) { static int expecting_next; static int done; - + switch(action) { case RXML_EVT_OPEN: /* initialization */ - expecting_next=0; - done=0; - break; + expecting_next=0; + done=0; + break; case RXML_EVT_END: - if(expecting_next == SCAN_XML_PRE_TRACKS) { /* end of tracks tag */ - expecting_next=0; - DPRINTF(E_DBG,L_SCAN,"Scanning tracks\n"); - return XML_STATE_TRACKS; - } - if(expecting_next == SCAN_XML_PRE_PLAYLISTS) { - expecting_next=0; - DPRINTF(E_DBG,L_SCAN,"Scanning playlists\n"); - return XML_STATE_PLAYLISTS; - } - break; + if(expecting_next == SCAN_XML_PRE_TRACKS) { /* end of tracks tag */ + expecting_next=0; + DPRINTF(E_DBG,L_SCAN,"Scanning tracks\n"); + return XML_STATE_TRACKS; + } + if(expecting_next == SCAN_XML_PRE_PLAYLISTS) { + expecting_next=0; + DPRINTF(E_DBG,L_SCAN,"Scanning playlists\n"); + return XML_STATE_PLAYLISTS; + } + break; case RXML_EVT_TEXT: /* scan for the tags we expect */ - if(!expecting_next) { - if(strcmp(info,"Application Version") == 0) { - expecting_next = SCAN_XML_PRE_VERSION; - } else if (strcmp(info,"Music Folder") == 0) { - expecting_next = SCAN_XML_PRE_PATH; - } else if (strcmp(info,"Tracks") == 0) { - expecting_next = SCAN_XML_PRE_TRACKS; - } else if (strcmp(info,"Playlists") == 0) { - expecting_next = SCAN_XML_PRE_PLAYLISTS; - } - } else { - /* we were expecting someting! */ - switch(expecting_next) { - case SCAN_XML_PRE_VERSION: - if(!scan_xml_itunes_version) { - scan_xml_itunes_version=strdup(info); - DPRINTF(E_DBG,L_SCAN,"iTunes Version: %s\n",info); - } - break; - case SCAN_XML_PRE_PATH: - if(!scan_xml_itunes_base_path) { - scan_xml_itunes_base_path=strdup(info); - scan_xml_itunes_decoded_base_path=scan_xml_urldecode(info,0); - DPRINTF(E_DBG,L_SCAN,"iTunes base path: %s\n",info); - } - break; - default: - break; - } - expecting_next=0; - } - break; /* RXML_EVT_TEXT */ + if(!expecting_next) { + if(strcmp(info,"Application Version") == 0) { + expecting_next = SCAN_XML_PRE_VERSION; + } else if (strcmp(info,"Music Folder") == 0) { + expecting_next = SCAN_XML_PRE_PATH; + } else if (strcmp(info,"Tracks") == 0) { + expecting_next = SCAN_XML_PRE_TRACKS; + } else if (strcmp(info,"Playlists") == 0) { + expecting_next = SCAN_XML_PRE_PLAYLISTS; + } + } else { + /* we were expecting someting! */ + switch(expecting_next) { + case SCAN_XML_PRE_VERSION: + if(!scan_xml_itunes_version) { + scan_xml_itunes_version=strdup(info); + DPRINTF(E_DBG,L_SCAN,"iTunes Version: %s\n",info); + } + break; + case SCAN_XML_PRE_PATH: + if(!scan_xml_itunes_base_path) { + scan_xml_itunes_base_path=strdup(info); + scan_xml_itunes_decoded_base_path=scan_xml_urldecode(info,0); + DPRINTF(E_DBG,L_SCAN,"iTunes base path: %s\n",info); + } + break; + default: + break; + } + expecting_next=0; + } + break; /* RXML_EVT_TEXT */ default: - break; + break; } return XML_STATE_PREAMBLE; @@ -569,7 +569,7 @@ int scan_xml_preamble_section(int action, char *info) { state = (c); \ return XML_STATE_TRACKS; \ }} - + int scan_xml_tracks_section(int action, char *info) { static int state; static int current_track_id; @@ -580,143 +580,144 @@ int scan_xml_tracks_section(int action, char *info) { MP3FILE *pmp3; if(action == RXML_EVT_OPEN) { - state = XML_TRACK_ST_INITIAL; - memset((void*)&mp3,0,sizeof(MP3FILE)); - song_path = NULL; - return 0; + state = XML_TRACK_ST_INITIAL; + memset((void*)&mp3,0,sizeof(MP3FILE)); + song_path = NULL; + return 0; } /* walk through the states */ switch(state) { case XML_TRACK_ST_INITIAL: - /* expection only a */ - MAYBESETSTATE_TR(RXML_EVT_BEGIN,"dict",XML_TRACK_ST_MAIN_DICT); - return XML_STATE_ERROR; - break; + /* expection only a */ + MAYBESETSTATE_TR(RXML_EVT_BEGIN,"dict",XML_TRACK_ST_MAIN_DICT); + return XML_STATE_ERROR; + break; case XML_TRACK_ST_MAIN_DICT: - /* either get a , or a */ - MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_EXPECTING_TRACK_ID); - if ((action == RXML_EVT_END) && (strcasecmp(info,"dict") == 0)) { - return XML_STATE_PREAMBLE; - } - return XML_STATE_ERROR; - break; + /* either get a , or a */ + MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_EXPECTING_TRACK_ID); + if ((action == RXML_EVT_END) && (strcasecmp(info,"dict") == 0)) { + return XML_STATE_PREAMBLE; + } + return XML_STATE_ERROR; + break; case XML_TRACK_ST_EXPECTING_TRACK_ID: - /* this is somewhat loose - id */ - MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_EXPECTING_TRACK_ID); - MAYBESETSTATE_TR(RXML_EVT_END,"key",XML_TRACK_ST_EXPECTING_TRACK_DICT); - if (action == RXML_EVT_TEXT) { - current_track_id = atoi(info); - DPRINTF(E_DBG,L_SCAN,"Scanning iTunes id #%d\n",current_track_id); - } else { - return XML_STATE_ERROR; - } - break; + /* this is somewhat loose - id */ + MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_EXPECTING_TRACK_ID); + MAYBESETSTATE_TR(RXML_EVT_END,"key",XML_TRACK_ST_EXPECTING_TRACK_DICT); + if (action == RXML_EVT_TEXT) { + current_track_id = atoi(info); + DPRINTF(E_DBG,L_SCAN,"Scanning iTunes id #%d\n",current_track_id); + } else { + return XML_STATE_ERROR; + } + break; case XML_TRACK_ST_EXPECTING_TRACK_DICT: - /* waiting for a dict */ - MAYBESETSTATE_TR(RXML_EVT_BEGIN,"dict",XML_TRACK_ST_TRACK_INFO); - return XML_STATE_ERROR; - break; + /* waiting for a dict */ + MAYBESETSTATE_TR(RXML_EVT_BEGIN,"dict",XML_TRACK_ST_TRACK_INFO); + return XML_STATE_ERROR; + break; case XML_TRACK_ST_TRACK_INFO: - /* again, kind of loose */ - MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_TRACK_INFO); - MAYBESETSTATE_TR(RXML_EVT_END,"key",XML_TRACK_ST_TRACK_DATA); - if(action == RXML_EVT_TEXT) { - current_field=scan_xml_get_tagindex(info); - if(current_field == SCAN_XML_T_DISABLED) { - mp3.disabled = 1; - } else if(current_field == SCAN_XML_T_COMPILATION) { - mp3.compilation = 1; - } - } else if((action == RXML_EVT_END) && (strcmp(info,"dict")==0)) { - state = XML_TRACK_ST_MAIN_DICT; - /* but more importantly, we gotta process the track */ - if(scan_xml_translate_path(song_path,real_path)) { - pmp3=db_fetch_path(real_path,0); - if(pmp3) { - /* Update the existing record with the - * updated stuff we got from the iTunes xml file - */ - MAYBECOPYSTRING(title); - MAYBECOPYSTRING(artist); - MAYBECOPYSTRING(album); - MAYBECOPYSTRING(genre); - MAYBECOPY(song_length); - MAYBECOPY(track); - MAYBECOPY(total_tracks); - MAYBECOPY(year); - MAYBECOPY(bitrate); - MAYBECOPY(samplerate); - MAYBECOPY(play_count); - MAYBECOPY(rating); - MAYBECOPY(disc); - MAYBECOPY(total_discs); - MAYBECOPY(time_added); + /* again, kind of loose */ + MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_TRACK_INFO); + MAYBESETSTATE_TR(RXML_EVT_END,"key",XML_TRACK_ST_TRACK_DATA); + if(action == RXML_EVT_TEXT) { + current_field=scan_xml_get_tagindex(info); + if(current_field == SCAN_XML_T_DISABLED) { + mp3.disabled = 1; + } else if(current_field == SCAN_XML_T_COMPILATION) { + mp3.compilation = 1; + } + } else if((action == RXML_EVT_END) && (strcmp(info,"dict")==0)) { + state = XML_TRACK_ST_MAIN_DICT; + /* but more importantly, we gotta process the track */ + if(scan_xml_translate_path(song_path,real_path)) { + /* FIXME: Error handling */ + pmp3=db_fetch_path(NULL,real_path,0); + if(pmp3) { + /* Update the existing record with the + * updated stuff we got from the iTunes xml file + */ + MAYBECOPYSTRING(title); + MAYBECOPYSTRING(artist); + MAYBECOPYSTRING(album); + MAYBECOPYSTRING(genre); + MAYBECOPY(song_length); + MAYBECOPY(track); + MAYBECOPY(total_tracks); + MAYBECOPY(year); + MAYBECOPY(bitrate); + MAYBECOPY(samplerate); + MAYBECOPY(play_count); + MAYBECOPY(rating); + MAYBECOPY(disc); + MAYBECOPY(total_discs); + MAYBECOPY(time_added); - /* must add to the red-black tree */ - scan_xml_add_lookup(current_track_id,pmp3->id); + /* must add to the red-black tree */ + scan_xml_add_lookup(current_track_id,pmp3->id); - db_add(pmp3); - db_dispose_item(pmp3); + db_add(NULL,pmp3); + db_dispose_item(pmp3); - memset((void*)&mp3,0,sizeof(MP3FILE)); - MAYBEFREE(song_path); - } - } - } else { - return XML_STATE_ERROR; - } - break; + memset((void*)&mp3,0,sizeof(MP3FILE)); + MAYBEFREE(song_path); + } + } + } else { + return XML_STATE_ERROR; + } + break; case XML_TRACK_ST_TRACK_DATA: - if(action == RXML_EVT_BEGIN) { - break; - } else if(action == RXML_EVT_TEXT) { - if(current_field == SCAN_XML_T_NAME) { - mp3.title = strdup(info); - } else if(current_field == SCAN_XML_T_ARTIST) { - mp3.artist = strdup(info); - } else if(current_field == SCAN_XML_T_ALBUM) { - mp3.album = strdup(info); - } else if(current_field == SCAN_XML_T_GENRE) { - mp3.genre = strdup(info); - } else if(current_field == SCAN_XML_T_TOTALTIME) { - mp3.song_length = atoi(info); - } else if(current_field == SCAN_XML_T_TRACKNUMBER) { - mp3.track = atoi(info); - } else if(current_field == SCAN_XML_T_TRACKCOUNT) { - mp3.total_tracks = atoi(info); - } else if(current_field == SCAN_XML_T_YEAR) { - mp3.year = atoi(info); - } else if(current_field == SCAN_XML_T_BITRATE) { - mp3.bitrate = atoi(info); - } else if(current_field == SCAN_XML_T_SAMPLERATE) { - mp3.samplerate = atoi(info); - } else if(current_field == SCAN_XML_T_PLAYCOUNT) { - mp3.play_count = atoi(info); - } else if(current_field == SCAN_XML_T_RATING) { - mp3.rating = atoi(info); - } else if(current_field == SCAN_XML_T_DISCNO) { - mp3.disc = atoi(info); - } else if(current_field == SCAN_XML_T_DISCCOUNT) { - mp3.total_discs = atoi(info); - } else if(current_field == SCAN_XML_T_LOCATION) { - song_path = scan_xml_urldecode(info,0); - } else if(current_field == SCAN_XML_T_DATE_ADDED) { - mp3.time_added = scan_xml_datedecode(info); - } - } else if(action == RXML_EVT_END) { - state = XML_TRACK_ST_TRACK_INFO; - } else { - return XML_STATE_ERROR; - } - break; + if(action == RXML_EVT_BEGIN) { + break; + } else if(action == RXML_EVT_TEXT) { + if(current_field == SCAN_XML_T_NAME) { + mp3.title = strdup(info); + } else if(current_field == SCAN_XML_T_ARTIST) { + mp3.artist = strdup(info); + } else if(current_field == SCAN_XML_T_ALBUM) { + mp3.album = strdup(info); + } else if(current_field == SCAN_XML_T_GENRE) { + mp3.genre = strdup(info); + } else if(current_field == SCAN_XML_T_TOTALTIME) { + mp3.song_length = atoi(info); + } else if(current_field == SCAN_XML_T_TRACKNUMBER) { + mp3.track = atoi(info); + } else if(current_field == SCAN_XML_T_TRACKCOUNT) { + mp3.total_tracks = atoi(info); + } else if(current_field == SCAN_XML_T_YEAR) { + mp3.year = atoi(info); + } else if(current_field == SCAN_XML_T_BITRATE) { + mp3.bitrate = atoi(info); + } else if(current_field == SCAN_XML_T_SAMPLERATE) { + mp3.samplerate = atoi(info); + } else if(current_field == SCAN_XML_T_PLAYCOUNT) { + mp3.play_count = atoi(info); + } else if(current_field == SCAN_XML_T_RATING) { + mp3.rating = atoi(info); + } else if(current_field == SCAN_XML_T_DISCNO) { + mp3.disc = atoi(info); + } else if(current_field == SCAN_XML_T_DISCCOUNT) { + mp3.total_discs = atoi(info); + } else if(current_field == SCAN_XML_T_LOCATION) { + song_path = scan_xml_urldecode(info,0); + } else if(current_field == SCAN_XML_T_DATE_ADDED) { + mp3.time_added = scan_xml_datedecode(info); + } + } else if(action == RXML_EVT_END) { + state = XML_TRACK_ST_TRACK_INFO; + } else { + return XML_STATE_ERROR; + } + break; default: - return XML_STATE_ERROR; + return XML_STATE_ERROR; } return XML_STATE_TRACKS; @@ -738,7 +739,7 @@ int scan_xml_tracks_section(int action, char *info) { state = (c); \ return XML_STATE_PLAYLISTS; \ }} - + /** * collect playlist data for each playlist in the itunes xml file * this again is implemented as a sloppy state machine, and assumes @@ -748,7 +749,7 @@ int scan_xml_tracks_section(int action, char *info) { * @param info text data associated with event */ int scan_xml_playlists_section(int action, char *info) { - static int state = XML_PL_ST_INITIAL; + static int state = XML_PL_ST_INITIAL; static int next_value=0; /** < what's next song info id or name */ static int native_plid=0; /** < the iTunes playlist id */ static int current_id=0; /** < the mt-daapd playlist id */ @@ -761,112 +762,116 @@ int scan_xml_playlists_section(int action, char *info) { /* do initialization */ if(action == RXML_EVT_OPEN) { - state = XML_PL_ST_INITIAL; - if(current_name) - free(current_name); - current_name = NULL; - dont_scan=0; - return 0; + state = XML_PL_ST_INITIAL; + if(current_name) + free(current_name); + current_name = NULL; + dont_scan=0; + return 0; } switch(state) { case XML_PL_ST_INITIAL: - /* expecting or error */ - MAYBESETSTATE_PL(RXML_EVT_BEGIN,"array",XML_PL_ST_EXPECTING_PL); - return XML_STATE_ERROR; + /* expecting or error */ + MAYBESETSTATE_PL(RXML_EVT_BEGIN,"array",XML_PL_ST_EXPECTING_PL); + return XML_STATE_ERROR; case XML_PL_ST_EXPECTING_PL: - /* either a new playlist, or end of playlist list */ - MAYBESETSTATE_PL(RXML_EVT_BEGIN,"dict",XML_PL_ST_EXPECTING_PL_DATA); - if((action == RXML_EVT_END) && (strcasecmp(info,"array") == 0)) - return XML_STATE_PREAMBLE; - return XML_STATE_ERROR; + /* either a new playlist, or end of playlist list */ + MAYBESETSTATE_PL(RXML_EVT_BEGIN,"dict",XML_PL_ST_EXPECTING_PL_DATA); + if((action == RXML_EVT_END) && (strcasecmp(info,"array") == 0)) + return XML_STATE_PREAMBLE; + return XML_STATE_ERROR; case XML_PL_ST_EXPECTING_PL_DATA: - /* either a key/data pair, or an array, signaling start of playlist - * or the end of the dict (end of playlist data) */ - MAYBESETSTATE_PL(RXML_EVT_BEGIN,"key",XML_PL_ST_EXPECTING_PL_DATA); - MAYBESETSTATE_PL(RXML_EVT_END,"key",XML_PL_ST_EXPECTING_PL_VALUE); - MAYBESETSTATE_PL(RXML_EVT_END,"dict",XML_PL_ST_EXPECTING_PL); - if(action == RXML_EVT_TEXT) { - next_value=XML_PL_NEXT_VALUE_NONE; - if(strcasecmp(info,"Name") == 0) { - next_value = XML_PL_NEXT_VALUE_NAME; - } else if(strcasecmp(info,"Playlist ID") == 0) { - next_value = XML_PL_NEXT_VALUE_ID; - } else if(strcasecmp(info,"Master") == 0) { - /* No point adding the master library... we have one */ - dont_scan=1; - } - return XML_STATE_PLAYLISTS; - } - return XML_STATE_ERROR; + /* either a key/data pair, or an array, signaling start of playlist + * or the end of the dict (end of playlist data) */ + MAYBESETSTATE_PL(RXML_EVT_BEGIN,"key",XML_PL_ST_EXPECTING_PL_DATA); + MAYBESETSTATE_PL(RXML_EVT_END,"key",XML_PL_ST_EXPECTING_PL_VALUE); + MAYBESETSTATE_PL(RXML_EVT_END,"dict",XML_PL_ST_EXPECTING_PL); + if(action == RXML_EVT_TEXT) { + next_value=XML_PL_NEXT_VALUE_NONE; + if(strcasecmp(info,"Name") == 0) { + next_value = XML_PL_NEXT_VALUE_NAME; + } else if(strcasecmp(info,"Playlist ID") == 0) { + next_value = XML_PL_NEXT_VALUE_ID; + } else if(strcasecmp(info,"Master") == 0) { + /* No point adding the master library... we have one */ + dont_scan=1; + } + return XML_STATE_PLAYLISTS; + } + return XML_STATE_ERROR; case XML_PL_ST_EXPECTING_PL_VALUE: - /* any tag, value we are looking for, any close tag */ - if((action == RXML_EVT_BEGIN) && (strcasecmp(info,"array") == 0)) { - /* we are about to get track list... must register the playlist */ - current_id=0; - if(dont_scan == 0) { - DPRINTF(E_DBG,L_SCAN,"Creating playlist for %s\n",current_name); - /* delete the old one first */ - pm3u = db_fetch_playlist(scan_xml_file,native_plid); - if(pm3u) { - db_delete_playlist(pm3u->id); - db_dispose_playlist(pm3u); - } - if(db_add_playlist(current_name,PL_STATICXML,NULL,scan_xml_file, - native_plid,¤t_id) != DB_E_SUCCESS) { - DPRINTF(E_LOG,L_SCAN,"err adding playlist %s\n",current_name); - current_id=0; - } - } - dont_scan=0; - state=XML_PL_ST_EXPECTING_PL_TRACKLIST; - return XML_STATE_PLAYLISTS; - } - if(action == RXML_EVT_BEGIN) - return XML_STATE_PLAYLISTS; - if(action == RXML_EVT_END) { - state = XML_PL_ST_EXPECTING_PL_DATA; - return XML_STATE_PLAYLISTS; - } - if(action == RXML_EVT_TEXT) { - /* got the value we were hoping for */ - if(next_value == XML_PL_NEXT_VALUE_NAME) { - if(current_name) - free(current_name); - current_name = strdup(info); - /* disallow specific playlists */ - if(strcasecmp(current_name,"Party Shuffle") == 0) { - dont_scan=1; - } - } else if(next_value == XML_PL_NEXT_VALUE_ID) { - native_plid = atoi(info); - } - return XML_STATE_PLAYLISTS; - } - return XML_STATE_ERROR; + /* any tag, value we are looking for, any close tag */ + if((action == RXML_EVT_BEGIN) && (strcasecmp(info,"array") == 0)) { + /* we are about to get track list... must register the playlist */ + current_id=0; + if(dont_scan == 0) { + DPRINTF(E_DBG,L_SCAN,"Creating playlist for %s\n",current_name); + /* delete the old one first */ + /* FIXME: Error handling */ + pm3u = db_fetch_playlist(NULL,scan_xml_file,native_plid); + if(pm3u) { + db_delete_playlist(NULL,pm3u->id); + db_dispose_playlist(pm3u); + } + if(db_add_playlist(NULL,current_name,PL_STATICXML,NULL, + scan_xml_file,native_plid, + ¤t_id) != DB_E_SUCCESS) + { + DPRINTF(E_LOG,L_SCAN,"err adding playlist %s\n",current_name); + current_id=0; + } + } + dont_scan=0; + state=XML_PL_ST_EXPECTING_PL_TRACKLIST; + return XML_STATE_PLAYLISTS; + } + if(action == RXML_EVT_BEGIN) + return XML_STATE_PLAYLISTS; + if(action == RXML_EVT_END) { + state = XML_PL_ST_EXPECTING_PL_DATA; + return XML_STATE_PLAYLISTS; + } + if(action == RXML_EVT_TEXT) { + /* got the value we were hoping for */ + if(next_value == XML_PL_NEXT_VALUE_NAME) { + if(current_name) + free(current_name); + current_name = strdup(info); + /* disallow specific playlists */ + if(strcasecmp(current_name,"Party Shuffle") == 0) { + dont_scan=1; + } + } else if(next_value == XML_PL_NEXT_VALUE_ID) { + native_plid = atoi(info); + } + return XML_STATE_PLAYLISTS; + } + return XML_STATE_ERROR; case XML_PL_ST_EXPECTING_PL_TRACKLIST: - if((strcasecmp(info,"dict") == 0) || (strcasecmp(info,"key") == 0)) - return XML_STATE_PLAYLISTS; - MAYBESETSTATE_PL(RXML_EVT_END,"array",XML_PL_ST_EXPECTING_PL_DATA); + if((strcasecmp(info,"dict") == 0) || (strcasecmp(info,"key") == 0)) + return XML_STATE_PLAYLISTS; + MAYBESETSTATE_PL(RXML_EVT_END,"array",XML_PL_ST_EXPECTING_PL_DATA); if(action == RXML_EVT_TEXT) { - if(strcasecmp(info,"Track ID") != 0) { - native_track_id = atoi(info); - DPRINTF(E_DBG,L_SCAN,"Adding itunes track #%s\n",info); - /* add it to the current playlist (current_id) */ - if(current_id && scan_xml_get_index(native_track_id, &track_id)) { - db_add_playlist_item(current_id,track_id); - } - } + if(strcasecmp(info,"Track ID") != 0) { + native_track_id = atoi(info); + DPRINTF(E_DBG,L_SCAN,"Adding itunes track #%s\n",info); + /* add it to the current playlist (current_id) */ + if(current_id && scan_xml_get_index(native_track_id, &track_id)) { + /* FIXME: Error handling */ + db_add_playlist_item(NULL,current_id,track_id); + } + } - return XML_STATE_PLAYLISTS; - } - return XML_STATE_PLAYLISTS; + return XML_STATE_PLAYLISTS; + } + return XML_STATE_PLAYLISTS; default: - return XML_STATE_ERROR; + return XML_STATE_ERROR; } - + return XML_STATE_PLAYLISTS; } diff --git a/src/xml-rpc.c b/src/xml-rpc.c index 932eb467..92589dc4 100644 --- a/src/xml-rpc.c +++ b/src/xml-rpc.c @@ -27,7 +27,7 @@ typedef struct tag_xmlstack { typedef struct tag_xmlstruct { WS_CONNINFO *pwsc; int stack_level; - XMLSTACK stack; + XMLSTACK stack; } XMLSTRUCT; /* Forwards */ @@ -50,16 +50,16 @@ void xml_deinit(XMLSTRUCT *pxml); */ XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header) { XMLSTRUCT *pxml; - + pxml=(XMLSTRUCT*)malloc(sizeof(XMLSTRUCT)); if(!pxml) { DPRINTF(E_FATAL,L_XML,"Malloc error\n"); } - + memset(pxml,0,sizeof(XMLSTRUCT)); - + pxml->pwsc = pwsc; - + if(emit_header) { ws_addresponseheader(pwsc,"Content-Type","text/xml; charset=utf-8"); ws_writefd(pwsc,"HTTP/1.0 200 OK\r\n"); @@ -67,7 +67,7 @@ XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header) { ws_writefd(pwsc,""); } - + return pxml; } @@ -75,16 +75,16 @@ XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header) { * push a new term on the stack * * @param pxml xml struct obtained from xml_init - * @param term next xlm section to start + * @param term next xlm section to start */ void xml_push(XMLSTRUCT *pxml, char *term) { XMLSTACK *pstack; - + pstack = (XMLSTACK *)malloc(sizeof(XMLSTACK)); pstack->next=pxml->stack.next; pstack->tag=strdup(term); pxml->stack.next=pstack; - + pxml->stack_level++; ws_writefd(pxml->pwsc,"<%s>",term); @@ -97,19 +97,19 @@ void xml_push(XMLSTRUCT *pxml, char *term) { */ void xml_pop(XMLSTRUCT *pxml) { XMLSTACK *pstack; - + pstack=pxml->stack.next; if(!pstack) { DPRINTF(E_LOG,L_XML,"xml_pop: tried to pop an empty stack\n"); return; } - + pxml->stack.next = pstack->next; - + ws_writefd(pxml->pwsc,"",pstack->tag); free(pstack->tag); free(pstack); - + pxml->stack_level--; } @@ -143,11 +143,11 @@ void xml_output(XMLSTRUCT *pxml, char *section, char *fmt, ...) { */ void xml_deinit(XMLSTRUCT *pxml) { XMLSTACK *pstack; - + if(pxml->stack.next) { DPRINTF(E_LOG,L_XML,"xml_deinit: entries still on stack (%s)\n",pxml->stack.next->tag); } - + while((pstack=pxml->stack.next)) { pxml->stack.next=pstack->next; free(pstack->tag); @@ -187,18 +187,18 @@ void xml_get_stats(WS_CONNINFO *pwsc) { WS_CONNINFO *pci; SCAN_STATUS *pss; WSTHREADENUM wste; - + int count; XMLSTRUCT *pxml; - + pxml=xml_init(pwsc,1); xml_push(pxml,"status"); xml_push(pxml,"service_status"); xml_push(pxml,"service"); - + xml_output(pxml,"name","Rendezvous"); - + #ifndef WITHOUT_MDNS if(config.use_mdns) { xml_output(pxml,"status",rend_running() ? "Stopped" : "Running"); /* ??? */ @@ -222,7 +222,7 @@ void xml_get_stats(WS_CONNINFO *pwsc) { xml_pop(pxml); /* service */ xml_pop(pxml); /* service_status */ - + xml_push(pxml,"thread_status"); pci = ws_thread_enum_first(config.server,&wste); @@ -237,11 +237,11 @@ void xml_get_stats(WS_CONNINFO *pwsc) { } pci=ws_thread_enum_next(config.server,&wste); } - + xml_pop(pxml); /* thread_status */ xml_push(pxml,"statistics"); - + r_secs=time(NULL)-config.stats.start_time; r_days=r_secs/(3600 * 24); @@ -254,36 +254,37 @@ void xml_get_stats(WS_CONNINFO *pwsc) { r_secs -= 60 * r_mins; memset(buf,0x0,sizeof(buf)); - if(r_days) - sprintf((char*)&buf[strlen(buf)],"%d day%s, ", r_days, - r_days == 1 ? "" : "s"); + if(r_days) + sprintf((char*)&buf[strlen(buf)],"%d day%s, ", r_days, + r_days == 1 ? "" : "s"); - if(r_days || r_hours) - sprintf((char*)&buf[strlen(buf)],"%d hour%s, ", r_hours, - r_hours == 1 ? "" : "s"); + if(r_days || r_hours) + sprintf((char*)&buf[strlen(buf)],"%d hour%s, ", r_hours, + r_hours == 1 ? "" : "s"); if(r_days || r_hours || r_mins) - sprintf((char*)&buf[strlen(buf)],"%d minute%s, ", r_mins, - r_mins == 1 ? "" : "s"); + sprintf((char*)&buf[strlen(buf)],"%d minute%s, ", r_mins, + r_mins == 1 ? "" : "s"); sprintf((char*)&buf[strlen(buf)],"%d second%s ", r_secs, - r_secs == 1 ? "" : "s"); - + r_secs == 1 ? "" : "s"); + xml_push(pxml,"stat"); xml_output(pxml,"name","Uptime"); xml_output(pxml,"value","%s",buf); xml_pop(pxml); /* stat */ - + xml_push(pxml,"stat"); xml_output(pxml,"name","Songs"); - xml_output(pxml,"value","%d",db_get_song_count()); + db_get_song_count(NULL,&count); + xml_output(pxml,"value","%d",count); xml_pop(pxml); /* stat */ - + xml_push(pxml,"stat"); xml_output(pxml,"name","Songs Served"); xml_output(pxml,"value","%d",config.stats.songs_served); xml_pop(pxml); /* stat */ - + xml_pop(pxml); /* statistics */ xml_pop(pxml); /* status */
ThreadSessionHostAction