enjoy/src/bin/db.c

358 lines
9.0 KiB
C

#include "private.h"
#include <sqlite3.h>
struct _DB
{
const char *path;
sqlite3 *handle;
struct {
/* just pre-compile most used and complex operations, no need to do so
* for simple update statements like "set song playcnt".
*/
sqlite3_stmt *songs_get;
} stmt;
};
static Eina_Bool
_db_stmt_reset(sqlite3_stmt *stmt)
{
Eina_Bool r, c;
r = sqlite3_reset(stmt) == SQLITE_OK;
if (!r)
ERR("could not reset SQL statement");
c = sqlite3_clear_bindings(stmt) == SQLITE_OK;
if (!c)
ERR("could not clear SQL");
return r && c;
}
static Eina_Bool
_db_stmt_finalize(sqlite3_stmt *stmt, const char *name)
{
int r = sqlite3_finalize(stmt);
if (r != SQLITE_OK)
ERR("could not finalize %s statement: #%d\n", name, r);
return r == SQLITE_OK;
}
static sqlite3_stmt *
_db_stmt_compile(DB *db, const char *name, const char *sql)
{
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db->handle, sql, -1, &stmt, NULL) != SQLITE_OK)
ERR("could not prepare %s sql=\"%s\": %s",
name, sql, sqlite3_errmsg(db->handle));
return stmt;
}
static Eina_Bool
_db_stmts_compile(DB *db)
{
#define C(m, sql) \
do \
{ \
db->stmt.m = _db_stmt_compile(db, stringify(m), sql); \
if (!db->stmt.m) return EINA_FALSE; \
} \
while (0)
C(songs_get,
"SELECT files.id, files.path, files.size, "
" audios.title, audios.album_id, audios.artist_id, audios.genre_id, "
" audios.trackno, audios.rating, audios.playcnt, audios.length, "
" audio_albums.name, audio_artists.name, audio_genres.name "
"FROM audios, files, audio_albums, audio_artists, audio_genres "
"WHERE "
" files.id = audios.id AND "
" audio_albums.id = audios.album_id AND "
" audio_artists.id = audios.artist_id AND "
" audio_genres.id = audios.genre_id "
"ORDER BY UPPER(audios.title)");
#undef C
return EINA_TRUE;
}
static Eina_Bool
_db_stmts_finalize(DB *db)
{
Eina_Bool ret = EINA_TRUE;
#define F(m) \
ret &= _db_stmt_finalize(db->stmt.m, stringify(m));
F(songs_get);
#undef F
return ret;
}
DB *
db_open(const char *path)
{
DB *db;
EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL);
db = calloc(1, sizeof(DB));
EINA_SAFETY_ON_NULL_RETURN_VAL(db, NULL);
db->path = eina_stringshare_add(path);
if (sqlite3_open(path, &db->handle) != SQLITE_OK)
{
CRITICAL("Could not open database '%s'", db->path);
goto error;
}
if (!_db_stmts_compile(db))
{
CRITICAL("Could not compile statements.");
goto error;
}
return db;
error:
_db_stmts_finalize(db);
sqlite3_close(db->handle);
eina_stringshare_del(db->path);
free(db);
return NULL;
}
Eina_Bool
db_close(DB *db)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(db, EINA_FALSE);
_db_stmts_finalize(db);
sqlite3_close(db->handle);
eina_stringshare_del(db->path);
free(db);
return EINA_TRUE;
}
Song *db_song_copy(const Song *orig)
{
// TODO: move to mempool to avoid fragmentation!
Song *copy;
EINA_SAFETY_ON_NULL_RETURN_VAL(orig, NULL);
copy = malloc(sizeof(Song));
EINA_SAFETY_ON_NULL_RETURN_VAL(copy, NULL);
/* Note: cannot use eina_stringshare_ref() as value may be from sqlite
* during iterator walks.
*/
#define STR(m) \
copy->m = eina_stringshare_add(orig->m); \
copy->len.m = orig->len.m
STR(path);
STR(title);
STR(album);
STR(artist);
#undef STR
#define N(m) copy->m = orig->m
N(id);
N(album_id);
N(artist_id);
N(genre_id);
N(size);
N(trackno);
N(rating);
N(playcnt);
N(length);
#undef N
return copy;
}
void
db_song_free(Song *song)
{
eina_stringshare_del(song->path);
eina_stringshare_del(song->title);
eina_stringshare_del(song->album);
eina_stringshare_del(song->artist);
free(song);
}
struct DB_Iterator
{
Eina_Iterator base;
DB *db;
sqlite3_stmt *stmt;
};
struct DB_Iterator_Songs
{
struct DB_Iterator base;
Song song;
};
static void *
_db_iterator_container_get(Eina_Iterator *iterator)
{
struct DB_Iterator *it = (struct DB_Iterator *)iterator;
if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR))
{
EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR);
return NULL;
}
return it->db;
}
static void
_db_iterator_free(Eina_Iterator *iterator)
{
struct DB_Iterator *it = (struct DB_Iterator *)iterator;
if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR))
{
EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR);
return;
}
_db_stmt_reset(it->stmt);
EINA_MAGIC_SET(&it->base, EINA_MAGIC_NONE);
free(it);
}
static Eina_Bool
_db_iterator_songs_next(Eina_Iterator *iterator, void **data)
{
struct DB_Iterator_Songs *it = (struct DB_Iterator_Songs *)iterator;
Song **song = (Song **)data;
int r;
EINA_SAFETY_ON_NULL_RETURN_VAL(song, EINA_FALSE);
*song = NULL;
if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR))
{
EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR);
return EINA_FALSE;
}
r = sqlite3_step(it->base.stmt);
if (r == SQLITE_DONE)
return EINA_FALSE;
if (r != SQLITE_ROW)
{
ERR("Error executing sql statement: %s",
sqlite3_errmsg(it->base.db->handle));
return EINA_FALSE;
}
#define ID(m, c) \
it->song.m = sqlite3_column_int64(it->base.stmt, c);
#define INT(m, c) \
it->song.m = sqlite3_column_int(it->base.stmt, c);
#define STR(m, c) \
it->song.m = (const char *)sqlite3_column_text(it->base.stmt, c); \
it->song.len.m = sqlite3_column_bytes(it->base.stmt, c)
ID(id, 0);
STR(path, 1);
INT(size, 2);
STR(title, 3);
ID(album_id, 4);
ID(artist_id, 5);
ID(genre_id, 6);
INT(trackno, 7);
INT(rating, 8);
INT(playcnt, 9);
INT(length, 10);
STR(album, 11);
STR(artist, 12);
STR(genre, 13);
#undef STR
#undef INT
#undef ID
*song = &it->song;
return EINA_TRUE;
}
Eina_Iterator *
db_songs_get(DB *db)
{
struct DB_Iterator_Songs *it;
EINA_SAFETY_ON_NULL_RETURN_VAL(db, NULL);
it = calloc(1, sizeof(*it));
EINA_SAFETY_ON_NULL_RETURN_VAL(it, NULL);
it->base.base.version = EINA_ITERATOR_VERSION;
it->base.base.next = _db_iterator_songs_next;
it->base.base.get_container = _db_iterator_container_get;
it->base.base.free = _db_iterator_free;
it->base.db = db;
it->base.stmt = db->stmt.songs_get;
EINA_MAGIC_SET(&it->base.base, EINA_MAGIC_ITERATOR);
return &it->base.base;
}
Eina_Bool
db_song_rating_set(DB *db, Song *song, int rating)
{
DBG("TODO");
return EINA_TRUE;
}
Eina_Bool
db_song_length_set(DB *db, Song *song, int length)
{
DBG("TODO");
return EINA_TRUE;
}
/*
TODO: filters:
// filter: free form string from user
// keep all searches with filter, not used now
// in future split on whitespace and search all terms in all fields (ie: even when listing artists, if term is Metallica, artist name would match and just "heavy metal" term will be shown
// possibly we'll take specifiers, like artist:metallica
// filters are case insensitive!
Eina_Bool db_path_del(DB *db, const char *path); // delete from ... where url like '$path/%', used to remove directories from media collection
// iterators return temporary struct and values straight from sqlite (= zero copy), use functions to copy when required:
Song *song_copy(const Song *);
Album *album_copy(const Album *);
Artist *artist_copy(const Artist *);
// use mempool + stringshare
void song_free(Song *);
void album_free(Album *);
void artist_free(Artist *);
Song *db_song_get(DB *db, id); // ret song
Eina_Bool db_song_rating_set(DB *db, Song *song, int rating);
Eina_Iterator *db_songs_get(DB *db); // ret song
Eina_Iterator *db_albums_get(DB *db); // ret albums (name, id)
Eina_Iterator *db_artists_get(DB *db); // ret artist (name, id)
Eina_Iterator *db_genres_get(DB *db); // ret genres (name, id)
Eina_Iterator *db_album_songs_get(DB *db, id); // ret all songs from album id
Eina_Iterator *db_artist_songs_get(DB *db, id); // ret all songs from artist id
Eina_Iterator *db_artist_albums_get(DB *db, id); // ret all albums (name, id) from artist id
Eina_Iterator *db_artist_album_songs_get(DB *db, id_artist, id_album); // ret all songs (name, id) from album from artist
Eina_Iterator *db_genre_songs_get(DB *db, id); // ret all songs from genre id
Eina_Iterator *db_genre_artists_get(DB *db, id); // ret all artists (name, id) from genre id
Eina_Iterator *db_genre_artist_albums_get(DB *db, id_genre, id_artist); // ret all albums from artist from genre
Eina_Iterator *db_genre_artist_album_songs_get(DB *db, id_genre, id_artist, id_album); // ret all songs from albums from artist from genre
*/