enjoy/src/bin/db.c

358 lines
9.0 KiB
C
Raw Normal View History

add basic enjoy code to svn. WARNING: there is not much to see here yet! I'm just adding to svn as people seems interested in help, and it is impossible to help without the base code. IF YOU DON'T WANT TO HELP WITH CODE, DON'T EVEN BOTHER COMPILING IT YET! Enjoy is (will be) a music player fully based on Enlightenment Foundation Libraries (EFL), the goal is to have a simple yet useful music player that works on desktop and mobiles with the minimum set of dependencies and maximum efficiency. It is based on LightMediaScanner (my code[1]) to scan directories for music and create a database and does so in a very and safe efficient way by having a scanner process with lower nice priority. It is also smart enough to hint to your kernel you'll not need the scanned files anymore (posix_fadvise()) and thus it will not pollute your RAM with useless file system cache. So far it is not creating the database on its own, neither have a a library manager to add/remove directories. In order to run Enjoy you must first create your own database using "test" program from lightmediascanner sources: {{{ cd lightmediascanner ./configure && make && make install mkdir -p $HOME/.config/enjoy/media.db ./src/bin/test -i 5000 -p id3 -s $HOME/Music $HOME/.config/enjoy/media.db }}} The GUI is pretty much ugly and needs huge work. It is almost fully done in Edje, so C part is quite lean, however I did no Edje work yet, just the basics to show something (uses r | g | b rectangles for actions, you have to guess what's what ;-)) = HOW TO HELP = Read TODO file. If you're more like a coder: * src/bin/db.c follow the propsed stubs and db_songs_get() example; * src/bin/page.c add recursive paging with "folder" pages; * src/bin/win.c start/stop ecore_thread with lms_process() + lms_check(); * src/bin/win.c write library manager: rescan collection (thread as above item), add directories, remove directories. If you're more like an edje guy: * data/themes: follow eve's style, focus on the bottom toolbar, then the list items, then the page/songs. Use dummy icons (copy from eve or efenniht), we'll provide proper icons soon * add nowplaying screen * add volume using dragable (copy from efenniht's slider) [1] download code from http://git.profusion.mobi/cgit.cgi/lightmediascanner.git/ or http://lms.garage.maemo.org/ SVN revision: 52658
2010-09-23 18:37:54 -07:00
#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
*/