owntone-server/src/db-sql.c

1864 lines
59 KiB
C

/*
* $Id$
* sql-specific db implementation
*
* Copyright (C) 2005 Ron Pedde (ron@pedde.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define _XOPEN_SOURCE 500
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "err.h"
#include "mp3-scanner.h"
#include "db-generic.h"
#include "db-sql.h"
#include "restart.h"
#include "ssc.h"
#include "smart-parser.h"
#ifdef HAVE_LIBSQLITE
#include "db-sql-sqlite2.h"
#endif
#ifdef HAVE_LIBSQLITE3
#include "db-sql-sqlite3.h"
#endif
/* Globals */
static int db_sql_reload=0;
static int db_sql_in_playlist_scan=0;
static int db_sql_in_scan=0;
static int db_sql_need_dispose=0;
/* Forwards */
int db_sql_get_size(DBQUERYINFO *pinfo, char **valarray);
int db_sql_build_dmap(DBQUERYINFO *pinfo, char **valarray, unsigned char *presult, int len);
void db_sql_build_mp3file(char **valarray, MP3FILE *pmp3);
int db_sql_update(char **pe, MP3FILE *pmp3, int *id);
int db_sql_update_playlists(char **pe);
int db_sql_parse_smart(char **pe, char **clause, char *phrase);
#define STR(a) (a) ? (a) : ""
#define ISSTR(a) ((a) && strlen((a)))
#define MAYBEFREE(a) { if((a)) free((a)); };
#define DMAPLEN(a) (((a) && strlen(a)) ? (8+(int)strlen((a))) : \
((pinfo->zero_length) ? 8 : 0))
#define EMIT(a) (pinfo->zero_length ? 1 : ((a) && strlen((a))) ? 1 : 0)
/**
* functions for the specific db backend
*/
int (*db_sql_open_fn)(char **pe, char *dsn) = NULL;
int (*db_sql_close_fn)(void) = NULL;
int (*db_sql_exec_fn)(char **pe, int loglevel, char *fmt, ...) = NULL;
char* (*db_sql_vmquery_fn)(char *fmt,va_list ap) = NULL;
void (*db_sql_vmfree_fn)(char *query) = NULL;
int (*db_sql_enum_begin_fn)(char **pe, char *fmt, ...) = NULL;
int (*db_sql_enum_fetch_fn)(char **pe, SQL_ROW *pr) = NULL;
int (*db_sql_enum_end_fn)(char **pe) = NULL;
int (*db_sql_enum_restart_fn)(char **pe);
int (*db_sql_event_fn)(int event_type);
int (*db_sql_insert_id_fn)(void);
#ifdef HAVE_LIBSQLITE
int db_sql_open_sqlite2(char **pe, char *parameters) {
/* first, set our external links to sqlite2 */
db_sql_open_fn = db_sqlite2_open;
db_sql_close_fn = db_sqlite2_close;
db_sql_exec_fn = db_sqlite2_exec;
db_sql_vmquery_fn = db_sqlite2_vmquery;
db_sql_vmfree_fn = db_sqlite2_vmfree;
db_sql_enum_begin_fn = db_sqlite2_enum_begin;
db_sql_enum_fetch_fn = db_sqlite2_enum_fetch;
db_sql_enum_end_fn = db_sqlite2_enum_end;
db_sql_enum_restart_fn = db_sqlite2_enum_restart;
db_sql_event_fn = db_sqlite2_event;
db_sql_insert_id_fn = db_sqlite2_insert_id;
return db_sql_open(pe,parameters);
}
#endif
#ifdef HAVE_LIBSQLITE3
int db_sql_open_sqlite3(char **pe, char *parameters) {
/* first, set our external links to sqlite2 */
db_sql_open_fn = db_sqlite3_open;
db_sql_close_fn = db_sqlite3_close;
db_sql_exec_fn = db_sqlite3_exec;
db_sql_vmquery_fn = db_sqlite3_vmquery;
db_sql_vmfree_fn = db_sqlite3_vmfree;
db_sql_enum_begin_fn = db_sqlite3_enum_begin;
db_sql_enum_fetch_fn = db_sqlite3_enum_fetch;
db_sql_enum_end_fn = db_sqlite3_enum_end;
db_sql_enum_restart_fn = db_sqlite3_enum_restart;
db_sql_event_fn = db_sqlite3_event;
db_sql_insert_id_fn = db_sqlite3_insert_id;
return db_sql_open(pe,parameters);
}
#endif
/**
* escape a sql string, returning it the supplied buffer.
* note that this uses the sqlite escape format -- use %q for quoted
* sql.
*
* @param buffer buffer to throw the escaped sql into
* @param size size of buffer (or size required, if DB_E_SIZE)
* @param fmt printf style format
* @returns DB_E_SUCCESS with buffer filled, or DB_E_SIZE, with size
* set to the required size
*/
int db_sql_escape(char *buffer, int *size, char *fmt, ...) {
va_list ap;
char *escaped;
va_start(ap,fmt);
escaped = db_sql_vmquery_fn(fmt,ap);
va_end(ap);
if(*size < (int)strlen(escaped)) {
*size = (int)strlen(escaped) + 1;
db_sql_vmfree_fn(escaped);
return DB_E_SIZE;
}
strcpy(buffer,escaped);
*size = (int)strlen(escaped);
db_sql_vmfree_fn(escaped);
return DB_E_SUCCESS;
}
/**
* fetch a single row, using the underlying database enum
* functions
*/
int db_sql_fetch_row(char **pe, SQL_ROW *row, char *fmt, ...) {
int err;
char *query;
va_list ap;
db_sql_need_dispose = 0;
*row=NULL;
va_start(ap,fmt);
query=db_sql_vmquery_fn(fmt,ap);
va_end(ap);
err=db_sql_enum_begin_fn(pe,"%s",query);
db_sql_vmfree_fn(query);
if(err != DB_E_SUCCESS) {
DPRINTF(E_SPAM,L_DB,"Error: enum_begin failed: %s\n",(pe) ? *pe : "?");
return err;
}
err=db_sql_enum_fetch_fn(pe, row);
if(err != DB_E_SUCCESS) {
DPRINTF(E_SPAM,L_DB,"Error: enum_fetch failed: %s\n",*pe);
db_sql_enum_end_fn(NULL);
return err;
}
if(!(*row)) {
db_sql_enum_end_fn(NULL);
db_get_error(pe,DB_E_NOROWS);
return DB_E_NOROWS;
}
db_sql_need_dispose = 1;
return DB_E_SUCCESS;
}
int db_sql_fetch_int(char **pe, int *result, char *fmt, ...) {
int err;
char *query;
va_list ap;
SQL_ROW row;
va_start(ap,fmt);
query=db_sql_vmquery_fn(fmt,ap);
va_end(ap);
err = db_sql_fetch_row(pe, &row, "%s", query);
db_sql_vmfree_fn(query);
if(err != DB_E_SUCCESS) {
DPRINTF(E_SPAM,L_DB,"fetch_row failed in fetch_int: %s\n",pe ? *pe : NULL);
return err;
}
*result = atoi(row[0]);
db_sql_dispose_row();
return DB_E_SUCCESS;
}
int db_sql_fetch_char(char **pe, char **result, char *fmt, ...) {
int err;
char *query;
va_list ap;
SQL_ROW row;
va_start(ap,fmt);
query=db_sql_vmquery_fn(fmt,ap);
va_end(ap);
err = db_sql_fetch_row(pe, &row, "%s", query);
if(err != DB_E_SUCCESS)
return err;
*result = strdup(row[0]);
db_sql_dispose_row();
return DB_E_SUCCESS;
}
int db_sql_dispose_row(void) {
int err = DB_E_SUCCESS;
/* don't really need the row */
if(db_sql_need_dispose) {
err=db_sql_enum_end_fn(NULL);
db_sql_need_dispose=0;
}
return err;
}
/**
* get the sql where clause for a smart playlist spec. This
* where clause must be freed by the caller
*
* @param phrase playlist spec to be converted
* @returns sql where clause if successful, NULL otherwise
*/
int db_sql_parse_smart(char **pe, char **clause, char *phrase) {
PARSETREE pt;
if(strcmp(phrase,"1") == 0) {
*clause = strdup("1");
return TRUE;
}
pt=sp_init();
if(!pt) {
if(pe) *pe = strdup("Could not initialize parse tree");
return FALSE;
}
if(!sp_parse(pt,phrase,SP_TYPE_PLAYLIST)) {
if(pe) *pe = strdup(sp_get_error(pt));
DPRINTF(E_LOG,L_DB,"Error parsing playlist: %s\n",sp_get_error(pt));
sp_dispose(pt);
return FALSE;
} else {
*clause = sp_sql_clause(pt);
}
sp_dispose(pt);
return TRUE;
}
/**
* open sqlite database
*
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_open(char **pe, char *parameters) {
int result;
result = db_sql_open_fn(pe,parameters);
return result;
}
/**
* initialize the sqlite database, reloading if requested
*
* @param reload whether or not to do a full reload on the db
* on return, reload is set to 1 if the db MUST be rescanned
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_init(int *reload) {
int items;
int rescan = 0;
int err;
err=db_sql_get_count(NULL,&items, countSongs);
if(err != DB_E_SUCCESS)
items = 0;
/* check if a request has been written into the db (by a db upgrade?) */
if(db_sql_fetch_int(NULL,&rescan,"select value from config where "
"term='rescan'") == DB_E_SUCCESS)
{
if(rescan)
*reload=1;
}
if(*reload || (!items)) {
DPRINTF(E_LOG,L_DB,"Full reload...\n");
db_sql_event_fn(DB_SQL_EVENT_FULLRELOAD);
db_sql_reload=1;
*reload=1;
} else {
db_sql_event_fn(DB_SQL_EVENT_STARTUP);
db_sql_reload=0;
*reload=0;
}
return DB_E_SUCCESS;
}
/**
* close the database
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_deinit(void) {
return db_sql_close_fn();
}
/**
* start a background scan
*
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_start_scan(void) {
DPRINTF(E_DBG,L_DB,"Starting db scan\n");
db_sql_event_fn(DB_SQL_EVENT_SONGSCANSTART);
db_sql_in_scan=1;
db_sql_in_playlist_scan=0;
return DB_E_SUCCESS;
}
/**
* end song scan -- start playlist scan
*
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_end_song_scan(void) {
DPRINTF(E_DBG,L_DB,"Ending song scan\n");
db_sql_event_fn(DB_SQL_EVENT_SONGSCANEND);
db_sql_event_fn(DB_SQL_EVENT_PLSCANSTART);
db_sql_in_scan=0;
db_sql_in_playlist_scan=1;
return DB_E_SUCCESS;
}
/**
* stop a db scan
*
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_end_scan(void) {
db_sql_event_fn(DB_SQL_EVENT_PLSCANEND);
db_sql_update_playlists(NULL);
db_sql_reload=0;
db_sql_in_playlist_scan=0;
return DB_E_SUCCESS;
}
/**
* delete a playlist
*
* @param playlistid playlist to delete
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_delete_playlist(char **pe, int playlistid) {
int type;
int result;
result=db_sql_fetch_int(pe,&type,"select type from playlists where id=%d",
playlistid);
if(result != DB_E_SUCCESS) {
if(result == DB_E_NOROWS) { /* Override the generic error */
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_PLAYLIST);
return DB_E_INVALID_PLAYLIST;
}
return result;
}
/* got a good playlist, now do what we need to do */
db_sql_exec_fn(pe,E_FATAL,"delete from playlists where id=%d",playlistid);
db_sql_exec_fn(pe,E_FATAL,"delete from playlistitems where playlistid=%d",playlistid);
return DB_E_SUCCESS;
}
/**
* delete a song from a playlist
*
* @param playlistid playlist to delete item from
* @param songid song to delete from playlist
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_delete_playlist_item(char **pe, int playlistid, int songid) {
int result;
int playlist_type;
int count;
/* first, check the playlist */
result=db_sql_fetch_int(pe,&playlist_type,
"select type from playlists where id=%d",
playlistid);
if(result != DB_E_SUCCESS) {
if(result == DB_E_NOROWS) { /* Override generic error */
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_PLAYLIST);
return DB_E_INVALID_PLAYLIST;
}
return result;
}
if(playlist_type == PL_SMART) { /* can't delete from a smart playlist */
db_get_error(pe,DB_E_INVALIDTYPE);
return DB_E_INVALIDTYPE;
}
/* make sure the songid is valid */
result=db_sql_fetch_int(pe,&count,"select count(*) from playlistitems "
"where playlistid=%d and songid=%d",
playlistid,songid);
if(result != DB_E_SUCCESS) {
if(result == DB_E_NOROWS) { /* Override generic error */
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_SONGID);
return DB_E_INVALID_SONGID;
}
return result;
}
/* looks valid, so lets add the item */
result=db_sql_exec_fn(pe,E_DBG,"delete from playlistitems where "
"playlistid=%d and songid=%d",playlistid,songid);
return result;
}
/**
* edit a playlist. The only things worth changing are the name
* and the "where" clause.
*
* @param id id of the playlist to alter
* @param name new name of the playlist (or NULL)
* @param where new where clause (or NULL)
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_edit_playlist(char **pe, int id, char *name, char *clause) {
int result;
int playlist_type;
int dup_id=id;
if((name == NULL) && (clause == NULL))
return DB_E_SUCCESS; /* I guess?? */
if(id == 1) { /* can't edit the library query */
db_get_error(pe,DB_E_INVALID_PLAYLIST);
return DB_E_INVALID_PLAYLIST;
}
/* first, check the playlist */
result=db_sql_fetch_int(pe,&playlist_type,
"select type from playlists where id=%d",id);
if(result != DB_E_SUCCESS) {
if(result == DB_E_NOROWS) { /* Override generic error */
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_PLAYLIST);
return DB_E_INVALID_PLAYLIST;
}
return result;
}
/* TODO: check for duplicate names here */
if(name) {
result = db_sql_fetch_int(pe,&dup_id,"select id from playlists "
"where upper(title)=upper('%q')",name);
if((result != DB_E_SUCCESS) && (result != DB_E_NOROWS))
return result;
if(result == DB_E_NOROWS)
if(pe) free(*pe);
if(dup_id != id) {
db_get_error(pe,DB_E_DUPLICATE_PLAYLIST,name);
return DB_E_DUPLICATE_PLAYLIST;
}
if((playlist_type == PL_SMART)&&(clause))
return db_sql_exec_fn(pe,E_LOG,"update playlists set title='%q', "
"query='%q' where id=%d",name,clause,id);
return db_sql_exec_fn(pe,E_LOG,"update playlists set title='%q' "
"where id=%d",name,id);
}
if((playlist_type == PL_SMART) && (clause))
return db_sql_exec_fn(pe,E_LOG,"update playlists set query='%q' "
"where id=%d",clause,id);
return DB_E_SUCCESS; /* ?? */
}
/**
* add a playlist
*
* @param name name of the playlist
* @param type playlist type: 0 - static, 1 - smart, 2 - m3u
* @param clause: "where" clause for smart playlist
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_add_playlist(char **pe, char *name, int type, char *clause, char *path, int index, int *playlistid) {
int cnt=0;
int result=DB_E_SUCCESS;
char *criteria;
char *estring;
result=db_sql_fetch_int(pe,&cnt,"select count(*) from playlists where "
"upper(title)=upper('%q')",name);
if(result != DB_E_SUCCESS) {
return result;
}
if(cnt != 0) { /* duplicate */
db_get_error(pe,DB_E_DUPLICATE_PLAYLIST,name);
return DB_E_DUPLICATE_PLAYLIST;
}
if((type == PL_SMART) && (!clause)) {
db_get_error(pe,DB_E_NOCLAUSE);
return DB_E_NOCLAUSE;
}
/* Let's throw it in */
switch(type) {
case PL_STATICWEB: /* static, maintained in web interface */
case PL_STATICFILE: /* static, from file */
case PL_STATICXML: /* from iTunes XML file */
result = db_sql_exec_fn(pe,E_LOG,"insert into playlists "
"(title,type,items,query,db_timestamp,path,idx) "
"values ('%q',%d,0,NULL,%d,'%q',%d)",
name,type,time(NULL),path,index);
break;
case PL_SMART: /* smart */
if(!db_sql_parse_smart(&estring,&criteria,clause)) {
db_get_error(pe,DB_E_PARSE,estring);
free(estring);
return DB_E_PARSE;
}
free(criteria);
result = db_sql_exec_fn(pe,E_LOG,"insert into playlists "
"(title,type,items,query,db_timestamp,idx) "
"values ('%q',%d,%d,'%q',%d,0)",
name,PL_SMART,cnt,clause,time(NULL));
break;
}
if(result)
return result;
result = db_sql_fetch_int(pe,playlistid,
"select id from playlists where title='%q'",
name);
if(((type==PL_STATICFILE)||(type==PL_STATICXML))
&& (db_sql_in_playlist_scan) && (!db_sql_reload)) {
db_sql_exec_fn(NULL,E_FATAL,"insert into plupdated values (%d)",*playlistid);
}
return result;
}
/**
* add a song to a static playlist
*
* @param playlistid playlist to add song to
* @param songid song to add
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_add_playlist_item(char **pe, int playlistid, int songid) {
int result;
int playlist_type;
int count;
/* first, check the playlist */
result=db_sql_fetch_int(pe,&playlist_type,
"select type from playlists where id=%d",
playlistid);
if(result != DB_E_SUCCESS) {
if(result == DB_E_NOROWS) { /* Override generic error */
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_PLAYLIST);
return DB_E_INVALID_PLAYLIST;
}
return result;
}
if(playlist_type == 1) { /* can't add to smart playlists, or static */
db_get_error(pe,DB_E_INVALIDTYPE);
return DB_E_INVALIDTYPE;
}
/* make sure the songid is valid */
result=db_sql_fetch_int(pe,&count,"select count(*) from songs where "
"id=%d",songid);
if(result != DB_E_SUCCESS) {
if(result == DB_E_NOROWS) { /* Override generic error */
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_SONGID);
return DB_E_INVALID_SONGID;
}
return result;
}
/* looks valid, so lets add the item */
result=db_sql_exec_fn(pe,E_DBG,"insert into playlistitems "
"(playlistid, songid) values (%d,%d)",
playlistid,songid);
return result;
}
/**
* add a database item
*
* @param pmp3 mp3 file to add
*/
int db_sql_add(char **pe, MP3FILE *pmp3, int *id) {
int err;
int count;
int insertid;
DPRINTF(E_SPAM,L_DB,"Entering db_sql_add\n");
if(!pmp3->time_added)
pmp3->time_added = (int)time(NULL);
if(!pmp3->time_modified)
pmp3->time_modified = (int)time(NULL);
pmp3->db_timestamp = (int)time(NULL);
/* Always an add if in song scan on full reload */
if((!db_sql_reload)||(!db_sql_in_scan)) {
err=db_sql_fetch_int(NULL,&count,"select count(*) from songs where "
"path='%q'",pmp3->path);
if((err == DB_E_SUCCESS) && (count == 1)) { /* we should update */
return db_sql_update(pe,pmp3,id);
} else if((err != DB_E_SUCCESS) && (err != DB_E_NOROWS)) {
DPRINTF(E_LOG,L_DB,"Error: %s\n",pe);
return err;
}
}
pmp3->play_count=0;
pmp3->time_played=0;
err=db_sql_exec_fn(pe,E_DBG,"INSERT INTO songs VALUES "
"(NULL," // id
"'%q'," // path
"'%q'," // fname
"'%q'," // title
"'%q'," // artist
"'%q'," // album
"'%q'," // genre
"'%q'," // comment
"'%q'," // type
"'%q'," // composer
"'%q'," // orchestra
"'%q'," // conductor
"'%q'," // grouping
"'%q'," // url
"%d," // bitrate
"%d," // samplerate
"%d," // song_length
"%d," // file_size
"%d," // year
"%d," // track
"%d," // total_tracks
"%d," // disc
"%d," // total_discs
"%d," // bpm
"%d," // compilation
"%d," // rating
"0," // play_count
"%d," // data_kind
"%d," // item_kind
"'%q'," // description
"%d," // time_added
"%d," // time_modified
"%d," // time_played
"%d," // db_timestamp
"%d," // disabled
"%d," // sample_count
"0," // force_update
"'%q'," // codectype
"%d," // index
"%d," // has_video
"%d)", // contentrating
STR(pmp3->path),
STR(pmp3->fname),
STR(pmp3->title),
STR(pmp3->artist),
STR(pmp3->album),
STR(pmp3->genre),
STR(pmp3->comment),
STR(pmp3->type),
STR(pmp3->composer),
STR(pmp3->orchestra),
STR(pmp3->conductor),
STR(pmp3->grouping),
STR(pmp3->url),
pmp3->bitrate,
pmp3->samplerate,
pmp3->song_length,
pmp3->file_size,
pmp3->year,
pmp3->track,
pmp3->total_tracks,
pmp3->disc,
pmp3->total_discs,
pmp3->bpm,
pmp3->compilation,
pmp3->rating,
pmp3->data_kind,
pmp3->item_kind,
STR(pmp3->description),
pmp3->time_added,
pmp3->time_modified,
pmp3->time_played,
pmp3->db_timestamp,
pmp3->disabled,
pmp3->sample_count,
STR(pmp3->codectype),
pmp3->index,
pmp3->has_video,
pmp3->contentrating);
if(err != DB_E_SUCCESS)
DPRINTF(E_FATAL,L_DB,"Error inserting file %s in database\n",pmp3->fname);
insertid = db_sql_insert_id_fn();
if((db_sql_in_scan)&&(!db_sql_reload)) {
db_sql_exec_fn(NULL,E_FATAL,"insert into updated values (%d)",
insertid);
}
if((!db_sql_in_scan) && (!db_sql_in_playlist_scan))
db_sql_update_playlists(NULL);
if(id)
*id = insertid;
DPRINTF(E_SPAM,L_DB,"Exiting db_sql_add\n");
return DB_E_SUCCESS;
}
/**
* update a database item
*
* @param pmp3 mp3 file to update
*/
int db_sql_update(char **pe, MP3FILE *pmp3, int *id) {
int err;
if(!pmp3->time_modified)
pmp3->time_modified = (int)time(NULL);
pmp3->db_timestamp = (int)time(NULL);
/* FIXME: this should update all fields */
err=db_sql_exec_fn(pe,E_LOG,"UPDATE songs SET "
"title='%q'," // title
"artist='%q'," // artist
"album='%q'," // album
"genre='%q'," // genre
"comment='%q'," // comment
"type='%q'," // type
"composer='%q'," // composer
"orchestra='%q'," // orchestra
"conductor='%q'," // conductor
"grouping='%q'," // grouping
"url='%q'," // url
"bitrate=%d," // bitrate
"samplerate=%d," // samplerate
"song_length=%d," // song_length
"file_size=%d," // file_size
"year=%d," // year
"track=%d," // track
"total_tracks=%d," // total_tracks
"disc=%d," // disc
"total_discs=%d," // total_discs
"time_modified=%d," // time_modified
"db_timestamp=%d," // db_timestamp
"bpm=%d," // bpm
"disabled=%d," // disabled
"compilation=%d," // compilation
"rating=%d," // rating
"sample_count=%d," // sample_count
"codectype='%q'" // codec
" WHERE path='%q'",
STR(pmp3->title),
STR(pmp3->artist),
STR(pmp3->album),
STR(pmp3->genre),
STR(pmp3->comment),
STR(pmp3->type),
STR(pmp3->composer),
STR(pmp3->orchestra),
STR(pmp3->conductor),
STR(pmp3->grouping),
STR(pmp3->url),
pmp3->bitrate,
pmp3->samplerate,
pmp3->song_length,
pmp3->file_size,
pmp3->year,
pmp3->track,
pmp3->total_tracks,
pmp3->disc,
pmp3->total_discs,
pmp3->time_modified,
pmp3->db_timestamp,
pmp3->bpm,
pmp3->disabled,
pmp3->compilation,
pmp3->rating,
pmp3->sample_count,
STR(pmp3->codectype),
pmp3->path);
if(err != DB_E_SUCCESS)
DPRINTF(E_FATAL,L_DB,"Error updating file: %s\n",pmp3->fname);
if(id) { /* we need the insert/update id */
err=db_sql_fetch_int(pe,id,"select id from songs where path='%q'",pmp3->path);
if(err != DB_E_SUCCESS)
return err;
}
if((db_sql_in_scan) && (!db_sql_reload)) {
if(id) {
db_sql_exec_fn(NULL,E_FATAL,"insert into updated (id) values (%d)",*id);
} else {
db_sql_exec_fn(NULL,E_FATAL,"insert into updated (id) "
"select id from songs where path='%q'",
pmp3->path);
}
}
if((!db_sql_in_scan) && (!db_sql_in_playlist_scan))
db_sql_update_playlists(NULL);
return 0;
}
/**
* Update the playlist item counts
*/
int db_sql_update_playlists(char **pe) {
typedef struct tag_plinfo {
char *plid;
char *type;
char *clause;
} PLINFO;
PLINFO *pinfo;
int playlists;
int err;
int index;
SQL_ROW row;
char *where_clause;
/* FIXME: There is a race here for externally added playlists */
err = db_sql_fetch_int(pe,&playlists,"select count(*) from playlists");
if(err != DB_E_SUCCESS) {
return err;
}
pinfo = (PLINFO*)malloc(sizeof(PLINFO) * playlists);
if(!pinfo) {
DPRINTF(E_FATAL,L_DB,"Malloc error\n");
}
/* now, let's walk through the table */
err = db_sql_enum_begin_fn(pe,"select * from playlists");
if(err != DB_E_SUCCESS)
return err;
/* otherwise, walk the table */
index=0;
while((db_sql_enum_fetch_fn(pe, &row) == DB_E_SUCCESS) && (row) &&
(index < playlists))
{
/* process row */
pinfo[index].plid=strdup(STR(row[0]));
pinfo[index].type=strdup(STR(row[1]));
pinfo[index].clause=strdup(STR(row[2]));
DPRINTF(E_SPAM,L_DB,"Found playlist %s: type %s, clause %s\n",pinfo[index].plid,
pinfo[index].type,pinfo[index].clause);
index++;
}
db_sql_enum_end_fn(pe);
if(index != playlists) {
DPRINTF(E_FATAL,L_DB,"Playlist count mismatch -- transaction problem?\n");
}
/* Now, update the playlists */
for(index=0;index < playlists; index++) {
if(atoi(pinfo[index].type) == 1) {
/* smart */
if(!db_sql_parse_smart(NULL,&where_clause,pinfo[index].clause)) {
DPRINTF(E_LOG,L_DB,"Playlist %d bad syntax",pinfo[index].plid);
where_clause = strdup("0");
}
db_sql_exec_fn(NULL,E_FATAL,"update playlists set items=("
"select count(*) from songs where %s) "
"where id=%s",where_clause,pinfo[index].plid);
free(where_clause);
} else {
db_sql_exec_fn(NULL,E_FATAL,"update playlists set items=("
"select count(*) from playlistitems where "
"playlistid=%s) where id=%s",
pinfo[index].plid, pinfo[index].plid);
}
if(pinfo[index].plid) free(pinfo[index].plid);
if(pinfo[index].type) free(pinfo[index].type);
if(pinfo[index].clause) free(pinfo[index].clause);
}
free(pinfo);
return DB_E_SUCCESS;
}
/**
* start enum based on the DBQUERYINFO struct passed
*
* @param pinfo DBQUERYINFO struct detailing what to enum
*/
int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) {
char scratch[4096];
char query[4096];
char query_select[255];
char query_count[255];
char query_rest[4096];
char *where_clause;
char *filter;
int is_smart;
int have_clause=0;
int err;
int browse=0;
int results=0;
SQL_ROW temprow;
query[0] = '\0';
query_select[0] = '\0';
query_count[0] = '\0';
query_rest[0] = '\0';
/* get the where qualifier to filter based on playlist, if there */
if((pinfo->playlist_id) && (pinfo->playlist_id != 1)) {
err = db_sql_enum_begin_fn(pe, "select type,query from playlists "
"where id=%d",pinfo->playlist_id);
if(err != DB_E_SUCCESS)
return err;
err = db_sql_enum_fetch_fn(pe,&temprow);
if(err != DB_E_SUCCESS) {
db_sql_enum_end_fn(NULL);
return err;
}
if(!temprow) { /* bad playlist */
db_get_error(pe,DB_E_INVALID_PLAYLIST);
db_sql_enum_end_fn(NULL);
return DB_E_INVALID_PLAYLIST;
}
is_smart=(atoi(temprow[0]) == 1);
if(is_smart) {
if(!db_sql_parse_smart(NULL,&where_clause,temprow[1]))
where_clause = strdup("0");
if(!where_clause) {
db_sql_enum_end_fn(NULL);
db_get_error(pe,DB_E_PARSE);
return DB_E_PARSE;
}
snprintf(query_rest,sizeof(query_rest)," where (%s)",where_clause);
free(where_clause);
} else {
/* We need to fix playlist queries to stop
* pulling the whole song db... the performance
* of these playlist queries sucks.
*/
sprintf(query_rest," where (songs.id in (select songid from "
"playlistitems where playlistid=%d))",
pinfo->playlist_id);
/*
sprintf(query_playlist,"(songs.id=playlistitems.songid and "
"playlistitems.playlistid=%d) order by "
"playlistitems.id",pinfo->playlist_id);
*/
}
have_clause=1;
db_sql_enum_end_fn(NULL);
}
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 ");
// sprintf(query_rest,"where (%s)",query_playlist);
break;
case queryTypePlaylistItems: /* Figure out if it's smart or dull */
sprintf(query_select,"select * from songs ");
sprintf(query_count,"select count(id) from songs ");
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->pt) {
DPRINTF(E_DBG,L_DB,"Got query/filter\n");
filter = sp_sql_clause(pinfo->pt);
if(filter) {
if(have_clause) {
strcat(query_rest," and ");
} else {
strcpy(query_rest," where ");
}
strcat(query_rest,"(");
strcat(query_rest,filter);
strcat(query_rest,")");
free(filter);
} else {
DPRINTF(E_LOG,L_DB,"Error getting sql for parse tree\n");
}
} else {
DPRINTF(E_DBG,L_DB,"No query/filter\n");
}
if((pinfo->index_type != indexTypeNone) || (pinfo->want_count)) {
/* the only time returned count is not specifiedtotalcount
* is if we have an index. */
strcpy(scratch,query_count);
strcat(scratch,query_rest);
if(browse)
strcat(scratch,")");
err = db_sql_fetch_int(pe,&results,"%s",scratch);
if(err != DB_E_SUCCESS)
return err;
DPRINTF(E_DBG,L_DB,"Number of results: %d\n",results);
pinfo->specifiedtotalcount = results;
}
strcpy(query,query_select);
strcat(query,query_rest);
/* FIXME: sqlite specific */
/* Apply any index */
switch(pinfo->index_type) {
case indexTypeFirst:
sprintf(scratch," limit %d",pinfo->index_low);
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 + 1,
pinfo->index_low);
break;
case indexTypeNone:
break;
default:
DPRINTF(E_LOG,L_DB,"Bad indexType: %d\n",(int)pinfo->index_type);
scratch[0]='\0';
break;
}
if(pinfo->index_type != indexTypeNone)
strcat(query,scratch);
/* start fetching... */
err=db_sql_enum_begin_fn(pe,"%s",query);
return err;
}
/**
* find the size of the response by walking through the query and
* sizing it
*
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_enum_size(char **pe, DBQUERYINFO *pinfo, int *count, int *total_size) {
int err;
int record_size;
SQL_ROW row;
DPRINTF(E_DBG,L_DB,"Enumerating size\n");
*count=0;
*total_size = 0;
while(((err=db_sql_enum_fetch_fn(pe,&row)) == DB_E_SUCCESS) && (row)) {
if((record_size = db_sql_get_size(pinfo,row))) {
*total_size += record_size;
*count = *count + 1;
}
}
if(err != DB_E_SUCCESS) {
db_sql_enum_end_fn(NULL);
return err;
}
err=db_sql_enum_restart_fn(pe);
DPRINTF(E_DBG,L_DB,"Got size: %d\n",*total_size);
return err;
}
/**
* fetch the next row in raw row format
*/
int db_sql_enum_fetch_row(char **pe, PACKED_MP3FILE *row, DBQUERYINFO *pinfo) {
int err;
err=db_sql_enum_fetch_fn(pe, (char***)row);
if(err != DB_E_SUCCESS) {
db_sql_enum_end_fn(NULL);
return err;
}
return DB_E_SUCCESS;
}
/**
* fetch the next record from the enum
*/
int db_sql_enum_fetch(char **pe, DBQUERYINFO *pinfo, int *size, unsigned char **pdmap) {
int err;
int result_size=0;
unsigned char *presult;
SQL_ROW row;
err=db_sql_enum_fetch_fn(pe, &row);
if(err != DB_E_SUCCESS) {
db_sql_enum_end_fn(NULL);
return err;
}
if(row) {
result_size = db_sql_get_size(pinfo,row);
if(result_size) {
presult = (unsigned char*)malloc(result_size);
if(!presult) {
DPRINTF(E_FATAL,L_DB,"Malloc error\n");
}
db_sql_build_dmap(pinfo,row,presult,result_size);
*pdmap=presult;
*size = result_size;
}
} else {
*size = 0;
}
return DB_E_SUCCESS;
}
/**
* start the enum again
*/
int db_sql_enum_reset(char **pe, DBQUERYINFO *pinfo) {
return db_sql_enum_restart_fn(pe);
}
/**
* stop the enum
*/
int db_sql_enum_end(char **pe) {
return db_sql_enum_end_fn(pe);
}
/**
* get the size of the generated dmap, given a specific meta
*/
int db_sql_get_size(DBQUERYINFO *pinfo, SQL_ROW valarray) {
int size;
int transcode;
switch(pinfo->query_type) {
case queryTypeBrowseArtists: /* simple 'mlit' entry */
case queryTypeBrowseAlbums:
case queryTypeBrowseGenres:
case queryTypeBrowseComposers:
return valarray[0] ? (8 + (int) 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 + (int) strlen(valarray[plTitle])); /* minm */
if(valarray[plType] && (atoi(valarray[plType])==1) &&
db_wantsmeta(pinfo->meta, metaMPlaylistSpec))
size += (8 + (int) strlen(valarray[plQuery])); /* MSPS */
if(db_wantsmeta(pinfo->meta, metaMPlaylistType))
size += 9; /* MPTY */
return size;
break;
case queryTypeItems:
case queryTypePlaylistItems: /* essentially the same query */
/* see if this is going to be transcoded */
transcode = server_side_convert(valarray[37]);
/* Items that get changed by transcode:
*
* type: item 8: changes to 'wav'
* description: item 29: changes to 'wav audio file'
* bitrate: item 15: guestimated, based on item 15, samplerate
*
* probably file size should change as well, but currently doesn't
*/
size = 8; /* mlit */
if(db_wantsmeta(pinfo->meta, metaItemKind))
/* mikd */
size += 9;
if(db_wantsmeta(pinfo->meta, metaSongDataKind))
/* asdk */
size += 9;
if(db_wantsmeta(pinfo->meta, metaSongDataURL))
/* asul */
size += DMAPLEN(valarray[13]);
if(db_wantsmeta(pinfo->meta, metaSongAlbum))
/* asal */
size += DMAPLEN(valarray[5]);
if(db_wantsmeta(pinfo->meta, metaSongArtist))
/* asar */
size += DMAPLEN(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(db_wantsmeta(pinfo->meta, metaSongComment))
/* ascm */
size += DMAPLEN(valarray[7]);
if(valarray[24] && atoi(valarray[24]) && db_wantsmeta(pinfo->meta,metaSongCompilation))
/* asco */
size += 9;
if(db_wantsmeta(pinfo->meta, metaSongComposer))
/* ascp */
size += DMAPLEN(valarray[9]);
if(db_wantsmeta(pinfo->meta, metaSongGrouping))
/* agrp */
size += DMAPLEN(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))
/* asdn */
size += 10;
if(db_wantsmeta(pinfo->meta, metaSongGenre))
/* asgn */
size += DMAPLEN(valarray[6]);
if(db_wantsmeta(pinfo->meta,metaItemId))
/* miid */
size += 12;
if(db_wantsmeta(pinfo->meta,metaSongFormat)) {
/* asfm */
if(transcode) {
size += 11; /* 'wav' */
} else {
size += DMAPLEN(valarray[8]);
}
}
if(db_wantsmeta(pinfo->meta,metaSongDescription)) {
/* asdt */
if(transcode) {
size += 22; /* 'wav audio file' */
} else {
size += DMAPLEN(valarray[29]);
}
}
if(db_wantsmeta(pinfo->meta,metaItemName))
/* minm */
size += DMAPLEN(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((valarray[37]) && (strlen(valarray[37]) == 4) &&
db_wantsmeta(pinfo->meta,metaSongCodecType))
/* ascd */
size += 12;
if(db_wantsmeta(pinfo->meta,metaSongContentRating))
/* ascr */
size += 9;
if(db_wantsmeta(pinfo->meta,metaItunesHasVideo))
/* aeHV */
size += 9;
return size;
break;
default:
DPRINTF(E_LOG,L_DB|L_DAAP,"Unknown query type: %d\n",(int)pinfo->query_type);
return 0;
}
return 0;
}
int db_sql_build_dmap(DBQUERYINFO *pinfo, char **valarray, unsigned char *presult, int len) {
unsigned char *current = presult;
int transcode;
int samplerate=0;
switch(pinfo->query_type) {
case queryTypeBrowseArtists: /* simple 'mlit' entry */
case queryTypeBrowseAlbums:
case queryTypeBrowseGenres:
case queryTypeBrowseComposers:
return db_dmap_add_string(current,"mlit",valarray[0]);
case queryTypePlaylists:
/* do I want to include the mlit? */
current += db_dmap_add_container(current,"mlit",len - 8);
if(db_wantsmeta(pinfo->meta,metaItemId))
current += db_dmap_add_int(current,"miid",atoi(valarray[plID]));
current += db_dmap_add_int(current,"mimc",atoi(valarray[plItems]));
if(db_wantsmeta(pinfo->meta,metaItunesSmartPlaylist)) {
if(valarray[plType] && (atoi(valarray[plType]) == 1))
current += db_dmap_add_char(current,"aeSP",1);
}
if(db_wantsmeta(pinfo->meta,metaItemName))
current += db_dmap_add_string(current,"minm",valarray[plTitle]);
if((valarray[plType]) && (atoi(valarray[plType])==1) &&
db_wantsmeta(pinfo->meta, metaMPlaylistSpec))
current += db_dmap_add_string(current,"MSPS",valarray[plQuery]);
if(db_wantsmeta(pinfo->meta, metaMPlaylistType))
current += db_dmap_add_char(current,"MPTY",atoi(valarray[plType]));
break;
case queryTypeItems:
case queryTypePlaylistItems: /* essentially the same query */
/* see if this is going to be transcoded */
transcode = server_side_convert(valarray[37]);
/* Items that get changed by transcode:
*
* type: item 8: changes to 'wav'
* description: item 29: changes to 'wav audio file'
* bitrate: item 15: guestimated, but doesn't change file size
*
* probably file size should change as well, but currently doesn't
*/
current += db_dmap_add_container(current,"mlit",len-8);
if(db_wantsmeta(pinfo->meta, metaItemKind))
current += db_dmap_add_char(current,"mikd",(char)atoi(valarray[28]));
if(db_wantsmeta(pinfo->meta, metaSongDataKind))
current += db_dmap_add_char(current,"asdk",(char)atoi(valarray[27]));
if(EMIT(valarray[13]) && db_wantsmeta(pinfo->meta, metaSongDataURL))
current += db_dmap_add_string(current,"asul",valarray[13]);
if(EMIT(valarray[5]) && db_wantsmeta(pinfo->meta, metaSongAlbum))
current += db_dmap_add_string(current,"asal",valarray[5]);
if(EMIT(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(EMIT(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(EMIT(valarray[9]) && db_wantsmeta(pinfo->meta, metaSongComposer))
current += db_dmap_add_string(current,"ascp",valarray[9]);
if(EMIT(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(EMIT(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(EMIT(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(EMIT(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(EMIT(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((valarray[37]) && (strlen(valarray[37]) == 4) &&
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]));
if(db_wantsmeta(pinfo->meta, metaItunesHasVideo))
current += db_dmap_add_char(current,"aeHV",atoi(valarray[39]));
if(db_wantsmeta(pinfo->meta, metaSongContentRating))
current += db_dmap_add_char(current,"ascr",atoi(valarray[40]));
return 0;
break;
default:
DPRINTF(E_LOG,L_DB|L_DAAP,"Unknown query type: %d\n",(int)pinfo->query_type);
return 0;
}
return 0;
}
int db_sql_atoi(const char *what) {
return what ? atoi(what) : 0;
}
char *db_sql_strdup(const char *what) {
return what ? (strlen(what) ? strdup(what) : NULL) : NULL;
}
void db_sql_build_m3ufile(SQL_ROW valarray, M3UFILE *pm3u) {
memset(pm3u,0x00,sizeof(M3UFILE));
pm3u->id=db_sql_atoi(valarray[0]);
pm3u->title=db_sql_strdup(valarray[1]);
pm3u->type=db_sql_atoi(valarray[2]);
pm3u->items=db_sql_atoi(valarray[3]);
pm3u->query=db_sql_strdup(valarray[4]);
pm3u->db_timestamp=db_sql_atoi(valarray[5]);
pm3u->path=db_sql_strdup(valarray[6]);
pm3u->index=db_sql_atoi(valarray[7]);
return;
}
void db_sql_build_mp3file(SQL_ROW valarray, MP3FILE *pmp3) {
memset(pmp3,0x00,sizeof(MP3FILE));
pmp3->id=db_sql_atoi(valarray[0]);
pmp3->path=db_sql_strdup(valarray[1]);
pmp3->fname=db_sql_strdup(valarray[2]);
pmp3->title=db_sql_strdup(valarray[3]);
pmp3->artist=db_sql_strdup(valarray[4]);
pmp3->album=db_sql_strdup(valarray[5]);
pmp3->genre=db_sql_strdup(valarray[6]);
pmp3->comment=db_sql_strdup(valarray[7]);
pmp3->type=db_sql_strdup(valarray[8]);
pmp3->composer=db_sql_strdup(valarray[9]);
pmp3->orchestra=db_sql_strdup(valarray[10]);
pmp3->conductor=db_sql_strdup(valarray[11]);
pmp3->grouping=db_sql_strdup(valarray[12]);
pmp3->url=db_sql_strdup(valarray[13]);
pmp3->bitrate=db_sql_atoi(valarray[14]);
pmp3->samplerate=db_sql_atoi(valarray[15]);
pmp3->song_length=db_sql_atoi(valarray[16]);
pmp3->file_size=db_sql_atoi(valarray[17]);
pmp3->year=db_sql_atoi(valarray[18]);
pmp3->track=db_sql_atoi(valarray[19]);
pmp3->total_tracks=db_sql_atoi(valarray[20]);
pmp3->disc=db_sql_atoi(valarray[21]);
pmp3->total_discs=db_sql_atoi(valarray[22]);
pmp3->bpm=db_sql_atoi(valarray[23]);
pmp3->compilation=db_sql_atoi(valarray[24]);
pmp3->rating=db_sql_atoi(valarray[25]);
pmp3->play_count=db_sql_atoi(valarray[26]);
pmp3->data_kind=db_sql_atoi(valarray[27]);
pmp3->item_kind=db_sql_atoi(valarray[28]);
pmp3->description=db_sql_strdup(valarray[29]);
pmp3->time_added=db_sql_atoi(valarray[30]);
pmp3->time_modified=db_sql_atoi(valarray[31]);
pmp3->time_played=db_sql_atoi(valarray[32]);
pmp3->db_timestamp=db_sql_atoi(valarray[33]);
pmp3->disabled=db_sql_atoi(valarray[34]);
pmp3->sample_count=db_sql_atoi(valarray[35]);
pmp3->force_update=db_sql_atoi(valarray[36]);
pmp3->codectype=db_sql_strdup(valarray[37]);
pmp3->index=db_sql_atoi(valarray[38]);
pmp3->has_video=db_sql_atoi(valarray[39]);
pmp3->contentrating=db_sql_atoi(valarray[40]);
}
/**
* fetch a playlist by path and index
*
* @param path path to fetch
*/
M3UFILE *db_sql_fetch_playlist(char **pe, char *path, int index) {
int result;
M3UFILE *pm3u=NULL;
SQL_ROW row;
result = db_sql_fetch_row(pe, &row, "select * from playlists where "
"path='%q' and idx=%d",path,index);
if(result != DB_E_SUCCESS) {
if(result == DB_E_NOROWS) {
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_PLAYLIST);
return NULL;
}
return NULL; /* sql error or something */
}
pm3u=(M3UFILE*)malloc(sizeof(M3UFILE));
if(!pm3u)
DPRINTF(E_FATAL,L_MISC,"malloc error: db_sql_fetch_playlist\n");
db_sql_build_m3ufile(row,pm3u);
db_sql_dispose_row();
if((db_sql_in_playlist_scan) && (!db_sql_reload)) {
db_sql_exec_fn(NULL,E_FATAL,"insert into plupdated values (%d)",
pm3u->id);
}
return pm3u;
}
/* FIXME: bad error handling -- not like the rest */
/**
* fetch a MP3FILE for a specific id
*
* @param id id to fetch
*/
MP3FILE *db_sql_fetch_item(char **pe, int id) {
SQL_ROW row;
MP3FILE *pmp3=NULL;
int err;
err=db_sql_fetch_row(pe,&row,"select * from songs where id=%d",id);
if(err != DB_E_SUCCESS) {
if(err == DB_E_NOROWS) { /* Override generic error */
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_SONGID);
return NULL;
}
return NULL;
}
pmp3=(MP3FILE*)malloc(sizeof(MP3FILE));
if(!pmp3)
DPRINTF(E_FATAL,L_MISC,"Malloc error in db_sql_fetch_item\n");
db_sql_build_mp3file(row,pmp3);
db_sql_dispose_row();
if ((db_sql_in_scan) && (!db_sql_reload)) {
db_sql_exec_fn(pe,E_FATAL,"INSERT INTO updated VALUES (%d)",id);
}
return pmp3;
}
/**
* retrieve a MP3FILE struct for the song with a given path
*
* @param path path of the file to retreive
*/
MP3FILE *db_sql_fetch_path(char **pe, char *path, int index) {
SQL_ROW row;
MP3FILE *pmp3=NULL;
int err;
err=db_sql_fetch_row(pe,&row,"select * from songs where path='%q'",path);
if(err != DB_E_SUCCESS) {
if(err == DB_E_NOROWS) { /* Override generic error */
if(pe) { free(*pe); };
db_get_error(pe,DB_E_INVALID_SONGID);
return NULL;
}
return NULL;
}
pmp3=(MP3FILE*)malloc(sizeof(MP3FILE));
if(!pmp3)
DPRINTF(E_FATAL,L_MISC,"Malloc error in db_sql_fetch_path\n");
db_sql_build_mp3file(row,pmp3);
db_sql_dispose_row();
if ((db_sql_in_scan) && (!db_sql_reload)) {
db_sql_exec_fn(pe,E_FATAL,"INSERT INTO updated VALUES (%d)",pmp3->id);
}
return pmp3;
}
/**
* dispose of a MP3FILE struct that was obtained
* from db_sql_fetch_item
*
* @param pmp3 item obtained from db_sql_fetch_item
*/
void db_sql_dispose_item(MP3FILE *pmp3) {
if(!pmp3)
return;
MAYBEFREE(pmp3->path);
MAYBEFREE(pmp3->fname);
MAYBEFREE(pmp3->title);
MAYBEFREE(pmp3->artist);
MAYBEFREE(pmp3->album);
MAYBEFREE(pmp3->genre);
MAYBEFREE(pmp3->comment);
MAYBEFREE(pmp3->type);
MAYBEFREE(pmp3->composer);
MAYBEFREE(pmp3->orchestra);
MAYBEFREE(pmp3->conductor);
MAYBEFREE(pmp3->grouping);
MAYBEFREE(pmp3->description);
MAYBEFREE(pmp3->url);
MAYBEFREE(pmp3->codectype);
free(pmp3);
}
void db_sql_dispose_playlist(M3UFILE *pm3u) {
if(!pm3u)
return;
MAYBEFREE(pm3u->title);
MAYBEFREE(pm3u->query);
MAYBEFREE(pm3u->path);
free(pm3u);
}
/**
* count either the number of playlists, or the number of
* songs
*
* @param type either countPlaylists or countSongs (type to count)
*/
int db_sql_get_count(char **pe, int *count, CountType_t type) {
char *table;
int err;
switch(type) {
case countPlaylists:
table="playlists";
break;
case countSongs:
default:
table="songs";
break;
}
err=db_sql_fetch_int(pe,count,"select count(*) FROM %q", table);
return err;
}
/**
* increment the play_count and time_played
*
* @param pe error string
* @param id id of the song to increment
* @returns DB_E_SUCCESS on success, error code otherwise
*/
int db_sql_playcount_increment(char **pe, int id) {
time_t now = time(NULL);
return db_sql_exec_fn(pe,E_INF,"update songs set play_count=play_count + 1"
", time_played=%d where id=%d",now,id);
}