owntone-server/src/dbs-sqlite.c

1835 lines
55 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 500
#include <errno.h>
#include <fcntl.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 "restart.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;
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);
#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) {
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) {
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 id 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 id=%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 id=%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 id=%d and songid=%d",
playlistid,songid);
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;
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 */
result=db_sqlite_get_int(E_DBG,&cnt,"select count (*) from songs where %s",clause);
if(result != DB_E_SUCCESS) return result;
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));
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 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
"'%d'," // index
"'%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
STR(pmp3->path),
pmp3->index,
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));
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;
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
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 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;
}
is_smart=(atoi(resarray[2]) == 1);
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);
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[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(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[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(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_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 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 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 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",
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;
}