/* * $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 600 #include #include #include #include #include #include #include #include "err.h" #include "mp3-scanner.h" #include "db-generic.h" #include "dbs-sqlite.h" #include "ssc.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; /* Forwards */ int db_sqlite_get_size(DBQUERYINFO *pinfo, char **valarray); int db_sqlite_build_dmap(DBQUERYINFO *pinfo, char **valarray, 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, 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); #define STR(a) (a) ? (a) : "" #define ISSTR(a) ((a) && strlen((a))) #define MAYBEFREE(a) { if((a)) free((a)); }; /** * 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) 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); db_sqlite_unlock(); return err; } return 0; } /** * 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); 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, char *fmt, ...) { int rows, cols; char **resarray; va_list ap; char *query; int err; char *perr; int retval; 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); db_sqlite_unlock(); return 0; } retval=atoi(resarray[cols]); sqlite_free_table(resarray); return retval; } /** * open sqlite database */ int db_sqlite_open(char *parameters) { char db_path[PATH_MAX + 1]; 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_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; db_sqlite_update_version(db_sqlite_get_version()); rescan=db_sqlite_get_int(E_DBG,"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"); } 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) { 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_in_scan=1; return 0; } /** * stop a db scan */ int db_sqlite_end_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'"); db_sqlite_exec(E_FATAL,"PRAGMA synchronous=NORMAL"); } 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_update_playlists(); db_sqlite_reload=0; db_sqlite_in_scan=0; return 0; } /** * 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 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); 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_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 "compilation=%d," // compilation "rating=%d," // rating "sample_count=%d" // sample_count " 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->compilation, pmp3->rating, pmp3->sample_count, 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_update_playlists(); return 0; } /** * Update the playlist item counts */ int db_sqlite_update_playlists(void) { char **resarray; int rows, cols, index; 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])) { // is a smart playlist db_sqlite_exec(E_FATAL,"UPDATE playlists SET items=(SELECT COUNT(*) " "FROM songs WHERE %s) WHERE id=%s",resarray[cols * index + 4], resarray[cols * index]); } else { db_sqlite_exec(E_FATAL,"UPDATE playlists SET items=(SELECT COUNT(*) " "FROM playlistitems WHERE id=%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]; 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 smart,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); db_sqlite_unlock(); return -1; } is_smart=atoi(resarray[2]); have_clause=1; if(is_smart) { sprintf(query_select,"SELECT * FROM songs "); sprintf(query_count,"SELECT COUNT(id) FROM songs "); sprintf(query_rest,"WHERE (%s)",resarray[3]); } else { sprintf(query_select,"SELECT * FROM songs "); sprintf(query_count,"SELECT COUNT(id) FROM songs "); sprintf(query_rest,"WHERE (id IN (SELECT songid FROM playlistitems WHERE id=%d))", pinfo->playlist_id); } 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); 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); 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); db_sqlite_unlock(); DPRINTF(E_FATAL,L_DB,"sqlite_step: %s\n",perr); } 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); db_sqlite_unlock(); DPRINTF(E_FATAL,L_DB,"sqlite_step: %s\n",perr); 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; db_sqlite_lock(); sqlite_finalize(db_sqlite_pvm,&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; /* miid */ size += 12; /* mimc */ size += 9; /* aeSP */ size += (8 + strlen(valarray[1])); /* minm */ return size; break; case queryTypeItems: case queryTypePlaylistItems: /* essentially the same query */ /* see if this is going to be transcoded */ transcode = server_side_convert(valarray[2]); /* 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(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; 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, 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); current += db_dmap_add_int(current,"miid",atoi(valarray[0])); current += db_dmap_add_int(current,"mimc",atoi(valarray[3])); current += db_dmap_add_char(current,"aeSP",atoi(valarray[2])); current += db_dmap_add_string(current,"minm",valarray[1]); break; case queryTypeItems: case queryTypePlaylistItems: /* essentially the same query */ /* see if this is going to be transcoded */ transcode = server_side_convert(valarray[2]); /* 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(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(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_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]); } /** * 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 rows,cols; char **resarray; MP3FILE *pmp3=NULL; db_sqlite_get_table(E_DBG,&resarray,&rows,&cols,"SELECT * FROM songs WHERE path='%q'",path); 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); free(pmp3); } /** * 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", 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) { 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); db_sqlite_exec(E_FATAL,db_sqlite_upgrade_scripts[from_version]); from_version++; } return 0; }