2009-06-07 12:56:35 -04:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2009 Julien BLACHE <jb@jblache.org>
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
|
|
|
|
#include <stddef.h>
|
|
|
|
|
|
|
|
#include <pthread.h>
|
|
|
|
|
|
|
|
#include <sqlite3.h>
|
|
|
|
|
|
|
|
#include "logger.h"
|
|
|
|
#include "db.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define STR(x) ((x) ? (x) : "")
|
|
|
|
#define DB_PATH "/var/cache/mt-daapd/songs3.db" /* FIXME */
|
|
|
|
|
2009-06-10 12:11:11 -04:00
|
|
|
/* Inotify cookies are uint32_t */
|
|
|
|
#define INOTIFY_FAKE_COOKIE ((int64_t)1 << 32)
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
#define DB_TYPE_CHAR 1
|
|
|
|
#define DB_TYPE_INT 2
|
|
|
|
#define DB_TYPE_INT64 3
|
|
|
|
#define DB_TYPE_STRING 4
|
|
|
|
|
|
|
|
struct col_type_map {
|
|
|
|
ssize_t offset;
|
|
|
|
short type;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define mfi_offsetof(field) offsetof(struct media_file_info, field)
|
|
|
|
#define pli_offsetof(field) offsetof(struct playlist_info, field)
|
|
|
|
|
|
|
|
/* This list must be kept in sync with
|
|
|
|
* - the order of the columns in the songs table
|
|
|
|
* - the type and name of the fields in struct media_file_info
|
|
|
|
*/
|
|
|
|
static struct col_type_map mfi_cols_map[] =
|
|
|
|
{
|
|
|
|
{ mfi_offsetof(id), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(path), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(fname), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(title), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(artist), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(album), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(genre), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(comment), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(type), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(composer), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(orchestra), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(conductor), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(grouping), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(url), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(bitrate), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(samplerate), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(song_length), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(file_size), DB_TYPE_INT64 },
|
|
|
|
{ mfi_offsetof(year), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(track), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(total_tracks), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(disc), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(total_discs), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(bpm), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(compilation), DB_TYPE_CHAR },
|
|
|
|
{ mfi_offsetof(rating), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(play_count), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(data_kind), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(item_kind), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(description), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(time_added), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(time_modified), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(time_played), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(db_timestamp), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(disabled), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(sample_count), DB_TYPE_INT64 },
|
|
|
|
{ mfi_offsetof(force_update), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(codectype), DB_TYPE_STRING },
|
|
|
|
{ mfi_offsetof(index), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(has_video), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(contentrating), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(bits_per_sample), DB_TYPE_INT },
|
|
|
|
{ mfi_offsetof(album_artist), DB_TYPE_STRING },
|
|
|
|
};
|
|
|
|
|
|
|
|
/* This list must be kept in sync with
|
|
|
|
* - the order of the columns in the playlists table
|
|
|
|
* - the type and name of the fields in struct playlist_info
|
|
|
|
*/
|
|
|
|
static struct col_type_map pli_cols_map[] =
|
|
|
|
{
|
|
|
|
{ pli_offsetof(id), DB_TYPE_INT },
|
|
|
|
{ pli_offsetof(title), DB_TYPE_STRING },
|
|
|
|
{ pli_offsetof(type), DB_TYPE_INT },
|
|
|
|
{ pli_offsetof(items), DB_TYPE_INT },
|
|
|
|
{ pli_offsetof(query), DB_TYPE_STRING },
|
|
|
|
{ pli_offsetof(db_timestamp), DB_TYPE_INT },
|
2009-06-10 09:23:02 -04:00
|
|
|
{ pli_offsetof(disabled), DB_TYPE_INT },
|
2009-06-07 12:56:35 -04:00
|
|
|
{ pli_offsetof(path), DB_TYPE_STRING },
|
|
|
|
{ pli_offsetof(index), DB_TYPE_INT },
|
|
|
|
};
|
|
|
|
|
|
|
|
#define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field)
|
|
|
|
#define dbpli_offsetof(field) offsetof(struct db_playlist_info, field)
|
|
|
|
|
|
|
|
/* This list must be kept in sync with
|
|
|
|
* - the order of the columns in the songs table
|
|
|
|
* - the name of the fields in struct db_media_file_info
|
|
|
|
*/
|
|
|
|
static ssize_t dbmfi_cols_map[] =
|
|
|
|
{
|
|
|
|
dbmfi_offsetof(id),
|
|
|
|
dbmfi_offsetof(path),
|
|
|
|
dbmfi_offsetof(fname),
|
|
|
|
dbmfi_offsetof(title),
|
|
|
|
dbmfi_offsetof(artist),
|
|
|
|
dbmfi_offsetof(album),
|
|
|
|
dbmfi_offsetof(genre),
|
|
|
|
dbmfi_offsetof(comment),
|
|
|
|
dbmfi_offsetof(type),
|
|
|
|
dbmfi_offsetof(composer),
|
|
|
|
dbmfi_offsetof(orchestra),
|
|
|
|
dbmfi_offsetof(conductor),
|
|
|
|
dbmfi_offsetof(grouping),
|
|
|
|
dbmfi_offsetof(url),
|
|
|
|
dbmfi_offsetof(bitrate),
|
|
|
|
dbmfi_offsetof(samplerate),
|
|
|
|
dbmfi_offsetof(song_length),
|
|
|
|
dbmfi_offsetof(file_size),
|
|
|
|
dbmfi_offsetof(year),
|
|
|
|
dbmfi_offsetof(track),
|
|
|
|
dbmfi_offsetof(total_tracks),
|
|
|
|
dbmfi_offsetof(disc),
|
|
|
|
dbmfi_offsetof(total_discs),
|
|
|
|
dbmfi_offsetof(bpm),
|
|
|
|
dbmfi_offsetof(compilation),
|
|
|
|
dbmfi_offsetof(rating),
|
|
|
|
dbmfi_offsetof(play_count),
|
|
|
|
dbmfi_offsetof(data_kind),
|
|
|
|
dbmfi_offsetof(item_kind),
|
|
|
|
dbmfi_offsetof(description),
|
|
|
|
dbmfi_offsetof(time_added),
|
|
|
|
dbmfi_offsetof(time_modified),
|
|
|
|
dbmfi_offsetof(time_played),
|
|
|
|
dbmfi_offsetof(db_timestamp),
|
|
|
|
dbmfi_offsetof(disabled),
|
|
|
|
dbmfi_offsetof(sample_count),
|
|
|
|
dbmfi_offsetof(force_update),
|
|
|
|
dbmfi_offsetof(codectype),
|
|
|
|
dbmfi_offsetof(idx),
|
|
|
|
dbmfi_offsetof(has_video),
|
|
|
|
dbmfi_offsetof(contentrating),
|
|
|
|
dbmfi_offsetof(bits_per_sample),
|
|
|
|
dbmfi_offsetof(album_artist),
|
|
|
|
};
|
|
|
|
|
|
|
|
/* This list must be kept in sync with
|
|
|
|
* - the order of the columns in the playlists table
|
|
|
|
* - the name of the fields in struct playlist_info
|
|
|
|
*/
|
|
|
|
static ssize_t dbpli_cols_map[] =
|
|
|
|
{
|
|
|
|
dbpli_offsetof(id),
|
|
|
|
dbpli_offsetof(title),
|
|
|
|
dbpli_offsetof(type),
|
|
|
|
dbpli_offsetof(items),
|
|
|
|
dbpli_offsetof(query),
|
|
|
|
dbpli_offsetof(db_timestamp),
|
2009-06-10 09:23:02 -04:00
|
|
|
dbpli_offsetof(disabled),
|
2009-06-07 12:56:35 -04:00
|
|
|
dbpli_offsetof(path),
|
|
|
|
dbpli_offsetof(index),
|
|
|
|
};
|
|
|
|
|
2009-06-10 12:11:11 -04:00
|
|
|
#define wi_offsetof(field) offsetof(struct watch_info, field)
|
|
|
|
|
|
|
|
/* This list must be kept in sync with
|
|
|
|
* - the order of the columns in the inotify table
|
|
|
|
* - the name and type of the fields in struct watch_info
|
|
|
|
*/
|
|
|
|
static struct col_type_map wi_cols_map[] =
|
|
|
|
{
|
|
|
|
{ wi_offsetof(wd), DB_TYPE_INT },
|
|
|
|
{ wi_offsetof(cookie), DB_TYPE_INT },
|
|
|
|
{ wi_offsetof(path), DB_TYPE_STRING },
|
|
|
|
{ wi_offsetof(toplevel), DB_TYPE_INT },
|
|
|
|
{ wi_offsetof(libidx), DB_TYPE_INT },
|
|
|
|
};
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
static __thread sqlite3 *hdl;
|
|
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
db_escape_string(const char *str)
|
|
|
|
{
|
|
|
|
char *escaped;
|
|
|
|
char *ret;
|
|
|
|
|
|
|
|
escaped = sqlite3_mprintf("%q", str);
|
|
|
|
if (!escaped)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for escaped string\n");
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = strdup(escaped);
|
|
|
|
|
|
|
|
sqlite3_free(escaped);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
free_mfi(struct media_file_info *mfi, int content_only)
|
|
|
|
{
|
|
|
|
if (mfi->path)
|
|
|
|
free(mfi->path);
|
|
|
|
|
|
|
|
if (mfi->fname)
|
|
|
|
free(mfi->fname);
|
|
|
|
|
|
|
|
if (mfi->title)
|
|
|
|
free(mfi->title);
|
|
|
|
|
|
|
|
if (mfi->artist)
|
|
|
|
free(mfi->artist);
|
|
|
|
|
|
|
|
if (mfi->album)
|
|
|
|
free(mfi->album);
|
|
|
|
|
|
|
|
if (mfi->genre)
|
|
|
|
free(mfi->genre);
|
|
|
|
|
|
|
|
if (mfi->comment)
|
|
|
|
free(mfi->comment);
|
|
|
|
|
|
|
|
if (mfi->type)
|
|
|
|
free(mfi->type);
|
|
|
|
|
|
|
|
if (mfi->composer)
|
|
|
|
free(mfi->composer);
|
|
|
|
|
|
|
|
if (mfi->orchestra)
|
|
|
|
free(mfi->orchestra);
|
|
|
|
|
|
|
|
if (mfi->conductor)
|
|
|
|
free(mfi->conductor);
|
|
|
|
|
|
|
|
if (mfi->grouping)
|
|
|
|
free(mfi->grouping);
|
|
|
|
|
|
|
|
if (mfi->description)
|
|
|
|
free(mfi->description);
|
|
|
|
|
|
|
|
if (mfi->codectype)
|
|
|
|
free(mfi->codectype);
|
|
|
|
|
|
|
|
if (mfi->album_artist)
|
|
|
|
free(mfi->album_artist);
|
|
|
|
|
|
|
|
if (!content_only)
|
|
|
|
free(mfi);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
free_pli(struct playlist_info *pli, int content_only)
|
|
|
|
{
|
|
|
|
if (pli->title)
|
|
|
|
free(pli->title);
|
|
|
|
|
|
|
|
if (pli->query)
|
|
|
|
free(pli->query);
|
|
|
|
|
|
|
|
if (pli->path)
|
|
|
|
free(pli->path);
|
|
|
|
|
|
|
|
if (!content_only)
|
|
|
|
free(pli);
|
|
|
|
}
|
|
|
|
|
2009-06-07 14:49:13 -04:00
|
|
|
void
|
|
|
|
db_purge_cruft(time_t ref)
|
|
|
|
{
|
|
|
|
char *errmsg;
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
char *queries[4] = { NULL, NULL, NULL, NULL };
|
|
|
|
char *queries_tmpl[4] =
|
|
|
|
{
|
|
|
|
"DELETE FROM playlistitems WHERE playlistid IN (SELECT id FROM playlists WHERE id <> 1 AND db_timestamp < %" PRIi64 ");",
|
|
|
|
"DELETE FROM playlists WHERE id <> 1 AND db_timestamp < %" PRIi64 ";",
|
|
|
|
"DELETE FROM playlistitems WHERE songid IN (SELECT id FROM songs WHERE db_timestamp < %" PRIi64 ");",
|
|
|
|
"DELETE FROM songs WHERE db_timestamp < %" PRIi64 ";"
|
|
|
|
};
|
|
|
|
|
|
|
|
if (sizeof(queries) != sizeof(queries_tmpl))
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "db_purge_cruft(): queries out of sync with queries_tmpl\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < (sizeof(queries_tmpl) / sizeof(queries_tmpl[0])); i++)
|
|
|
|
{
|
|
|
|
queries[i] = sqlite3_mprintf(queries_tmpl[i], (int64_t)ref);
|
|
|
|
if (!queries[i])
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
goto purge_fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running purge query '%s'\n", queries[i]);
|
|
|
|
|
|
|
|
ret = sqlite3_exec(hdl, queries[i], NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Purge query %d error: %s\n", i, errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
|
|
|
|
}
|
|
|
|
|
|
|
|
purge_fail:
|
|
|
|
for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
|
|
|
|
{
|
|
|
|
sqlite3_free(queries[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
/* Queries */
|
|
|
|
static int
|
|
|
|
db_query_get_count(char *query)
|
|
|
|
{
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_column_int(stmt, 0);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
db_build_query_index_clause(struct query_params *qp, char **i)
|
|
|
|
{
|
|
|
|
char *idx;
|
|
|
|
|
|
|
|
switch (qp->idx_type)
|
|
|
|
{
|
|
|
|
case I_FIRST:
|
|
|
|
idx = sqlite3_mprintf("LIMIT %d", qp->limit);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case I_LAST:
|
|
|
|
idx = sqlite3_mprintf("LIMIT -1 OFFSET %d", qp->limit, qp->results - qp->limit);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case I_SUB:
|
|
|
|
idx = sqlite3_mprintf("LIMIT %d OFFSET %d", qp->limit, qp->offset);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case I_NONE:
|
|
|
|
*i = NULL;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DPRINTF(E_LOG, L_DB, "Unknown index type\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!idx)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not build index string; out of memory");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*i = idx;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
db_build_query_items(struct query_params *qp, char **q)
|
|
|
|
{
|
|
|
|
char *query;
|
|
|
|
char *count;
|
|
|
|
char *idx;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (qp->filter)
|
2009-06-10 09:23:02 -04:00
|
|
|
count = sqlite3_mprintf("SELECT COUNT(*) FROM songs WHERE disabled = 0 AND %s;", qp->filter);
|
2009-06-07 12:56:35 -04:00
|
|
|
else
|
2009-06-10 09:23:02 -04:00
|
|
|
count = sqlite3_mprintf("SELECT COUNT(*) FROM songs WHERE disabled = 0;");
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
if (!count)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
qp->results = db_query_get_count(count);
|
|
|
|
sqlite3_free(count);
|
|
|
|
|
|
|
|
if (qp->results < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* Get index clause */
|
|
|
|
ret = db_build_query_index_clause(qp, &idx);
|
|
|
|
if (ret < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (idx && qp->filter)
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT * FROM songs WHERE disabled = 0 AND %s %s;", qp->filter, idx);
|
2009-06-07 12:56:35 -04:00
|
|
|
else if (idx)
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT * FROM songs WHERE disabled = 0 %s;", idx);
|
2009-06-07 12:56:35 -04:00
|
|
|
else if (qp->filter)
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT * FROM songs WHERE disabled = 0 AND %s;", qp->filter);
|
2009-06-07 12:56:35 -04:00
|
|
|
else
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT * FROM songs WHERE disabled = 0;");
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*q = query;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
db_build_query_pls(struct query_params *qp, char **q)
|
|
|
|
{
|
|
|
|
char *query;
|
|
|
|
char *idx;
|
|
|
|
int ret;
|
|
|
|
|
2009-06-10 09:23:02 -04:00
|
|
|
qp->results = db_query_get_count("SELECT COUNT(*) FROM playlists WHERE disabled = 0;");
|
2009-06-07 12:56:35 -04:00
|
|
|
if (qp->results < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* Get index clause */
|
|
|
|
ret = db_build_query_index_clause(qp, &idx);
|
|
|
|
if (ret < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (idx)
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT * FROM playlists WHERE disabled = 0 %s;", idx);
|
2009-06-07 12:56:35 -04:00
|
|
|
else
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT * FROM playlists WHERE disabled = 0;");
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*q = query;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
db_build_query_plitems(struct query_params *qp, char **q)
|
|
|
|
{
|
|
|
|
char *query;
|
|
|
|
char *count;
|
|
|
|
char *idx;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (qp->pl_id <= 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "No playlist id specified in playlist items query\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qp->pl_id == 1)
|
|
|
|
{
|
|
|
|
if (qp->filter)
|
2009-06-10 09:23:02 -04:00
|
|
|
count = sqlite3_mprintf("SELECT COUNT(*) FROM songs WHERE disabled = 0 AND %s;", qp->filter);
|
2009-06-07 12:56:35 -04:00
|
|
|
else
|
2009-06-10 09:23:02 -04:00
|
|
|
count = sqlite3_mprintf("SELECT COUNT(*) FROM songs WHERE disabled = 0;");
|
2009-06-07 12:56:35 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (qp->filter)
|
|
|
|
count = sqlite3_mprintf("SELECT COUNT(*) FROM songs JOIN playlistitems ON songs.id = playlistitems.songid"
|
2009-06-10 09:23:02 -04:00
|
|
|
" WHERE playlistitems.playlistid = %d AND songs.disabled = 0 AND %s;", qp->pl_id, qp->filter);
|
2009-06-07 12:56:35 -04:00
|
|
|
else
|
|
|
|
count = sqlite3_mprintf("SELECT COUNT(*) FROM songs JOIN playlistitems ON songs.id = playlistitems.songid"
|
2009-06-10 09:23:02 -04:00
|
|
|
" WHERE playlistitems.playlistid = %d AND songs.disabled = 0;", qp->pl_id);
|
2009-06-07 12:56:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!count)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
qp->results = db_query_get_count(count);
|
|
|
|
sqlite3_free(count);
|
|
|
|
|
|
|
|
if (qp->results < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* Get index clause */
|
|
|
|
ret = db_build_query_index_clause(qp, &idx);
|
|
|
|
if (ret < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (idx && qp->filter)
|
|
|
|
query = sqlite3_mprintf("SELECT songs.* FROM songs JOIN playlistitems ON songs.id = playlistitems.songid"
|
2009-06-10 09:23:02 -04:00
|
|
|
" WHERE playlistitems.playlistid = %d AND songs.disabled = 0 AND %s ORDER BY playlistitems.id ASC %s;",
|
2009-06-07 12:56:35 -04:00
|
|
|
qp->pl_id, qp->filter, idx);
|
|
|
|
else if (idx)
|
|
|
|
query = sqlite3_mprintf("SELECT songs.* FROM songs JOIN playlistitems ON songs.id = playlistitems.songid"
|
2009-06-10 09:23:02 -04:00
|
|
|
" WHERE playlistitems.playlistid = %d AND songs.disabled = 0 ORDER BY playlistitems.id ASC %s;",
|
2009-06-07 12:56:35 -04:00
|
|
|
qp->pl_id, idx);
|
|
|
|
else if (qp->filter)
|
|
|
|
query = sqlite3_mprintf("SELECT songs.* FROM songs JOIN playlistitems ON songs.id = playlistitems.songid"
|
2009-06-10 09:23:02 -04:00
|
|
|
" WHERE playlistitems.playlistid = %d AND songs.disabled = 0 AND %s ORDER BY playlistitems.id ASC;",
|
2009-06-07 12:56:35 -04:00
|
|
|
qp->pl_id, qp->filter);
|
|
|
|
else
|
|
|
|
query = sqlite3_mprintf("SELECT songs.* FROM songs JOIN playlistitems ON songs.id = playlistitems.songid"
|
2009-06-10 09:23:02 -04:00
|
|
|
" WHERE playlistitems.playlistid = %d AND songs.disabled = 0 ORDER BY playlistitems.id ASC;",
|
2009-06-07 12:56:35 -04:00
|
|
|
qp->pl_id);
|
|
|
|
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*q = query;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
db_build_query_browse(struct query_params *qp, char *field, char **q)
|
|
|
|
{
|
|
|
|
char *query;
|
|
|
|
char *count;
|
|
|
|
char *idx;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (qp->filter)
|
2009-06-10 09:23:02 -04:00
|
|
|
count = sqlite3_mprintf("SELECT COUNT(DISTINCT %s) FROM songs WHERE data_kind = 0 AND disabled = 0 AND %s != '' AND %s;",
|
|
|
|
field, field, qp->filter);
|
2009-06-07 12:56:35 -04:00
|
|
|
else
|
2009-06-10 09:23:02 -04:00
|
|
|
count = sqlite3_mprintf("SELECT COUNT(DISTINCT %s) FROM songs WHERE data_kind = 0 AND disabled = 0 AND %s != '';",
|
|
|
|
field, field);
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
if (!count)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
qp->results = db_query_get_count(count);
|
|
|
|
sqlite3_free(count);
|
|
|
|
|
|
|
|
if (qp->results < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* Get index clause */
|
|
|
|
ret = db_build_query_index_clause(qp, &idx);
|
|
|
|
if (ret < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (idx && qp->filter)
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT DISTINCT %s FROM songs WHERE data_kind = 0 AND disabled = 0 AND %s != ''"
|
2009-06-07 12:56:35 -04:00
|
|
|
" AND %s %s;", field, field, qp->filter, idx);
|
|
|
|
else if (idx)
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT DISTINCT %s FROM songs WHERE data_kind = 0 AND disabled = 0 AND %s != ''"
|
2009-06-07 12:56:35 -04:00
|
|
|
" %s;", field, field, idx);
|
|
|
|
else if (qp->filter)
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT DISTINCT %s FROM songs WHERE data_kind = 0 AND disabled = 0 AND %s != ''"
|
2009-06-07 12:56:35 -04:00
|
|
|
" AND %s;", field, field, qp->filter);
|
|
|
|
else
|
2009-06-10 09:23:02 -04:00
|
|
|
query = sqlite3_mprintf("SELECT DISTINCT %s FROM songs WHERE data_kind = 0 AND disabled = 0 AND %s != ''",
|
2009-06-07 12:56:35 -04:00
|
|
|
field, field);
|
|
|
|
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*q = query;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_query_start(struct query_params *qp)
|
|
|
|
{
|
|
|
|
char *query;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
qp->stmt = NULL;
|
|
|
|
|
|
|
|
switch (qp->type)
|
|
|
|
{
|
|
|
|
case Q_ITEMS:
|
|
|
|
ret = db_build_query_items(qp, &query);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Q_PL:
|
|
|
|
ret = db_build_query_pls(qp, &query);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Q_PLITEMS:
|
|
|
|
ret = db_build_query_plitems(qp, &query);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Q_BROWSE_ALBUMS:
|
|
|
|
ret = db_build_query_browse(qp, "album", &query);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Q_BROWSE_ARTISTS:
|
|
|
|
ret = db_build_query_browse(qp, "artist", &query);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Q_BROWSE_GENRES:
|
|
|
|
ret = db_build_query_browse(qp, "genre", &query);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Q_BROWSE_COMPOSERS:
|
|
|
|
ret = db_build_query_browse(qp, "composer", &query);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DPRINTF(E_LOG, L_DB, "Unknown query type\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Starting query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &qp->stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
db_query_end(struct query_params *qp)
|
|
|
|
{
|
|
|
|
if (!qp->stmt)
|
|
|
|
return;
|
|
|
|
|
|
|
|
qp->results = -1;
|
|
|
|
|
|
|
|
sqlite3_finalize(qp->stmt);
|
|
|
|
qp->stmt = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_query_fetch_file(struct query_params *qp, struct db_media_file_info *dbmfi)
|
|
|
|
{
|
|
|
|
int ncols;
|
|
|
|
char **strcol;
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
memset(dbmfi, 0, sizeof(struct db_media_file_info));
|
|
|
|
|
|
|
|
if (!qp->stmt)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query not started!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((qp->type != Q_ITEMS) && (qp->type != Q_PLITEMS))
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Not an items or playlist items query!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(qp->stmt);
|
|
|
|
if (ret == SQLITE_DONE)
|
|
|
|
{
|
|
|
|
DPRINTF(E_INFO, L_DB, "End of query results\n");
|
|
|
|
dbmfi->id = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ncols = sqlite3_column_count(qp->stmt);
|
|
|
|
|
|
|
|
if (sizeof(dbmfi_cols_map) / sizeof(dbmfi_cols_map[0]) != ncols)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "BUG: dbmfi column map out of sync with schema\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ncols; i++)
|
|
|
|
{
|
|
|
|
strcol = (char **) ((char *)dbmfi + dbmfi_cols_map[i]);
|
|
|
|
|
|
|
|
*strcol = (char *)sqlite3_column_text(qp->stmt, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_query_fetch_pl(struct query_params *qp, struct db_playlist_info *dbpli)
|
|
|
|
{
|
|
|
|
int ncols;
|
|
|
|
char **strcol;
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
memset(dbpli, 0, sizeof(struct db_playlist_info));
|
|
|
|
|
|
|
|
if (!qp->stmt)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query not started!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qp->type != Q_PL)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Not a playlist query!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(qp->stmt);
|
|
|
|
if (ret == SQLITE_DONE)
|
|
|
|
{
|
|
|
|
DPRINTF(E_INFO, L_DB, "End of query results\n");
|
|
|
|
dbpli->id = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ncols = sqlite3_column_count(qp->stmt);
|
|
|
|
|
|
|
|
if (sizeof(dbpli_cols_map) / sizeof(dbpli_cols_map[0]) != ncols)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "BUG: dbpli column map out of sync with schema\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ncols; i++)
|
|
|
|
{
|
|
|
|
strcol = (char **) ((char *)dbpli + dbpli_cols_map[i]);
|
|
|
|
|
|
|
|
*strcol = (char *)sqlite3_column_text(qp->stmt, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_query_fetch_string(struct query_params *qp, char **string)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
*string = NULL;
|
|
|
|
|
|
|
|
if (!qp->stmt)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query not started!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(qp->type & Q_F_BROWSE))
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Not a browse query!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(qp->stmt);
|
|
|
|
if (ret == SQLITE_DONE)
|
|
|
|
{
|
|
|
|
DPRINTF(E_INFO, L_DB, "End of query results\n");
|
|
|
|
*string = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*string = (char *)sqlite3_column_text(qp->stmt, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Files */
|
|
|
|
int
|
|
|
|
db_files_get_count(int *count)
|
|
|
|
{
|
2009-06-10 09:23:02 -04:00
|
|
|
char *query = "SELECT COUNT(*) FROM songs WHERE disabled = 0;";
|
2009-06-07 12:56:35 -04:00
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, strlen(query) + 1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*count = sqlite3_column_int(stmt, 0);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
db_file_inc_playcount(int id)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "UPDATE songs SET play_count = play_count + 1, time_played = %" PRIi64 " WHERE id = %d;"
|
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
DPRINTF(E_LOG, L_DB, "Error incrementing play count on %d: %s\n", id, errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
db_file_ping(int id)
|
|
|
|
{
|
2009-06-10 09:23:02 -04:00
|
|
|
#define Q_TMPL "UPDATE songs SET db_timestamp = %" PRIi64 ", disabled = 0 WHERE id = %d;"
|
2009-06-07 12:56:35 -04:00
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
DPRINTF(E_LOG, L_DB, "Error pinging file %d: %s\n", id, errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_file_id_bypath(char *path, int *id)
|
|
|
|
{
|
2009-06-10 09:23:02 -04:00
|
|
|
#define Q_TMPL "SELECT id FROM songs WHERE disabled = 0 AND path = '%q';"
|
2009-06-07 12:56:35 -04:00
|
|
|
char *query;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, path);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, strlen(query) + 1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
if (ret == SQLITE_DONE)
|
|
|
|
DPRINTF(E_INFO, L_DB, "No results\n");
|
|
|
|
else
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*id = sqlite3_column_int(stmt, 0);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct media_file_info *
|
|
|
|
db_file_fetch_byquery(char *query)
|
|
|
|
{
|
|
|
|
struct media_file_info *mfi;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ncols;
|
|
|
|
char *cval;
|
|
|
|
uint32_t *ival;
|
|
|
|
uint64_t *i64val;
|
|
|
|
char **strval;
|
2009-06-10 09:23:02 -04:00
|
|
|
uint64_t disabled;
|
2009-06-07 12:56:35 -04:00
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!query)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
mfi = (struct media_file_info *)malloc(sizeof(struct media_file_info));
|
|
|
|
if (!mfi)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not allocate struct media_file_info, out of memory\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
memset(mfi, 0, sizeof(struct media_file_info));
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
free(mfi);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
if (ret == SQLITE_DONE)
|
|
|
|
DPRINTF(E_INFO, L_DB, "No results\n");
|
|
|
|
else
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
free(mfi);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ncols = sqlite3_column_count(stmt);
|
|
|
|
|
|
|
|
if (sizeof(mfi_cols_map) / sizeof(mfi_cols_map[0]) != ncols)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "BUG: mfi column map out of sync with schema\n");
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
/* Can't risk free()ing what's inside the mfi in this case... */
|
|
|
|
free(mfi);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ncols; i++)
|
|
|
|
{
|
|
|
|
switch (mfi_cols_map[i].type)
|
|
|
|
{
|
|
|
|
case DB_TYPE_CHAR:
|
|
|
|
cval = (char *)mfi + mfi_cols_map[i].offset;
|
|
|
|
|
|
|
|
*cval = sqlite3_column_int(stmt, i);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DB_TYPE_INT:
|
|
|
|
ival = (uint32_t *) ((char *)mfi + mfi_cols_map[i].offset);
|
|
|
|
|
2009-06-10 09:23:02 -04:00
|
|
|
if (mfi_cols_map[i].offset == mfi_offsetof(disabled))
|
|
|
|
{
|
|
|
|
disabled = sqlite3_column_int64(stmt, i);
|
|
|
|
*ival = (disabled != 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
*ival = sqlite3_column_int(stmt, i);
|
2009-06-07 12:56:35 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case DB_TYPE_INT64:
|
|
|
|
i64val = (uint64_t *) ((char *)mfi + mfi_cols_map[i].offset);
|
|
|
|
|
|
|
|
*i64val = sqlite3_column_int64(stmt, i);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DB_TYPE_STRING:
|
|
|
|
strval = (char **) ((char *)mfi + mfi_cols_map[i].offset);
|
|
|
|
|
|
|
|
cval = (char *)sqlite3_column_text(stmt, i);
|
|
|
|
if (cval)
|
|
|
|
*strval = strdup(cval);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DPRINTF(E_LOG, L_DB, "BUG: Unknown type %d in mfi column map\n", mfi_cols_map[i].type);
|
|
|
|
|
|
|
|
free_mfi(mfi, 0);
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
return mfi;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct media_file_info *
|
|
|
|
db_file_fetch_byid(int id)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "SELECT * FROM songs WHERE id = %d;"
|
|
|
|
struct media_file_info *mfi;
|
|
|
|
char *query;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, id);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfi = db_file_fetch_byquery(query);
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return mfi;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
struct media_file_info *
|
|
|
|
db_file_fetch_bypath(char *path)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "SELECT * FROM songs WHERE path = '%q';"
|
|
|
|
struct media_file_info *mfi;
|
|
|
|
char *query;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, path);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfi = db_file_fetch_byquery(query);
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return mfi;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_file_add(struct media_file_info *mfi)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "INSERT INTO songs (id, path, fname, title, artist, album, genre, comment, type, composer," \
|
|
|
|
" orchestra, conductor, grouping, url, bitrate, samplerate, song_length, file_size, year, track," \
|
|
|
|
" total_tracks, disc, total_discs, bpm, compilation, rating, play_count, data_kind, item_kind," \
|
|
|
|
" description, time_added, time_modified, time_played, db_timestamp, disabled, sample_count," \
|
|
|
|
" force_update, codectype, idx, has_video, contentrating, bits_per_sample, album_artist)" \
|
|
|
|
" VALUES (NULL, '%q', '%q', %Q, %Q, %Q, %Q, %Q, %Q, %Q," \
|
|
|
|
" %Q, %Q, %Q, %Q, %d, %d, %d, %" PRIi64 ", %d, %d," \
|
|
|
|
" %d, %d, %d, %d, %d, %d, %d, %d, %d," \
|
|
|
|
" %Q, %" PRIi64 ", %" PRIi64 ", %" PRIi64 ", %" PRIi64 ", %d, %" PRIi64 "," \
|
|
|
|
" %d, %Q, %d, %d, %d, %d, %Q);"
|
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
|
|
if (mfi->id != 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_DB, "Trying to update file with id > 0; use db_file_update()?\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfi->db_timestamp = (uint64_t)time(NULL);
|
|
|
|
mfi->time_added = mfi->db_timestamp;
|
|
|
|
|
|
|
|
if (mfi->time_modified == 0)
|
|
|
|
mfi->time_modified = mfi->db_timestamp;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL,
|
|
|
|
STR(mfi->path), STR(mfi->fname), mfi->title, mfi->artist, mfi->album,
|
|
|
|
mfi->genre, mfi->comment, mfi->type, mfi->composer,
|
|
|
|
mfi->orchestra, mfi->conductor, mfi->grouping, mfi->url, mfi->bitrate,
|
|
|
|
mfi->samplerate, mfi->song_length, mfi->file_size, mfi->year, mfi->track,
|
|
|
|
mfi->total_tracks, mfi->disc, mfi->total_discs, mfi->bpm, mfi->compilation,
|
|
|
|
mfi->rating, mfi->play_count, mfi->data_kind, mfi->item_kind,
|
|
|
|
mfi->description, (int64_t)mfi->time_added, (int64_t)mfi->time_modified,
|
|
|
|
(int64_t)mfi->time_played, (int64_t)mfi->db_timestamp, mfi->disabled, mfi->sample_count,
|
|
|
|
mfi->force_update, mfi->codectype, mfi->index, mfi->has_video,
|
|
|
|
mfi->contentrating, mfi->bits_per_sample, mfi->album_artist);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_file_update(struct media_file_info *mfi)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "UPDATE songs SET 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 = %Q, bitrate = %d, samplerate = %d, song_length = %d, file_size = %" PRIi64 "," \
|
|
|
|
" year = %d, track = %d, total_tracks = %d, disc = %d, total_discs = %d, bpm = %d," \
|
|
|
|
" compilation = %d, rating = %d, data_kind = %d, item_kind = %d," \
|
|
|
|
" description = %Q, time_modified = %" PRIi64 "," \
|
2009-06-10 09:23:02 -04:00
|
|
|
" db_timestamp = %" PRIi64 ", sample_count = %d," \
|
2009-06-07 12:56:35 -04:00
|
|
|
" force_update = %d, codectype = %Q, idx = %d, has_video = %d," \
|
|
|
|
" bits_per_sample = %d, album_artist = %Q WHERE id = %d;"
|
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (mfi->id == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_DB, "Trying to update file with id 0; use db_file_add()?\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfi->db_timestamp = (uint64_t)time(NULL);
|
|
|
|
|
|
|
|
if (mfi->time_modified == 0)
|
|
|
|
mfi->time_modified = mfi->db_timestamp;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL,
|
|
|
|
STR(mfi->path), STR(mfi->fname), mfi->title, mfi->artist, mfi->album, mfi->genre,
|
|
|
|
mfi->comment, mfi->type, mfi->composer, mfi->orchestra, mfi->conductor, mfi->grouping,
|
|
|
|
mfi->url, mfi->bitrate, mfi->samplerate, mfi->song_length, mfi->file_size,
|
|
|
|
mfi->year, mfi->track, mfi->total_tracks, mfi->disc, mfi->total_discs, mfi->bpm,
|
|
|
|
mfi->compilation, mfi->rating, mfi->data_kind, mfi->item_kind,
|
|
|
|
mfi->description, (int64_t)mfi->time_modified,
|
2009-06-10 09:23:02 -04:00
|
|
|
(int64_t)mfi->db_timestamp, mfi->sample_count,
|
2009-06-07 12:56:35 -04:00
|
|
|
mfi->force_update, mfi->codectype, mfi->index, mfi->has_video,
|
|
|
|
mfi->bits_per_sample, mfi->album_artist, mfi->id);
|
|
|
|
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
2009-06-10 15:36:43 -04:00
|
|
|
void
|
|
|
|
db_file_delete_bypath(char *path)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "DELETE FROM songs WHERE path = '%q';"
|
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, path);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
DPRINTF(E_LOG, L_DB, "Error deleting file: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
/* Playlists */
|
|
|
|
int
|
|
|
|
db_pl_get_count(int *count)
|
|
|
|
{
|
2009-06-10 09:23:02 -04:00
|
|
|
char *query = "SELECT COUNT(*) FROM playlists WHERE disabled = 0;";
|
2009-06-07 12:56:35 -04:00
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, strlen(query) + 1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*count = sqlite3_column_int(stmt, 0);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
db_pl_ping(int id)
|
|
|
|
{
|
2009-06-10 09:23:02 -04:00
|
|
|
#define Q_TMPL "UPDATE playlists SET db_timestamp = %" PRIi64 ", disabled = 0 WHERE id = %d;"
|
2009-06-07 12:56:35 -04:00
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
DPRINTF(E_LOG, L_DB, "Error pinging playlist %d: %s\n", id, errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
2009-06-10 15:36:43 -04:00
|
|
|
static int
|
|
|
|
db_pl_id_bypath(char *path, int *id)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "SELECT id FROM playlists WHERE path = '%q';"
|
|
|
|
char *query;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, path);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
if (ret == SQLITE_DONE)
|
|
|
|
DPRINTF(E_INFO, L_DB, "No results\n");
|
|
|
|
else
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*id = sqlite3_column_int(stmt, 0);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
static struct playlist_info *
|
|
|
|
db_pl_fetch_byquery(char *query)
|
|
|
|
{
|
|
|
|
struct playlist_info *pli;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ncols;
|
|
|
|
char *cval;
|
|
|
|
uint32_t *ival;
|
|
|
|
char **strval;
|
2009-06-10 09:23:02 -04:00
|
|
|
uint64_t disabled;
|
2009-06-07 12:56:35 -04:00
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!query)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
pli = (struct playlist_info *)malloc(sizeof(struct playlist_info));
|
|
|
|
if (!pli)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not allocate struct playlist_info, out of memory\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
memset(pli, 0, sizeof(struct playlist_info));
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
free(pli);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
if (ret == SQLITE_DONE)
|
|
|
|
DPRINTF(E_INFO, L_DB, "No results\n");
|
|
|
|
else
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
free(pli);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ncols = sqlite3_column_count(stmt);
|
|
|
|
|
|
|
|
if (sizeof(pli_cols_map) / sizeof(pli_cols_map[0]) != ncols)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "BUG: pli column map out of sync with schema\n");
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
/* Can't risk free()ing what's inside the pli in this case... */
|
|
|
|
free(pli);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ncols; i++)
|
|
|
|
{
|
|
|
|
switch (pli_cols_map[i].type)
|
|
|
|
{
|
|
|
|
case DB_TYPE_INT:
|
|
|
|
ival = (uint32_t *) ((char *)pli + pli_cols_map[i].offset);
|
|
|
|
|
2009-06-10 09:23:02 -04:00
|
|
|
if (pli_cols_map[i].offset == pli_offsetof(disabled))
|
|
|
|
{
|
|
|
|
disabled = sqlite3_column_int64(stmt, i);
|
|
|
|
*ival = (disabled != 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
*ival = sqlite3_column_int(stmt, i);
|
2009-06-07 12:56:35 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case DB_TYPE_STRING:
|
|
|
|
strval = (char **) ((char *)pli + pli_cols_map[i].offset);
|
|
|
|
|
|
|
|
cval = (char *)sqlite3_column_text(stmt, i);
|
|
|
|
if (cval)
|
|
|
|
*strval = strdup(cval);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DPRINTF(E_LOG, L_DB, "BUG: Unknown type %d in pli column map\n", pli_cols_map[i].type);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
free_pli(pli, 0);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
/* Playlist 1: all files */
|
|
|
|
if (pli->id == 1)
|
|
|
|
{
|
|
|
|
ret = db_files_get_count(&i);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Couldn't get song count for playlist 1\n");
|
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pli->items = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pli;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct playlist_info *
|
|
|
|
db_pl_fetch_bypath(char *path)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "SELECT * FROM playlists WHERE path = '%q';"
|
|
|
|
struct playlist_info *pli;
|
|
|
|
char *query;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, path);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pli = db_pl_fetch_byquery(query);
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return pli;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_pl_add(char *title, char *path, int *id)
|
|
|
|
{
|
|
|
|
#define QDUP_TMPL "SELECT COUNT(*) FROM playlists WHERE title = '%q' OR path = '%q';"
|
2009-06-10 09:23:02 -04:00
|
|
|
#define QADD_TMPL "INSERT INTO playlists (title, type, items, query, db_timestamp, disabled, path, idx)" \
|
|
|
|
" VALUES ('%q', 0, 0, NULL, %" PRIi64 ", 0, '%q', 0);"
|
2009-06-07 12:56:35 -04:00
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Check duplicates */
|
|
|
|
query = sqlite3_mprintf(QDUP_TMPL, title, path);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_column_int(stmt, 0);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
if (ret > 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_DB, "Duplicate playlist with title '%s' path '%s'\n", title, path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add */
|
|
|
|
query = sqlite3_mprintf(QADD_TMPL, title, (int64_t)time(NULL), path);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
*id = (int)sqlite3_last_insert_rowid(hdl);
|
|
|
|
if (*id == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Successful insert but no last_insert_rowid!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Added playlist %s (path %s) with id %d\n", title, path, *id);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef QDUP_TMPL
|
|
|
|
#undef QADD_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_pl_add_item(int plid, int mfid)
|
|
|
|
{
|
|
|
|
#define QPL_TMPL "SELECT title FROM playlists WHERE id = %d;"
|
|
|
|
#define QMF_TMPL "SELECT fname FROM songs WHERE id = %d;"
|
|
|
|
#define QADD_TMPL "INSERT INTO playlistitems (playlistid, songid) VALUES (%d, %d);"
|
|
|
|
char *query;
|
|
|
|
char *str;
|
|
|
|
char *errmsg;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Check playlist */
|
|
|
|
query = sqlite3_mprintf(QPL_TMPL, plid);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Playlist id %d not found\n", plid);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
str = (char *)sqlite3_column_text(stmt, 0);
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Found playlist id %d ('%s')\n", plid, str);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
/* Check media file */
|
|
|
|
query = sqlite3_mprintf(QMF_TMPL, mfid);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Media file id %d not found\n", mfid);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
str = (char *)sqlite3_column_text(stmt, 0);
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Found media file id %d (%s)\n", mfid, str);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
/* Add */
|
|
|
|
query = sqlite3_mprintf(QADD_TMPL, plid, mfid);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Added media file id %d to playlist id %d\n", mfid, plid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef QPL_TMPL
|
|
|
|
#undef QMF_TMPL
|
|
|
|
#undef QADD_TMPL
|
|
|
|
}
|
|
|
|
|
2009-06-10 15:36:43 -04:00
|
|
|
static void
|
|
|
|
db_pl_clear_items(int id)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "DELETE FROM playlistitems WHERE playlistid = %d;"
|
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, id);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
DPRINTF(E_LOG, L_DB, "Error clearing playlist %d items: %s\n", id, errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
void
|
|
|
|
db_pl_update(int id)
|
|
|
|
{
|
|
|
|
#define QPL1_TMPL "UPDATE playlists SET items = %d WHERE id = 1;"
|
2009-06-10 09:23:02 -04:00
|
|
|
#define Q_TMPL "UPDATE playlists SET items = (SELECT COUNT(*) FROM playlistitems JOIN songs" \
|
|
|
|
" ON playlistitems.songid = songs.id WHERE songs.disabled = 0 AND playlistitems.playlistid = %d) WHERE id = %d;"
|
2009-06-07 12:56:35 -04:00
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int nsongs;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (id == 1)
|
|
|
|
{
|
|
|
|
ret = db_files_get_count(&nsongs);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not get file count\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(QPL1_TMPL, nsongs);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, id, id);
|
|
|
|
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
#undef QPL1_TMPL
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
db_pl_update_all(void)
|
|
|
|
{
|
|
|
|
char *query = "SELECT id FROM playlists;";
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int plid;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, strlen(query) + 1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((ret = sqlite3_step(stmt)) == SQLITE_ROW)
|
|
|
|
{
|
|
|
|
plid = sqlite3_column_int(stmt, 0);
|
|
|
|
db_pl_update(plid);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret != SQLITE_DONE)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
db_pl_delete(int id)
|
|
|
|
{
|
2009-06-10 15:36:43 -04:00
|
|
|
#define Q_TMPL "DELETE FROM playlists WHERE id = %d;"
|
|
|
|
char *query;
|
2009-06-07 12:56:35 -04:00
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (id == 1)
|
|
|
|
return;
|
|
|
|
|
2009-06-10 15:36:43 -04:00
|
|
|
query = sqlite3_mprintf(Q_TMPL, id);
|
|
|
|
if (!query)
|
2009-06-07 12:56:35 -04:00
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-06-10 15:36:43 -04:00
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
2009-06-07 12:56:35 -04:00
|
|
|
|
2009-06-10 15:36:43 -04:00
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
DPRINTF(E_LOG, L_DB, "Error deleting playlist %d: %s\n", id, errmsg);
|
2009-06-07 12:56:35 -04:00
|
|
|
|
2009-06-10 15:36:43 -04:00
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
db_pl_clear_items(id);
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
db_pl_delete_bypath(char *path)
|
|
|
|
{
|
|
|
|
int id;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = db_pl_id_bypath(path, &id);
|
|
|
|
if (ret < 0)
|
|
|
|
return;
|
2009-06-07 12:56:35 -04:00
|
|
|
|
2009-06-10 15:36:43 -04:00
|
|
|
db_pl_delete(id);
|
2009-06-07 12:56:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-10 12:11:11 -04:00
|
|
|
/* Inotify */
|
|
|
|
int
|
|
|
|
db_watch_clear(void)
|
|
|
|
{
|
|
|
|
char *query = "DELETE FROM inotify;";
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_watch_add(struct watch_info *wi)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "INSERT INTO inotify (wd, cookie, path, toplevel, libidx) VALUES (%d, 0, '%q', %d, %d);"
|
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, wi->wd, wi->path, wi->toplevel, wi->libidx);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Error adding watch: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_watch_delete_bywd(struct watch_info *wi)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "DELETE FROM inotify WHERE wd = %d;"
|
|
|
|
char *query;
|
|
|
|
char *errmsg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, wi->wd);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
errmsg = NULL;
|
|
|
|
ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Error deleting watch: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
db_watch_get_bywd(struct watch_info *wi)
|
|
|
|
{
|
|
|
|
#define Q_TMPL "SELECT * FROM inotify WHERE wd = %d;"
|
|
|
|
char *query;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
char **strval;
|
|
|
|
char *cval;
|
|
|
|
uint32_t *ival;
|
|
|
|
int64_t cookie;
|
|
|
|
int ncols;
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
query = sqlite3_mprintf(Q_TMPL, wi->wd);
|
|
|
|
if (!query)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Watch wd %d not found\n", wi->wd);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ncols = sqlite3_column_count(stmt);
|
|
|
|
|
|
|
|
if (sizeof(wi_cols_map) / sizeof(wi_cols_map[0]) != ncols)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "BUG: wi column map out of sync with schema\n");
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ncols; i++)
|
|
|
|
{
|
|
|
|
switch (wi_cols_map[i].type)
|
|
|
|
{
|
|
|
|
case DB_TYPE_INT:
|
|
|
|
ival = (uint32_t *) ((char *)wi + wi_cols_map[i].offset);
|
|
|
|
|
|
|
|
if (wi_cols_map[i].offset == wi_offsetof(cookie))
|
|
|
|
{
|
|
|
|
cookie = sqlite3_column_int64(stmt, i);
|
|
|
|
*ival = (cookie == INOTIFY_FAKE_COOKIE) ? 0 : cookie;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
*ival = sqlite3_column_int(stmt, i);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DB_TYPE_STRING:
|
|
|
|
strval = (char **) ((char *)wi + wi_cols_map[i].offset);
|
|
|
|
|
|
|
|
cval = (char *)sqlite3_column_text(stmt, i);
|
|
|
|
if (cval)
|
|
|
|
*strval = strdup(cval);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DPRINTF(E_LOG, L_DB, "BUG: Unknown type %d in wi column map\n", wi_cols_map[i].type);
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_free(query);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef Q_TMPL
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
int
|
|
|
|
db_perthread_init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = sqlite3_open(DB_PATH, &hdl);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not open database: %s\n", sqlite3_errmsg(hdl));
|
|
|
|
|
|
|
|
sqlite3_close(hdl);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
db_perthread_deinit(void)
|
|
|
|
{
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
|
|
|
|
/* Tear down anything that's in flight */
|
|
|
|
while ((stmt = sqlite3_next_stmt(hdl, 0)))
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
sqlite3_close(hdl);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define T_ADMIN \
|
|
|
|
"CREATE TABLE IF NOT EXISTS admin(" \
|
|
|
|
" key VARCHAR(32) NOT NULL," \
|
|
|
|
" value VARCHAR(32) NOT NULL" \
|
|
|
|
");"
|
|
|
|
|
|
|
|
#define T_SONGS \
|
|
|
|
"CREATE TABLE IF NOT EXISTS songs (" \
|
|
|
|
" id INTEGER PRIMARY KEY NOT NULL," \
|
|
|
|
" path VARCHAR(4096) NOT NULL," \
|
|
|
|
" fname VARCHAR(255) NOT NULL," \
|
|
|
|
" title VARCHAR(1024) DEFAULT NULL," \
|
|
|
|
" artist VARCHAR(1024) DEFAULT NULL," \
|
|
|
|
" album VARCHAR(1024) DEFAULT NULL," \
|
|
|
|
" genre VARCHAR(255) DEFAULT NULL," \
|
|
|
|
" comment VARCHAR(4096) DEFAULT NULL," \
|
|
|
|
" type VARCHAR(255) DEFAULT NULL," \
|
|
|
|
" composer VARCHAR(1024) DEFAULT NULL," \
|
|
|
|
" orchestra VARCHAR(1024) DEFAULT NULL," \
|
|
|
|
" conductor VARCHAR(1024) DEFAULT NULL," \
|
|
|
|
" grouping VARCHAR(1024) DEFAULT NULL," \
|
|
|
|
" url VARCHAR(1024) DEFAULT NULL," \
|
|
|
|
" bitrate INTEGER DEFAULT 0," \
|
|
|
|
" samplerate INTEGER DEFAULT 0," \
|
|
|
|
" song_length INTEGER DEFAULT 0," \
|
|
|
|
" file_size INTEGER DEFAULT 0," \
|
|
|
|
" year INTEGER DEFAULT 0," \
|
|
|
|
" track INTEGER DEFAULT 0," \
|
|
|
|
" total_tracks INTEGER DEFAULT 0," \
|
|
|
|
" disc INTEGER DEFAULT 0," \
|
|
|
|
" total_discs INTEGER DEFAULT 0," \
|
|
|
|
" bpm INTEGER DEFAULT 0," \
|
|
|
|
" compilation INTEGER DEFAULT 0," \
|
|
|
|
" rating INTEGER DEFAULT 0," \
|
|
|
|
" play_count INTEGER DEFAULT 0," \
|
|
|
|
" data_kind INTEGER DEFAULT 0," \
|
|
|
|
" item_kind INTEGER DEFAULT 0," \
|
|
|
|
" description INTEGER DEFAULT 0," \
|
|
|
|
" time_added INTEGER DEFAULT 0," \
|
|
|
|
" time_modified INTEGER DEFAULT 0," \
|
|
|
|
" time_played INTEGER DEFAULT 0," \
|
|
|
|
" db_timestamp INTEGER DEFAULT 0," \
|
|
|
|
" disabled INTEGER DEFAULT 0," \
|
|
|
|
" sample_count INTEGER DEFAULT 0," \
|
|
|
|
" force_update INTEGER DEFAULT 0," \
|
|
|
|
" codectype VARCHAR(5) DEFAULT NULL," \
|
|
|
|
" idx INTEGER NOT NULL," \
|
|
|
|
" has_video INTEGER DEFAULT 0," \
|
|
|
|
" contentrating INTEGER DEFAULT 0," \
|
|
|
|
" bits_per_sample INTEGER DEFAULT 0," \
|
|
|
|
" album_artist VARCHAR(1024)" \
|
|
|
|
");"
|
|
|
|
|
|
|
|
#define T_PL \
|
|
|
|
"CREATE TABLE IF NOT EXISTS playlists (" \
|
|
|
|
" id INTEGER PRIMARY KEY NOT NULL," \
|
|
|
|
" title VARCHAR(255) NOT NULL," \
|
|
|
|
" type INTEGER NOT NULL," \
|
|
|
|
" items INTEGER NOT NULL," \
|
|
|
|
" query VARCHAR(1024)," \
|
|
|
|
" db_timestamp INTEGER NOT NULL," \
|
2009-06-10 09:23:02 -04:00
|
|
|
" disabled INTEGER DEFAULT 0," \
|
2009-06-07 12:56:35 -04:00
|
|
|
" path VARCHAR(4096)," \
|
|
|
|
" idx INTEGER NOT NULL" \
|
|
|
|
");"
|
|
|
|
|
|
|
|
#define T_PLITEMS \
|
|
|
|
"CREATE TABLE IF NOT EXISTS playlistitems (" \
|
|
|
|
" id INTEGER PRIMARY KEY NOT NULL," \
|
|
|
|
" playlistid INTEGER NOT NULL," \
|
|
|
|
" songid INTEGER NOT NULL" \
|
|
|
|
");"
|
|
|
|
|
2009-06-10 12:11:11 -04:00
|
|
|
#define T_INOTIFY \
|
|
|
|
"CREATE TABLE IF NOT EXISTS inotify (" \
|
|
|
|
" wd INTEGER PRIMARY KEY NOT NULL," \
|
|
|
|
" cookie INTEGER NOT NULL," \
|
|
|
|
" path VARCHAR(4096) NOT NULL," \
|
|
|
|
" toplevel INTEGER NOT NULL," \
|
|
|
|
" libidx INTEGER NOT NULL" \
|
|
|
|
");"
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
#define Q_PL1 \
|
|
|
|
"INSERT INTO playlists (id, title, type, items, query, db_timestamp, path, idx)" \
|
|
|
|
" VALUES(1, 'Library', 1, 0, '1', 0, '', 0);"
|
|
|
|
|
|
|
|
#define I_PATH \
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_path ON songs(path, idx);"
|
|
|
|
|
|
|
|
#define I_FILEID \
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_fileid ON playlistitems(songid ASC);"
|
|
|
|
|
|
|
|
#define I_PLITEMID \
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_playlistid ON playlistitems(playlistid, songid);"
|
|
|
|
|
2009-06-08 12:41:59 -04:00
|
|
|
|
|
|
|
#define SCHEMA_VERSION 1
|
|
|
|
#define Q_SCVER \
|
|
|
|
"INSERT INTO admin (key, value) VALUES ('schema_version', '1');"
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
struct db_init_query {
|
|
|
|
char *query;
|
|
|
|
char *desc;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct db_init_query db_init_queries[] =
|
|
|
|
{
|
|
|
|
{ T_ADMIN, "create table admin" },
|
|
|
|
{ T_SONGS, "create table songs" },
|
|
|
|
{ T_PL, "create table playlists" },
|
|
|
|
{ T_PLITEMS, "create table playlistitems" },
|
2009-06-10 12:11:11 -04:00
|
|
|
{ T_INOTIFY, "create table inotify" },
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
{ I_PATH, "create file path index" },
|
|
|
|
{ I_FILEID, "create file id index" },
|
|
|
|
{ I_PLITEMID, "create playlist id index" },
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
db_create_tables(void)
|
|
|
|
{
|
|
|
|
char *errmsg;
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
for (i = 0; i < (sizeof(db_init_queries) / sizeof(db_init_queries[0])); i++)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_DB, "DB init query: %s\n", db_init_queries[i].desc);
|
|
|
|
|
|
|
|
ret = sqlite3_exec(hdl, db_init_queries[i].query, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_FATAL, L_DB, "DB init error: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = db_query_get_count("SELECT COUNT(*) FROM playlists WHERE id = 1;");
|
|
|
|
if (ret != 1)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_DB, "Creating default playlist\n");
|
|
|
|
|
|
|
|
ret = sqlite3_exec(hdl, Q_PL1, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_FATAL, L_DB, "Could not add default playlist: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-08 12:41:59 -04:00
|
|
|
ret = db_query_get_count("SELECT COUNT(*) FROM admin WHERE key = 'schema_version';");
|
|
|
|
if (ret != 1)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_DB, "Setting schema version\n");
|
|
|
|
|
|
|
|
ret = sqlite3_exec(hdl, Q_SCVER, NULL, NULL, &errmsg);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_FATAL, L_DB, "Could not set schema version: %s\n", errmsg);
|
|
|
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-08 12:41:59 -04:00
|
|
|
static int
|
|
|
|
db_check_version(void)
|
|
|
|
{
|
|
|
|
#define Q_VER "SELECT value FROM admin WHERE key = 'schema_version';"
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", Q_VER);
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v2(hdl, Q_VER, strlen(Q_VER) + 1, &stmt, NULL);
|
|
|
|
if (ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s %d\n", sqlite3_errmsg(hdl), ret);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_step(stmt);
|
|
|
|
if (ret != SQLITE_ROW)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Could not step: %s %d\n", sqlite3_errmsg(hdl), ret);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sqlite3_column_int(stmt, 0);
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
if (ret < SCHEMA_VERSION)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Database schema outdated, schema upgrade needed\n");
|
|
|
|
/* We'll handle the upgrade */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (ret > SCHEMA_VERSION)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_DB, "Database schema is newer than the supported version\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#undef Q_VER
|
|
|
|
}
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
int
|
|
|
|
db_init(void)
|
|
|
|
{
|
|
|
|
int files;
|
|
|
|
int pls;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!sqlite3_threadsafe())
|
|
|
|
{
|
|
|
|
DPRINTF(E_FATAL, L_DB, "The SQLite3 library is not built with a threadsafe configuration\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = db_perthread_init();
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = db_create_tables();
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_FATAL, L_DB, "Could not create tables\n");
|
|
|
|
db_perthread_deinit();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-06-08 12:41:59 -04:00
|
|
|
ret = db_check_version();
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_FATAL, L_DB, "Could not check database version\n");
|
|
|
|
db_perthread_deinit();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-06-07 12:56:35 -04:00
|
|
|
ret = db_files_get_count(&files);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_FATAL, L_DB, "Could not fetch file count\n");
|
|
|
|
db_perthread_deinit();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = db_pl_get_count(&pls);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_FATAL, L_DB, "Could not fetch playlist count\n");
|
|
|
|
db_perthread_deinit();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
db_perthread_deinit();
|
|
|
|
|
2009-06-10 09:23:02 -04:00
|
|
|
DPRINTF(E_INFO, L_DB, "Database OK with %d active files and %d active playlists\n", files, pls);
|
2009-06-07 12:56:35 -04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|