owntone-server/src/dbs-sqlite.c

1341 lines
38 KiB
C

/*
* $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 <errno.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite.h>
#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;
}