Audio database support.
authorGustavo Sverzut Barbieri <barbieri@gmail.com>
Thu, 22 Nov 2007 02:26:27 +0000 (23:26 -0300)
committerGustavo Sverzut Barbieri <barbieri@gmail.com>
Thu, 22 Nov 2007 23:41:27 +0000 (00:41 +0100)
This database helper will be used by upcoming MP3 parser.

src/lib/Makefile.am
src/lib/lightmediascanner_db.h
src/lib/lightmediascanner_db_audio.c [new file with mode: 0644]

index def33ab..411f58e 100644 (file)
@@ -15,7 +15,8 @@ liblightmediascanner_la_SOURCES = \
        lightmediascanner.c \
        lightmediascanner_utils.c \
        lightmediascanner_db_common.c \
-       lightmediascanner_db_image.c
+       lightmediascanner_db_image.c \
+       lightmediascanner_db_audio.c
 
 liblightmediascanner_la_LIBADD = -ldl @SQLITE3_LIBS@
 liblightmediascanner_la_LDFLAGS = -version-info @version_info@
index 3124824..541ea94 100644 (file)
@@ -73,6 +73,25 @@ extern "C" {
     API int lms_db_image_free(lms_db_image_t *lms_db_image) GNUC_NON_NULL(1);
     API int lms_db_image_add(lms_db_image_t *lms_db_image, struct lms_image_info *info) GNUC_NON_NULL(1, 2);
 
+    /* Audio Records */
+    struct lms_audio_info {
+        int64_t id;
+        struct lms_string_size title;
+        struct lms_string_size artist;
+        struct lms_string_size album;
+        struct lms_string_size genre;
+        double length;
+        unsigned char trackno;
+        unsigned char rating;
+    };
+
+    typedef struct lms_db_audio lms_db_audio_t;
+
+    API lms_db_audio_t *lms_db_audio_new(sqlite3 *db) GNUC_NON_NULL(1);
+    API int lms_db_audio_start(lms_db_audio_t *ldi) GNUC_NON_NULL(1);
+    API int lms_db_audio_free(lms_db_audio_t *lms_db_audio) GNUC_NON_NULL(1);
+    API int lms_db_audio_add(lms_db_audio_t *lms_db_audio, struct lms_audio_info *info) GNUC_NON_NULL(1, 2);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/lib/lightmediascanner_db_audio.c b/src/lib/lightmediascanner_db_audio.c
new file mode 100644 (file)
index 0000000..d69e568
--- /dev/null
@@ -0,0 +1,532 @@
+#include <lightmediascanner_db.h>
+#include "lightmediascanner_db_private.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+struct lms_db_audio {
+    sqlite3 *db;
+    sqlite3_stmt *insert_audio;
+    sqlite3_stmt *insert_artist;
+    sqlite3_stmt *insert_album;
+    sqlite3_stmt *insert_genre;
+    sqlite3_stmt *get_artist;
+    sqlite3_stmt *get_album;
+    sqlite3_stmt *get_genre;
+    int _references;
+};
+
+static lms_db_audio_t *_singleton = NULL;
+
+static int
+_db_create(sqlite3 *db, const char *name, const char *sql)
+{
+    char *err;
+    int r;
+
+    r = sqlite3_exec(db, sql, NULL, NULL, &err);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create \"%s\": %s\n", name, err);
+        sqlite3_free(err);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+_db_create_tables_if_required(sqlite3 *db)
+{
+    int ret;
+
+    ret = _db_create(db, "audios",
+        "CREATE TABLE IF NOT EXISTS audios ("
+        "id INTEGER PRIMARY KEY, "
+        "title TEXT, "
+        "album_id INTEGER, "
+        "genre_id INTEGER, "
+        "length REAL NOT NULL, "
+        "trackno INTEGER, "
+        "rating INTEGER "
+        ")");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_artists",
+        "CREATE TABLE IF NOT EXISTS audio_artists ("
+        "id INTEGER PRIMARY KEY, "
+        "name TEXT UNIQUE"
+        ")");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_albums",
+        "CREATE TABLE IF NOT EXISTS audio_albums ("
+        "id INTEGER PRIMARY KEY, "
+        "artist_id INTEGER, "
+        "name TEXT"
+        ")");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_genres",
+        "CREATE TABLE IF NOT EXISTS audio_genres ("
+        "id INTEGER PRIMARY KEY, "
+        "name TEXT UNIQUE"
+        ")");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audios_title_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audios_title_idx ON audios (title)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audios_album_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audios_album_idx ON audios (album_id)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audios_genre_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audios_genre_idx ON audios (genre_id)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_artists_name_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audio_artists_name_idx ON audio_artists (name)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_albums_name_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audio_albums_name_idx ON audio_albums (name)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_albums_artist_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audio_albums_artist_idx ON audio_albums (artist_id)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_genres_name_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audio_albums_name_idx ON audio_albums (name)");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_audios_on_files_deleted "
+        "DELETE ON files FOR EACH ROW BEGIN"
+        "   DELETE FROM audios WHERE id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_files_on_audios_deleted "
+        "DELETE ON audios FOR EACH ROW BEGIN"
+        " DELETE FROM files WHERE id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_audios_on_albums_deleted "
+        "DELETE ON audio_albums FOR EACH ROW BEGIN"
+        " DELETE FROM audios WHERE album_id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_audios_on_genres_deleted "
+        "DELETE ON audio_genres FOR EACH ROW BEGIN"
+        " DELETE FROM audios WHERE genre_id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_audio_albums_on_artists_deleted "
+        "DELETE ON audio_artists FOR EACH ROW BEGIN"
+        " DELETE FROM audio_albums WHERE artist_id = OLD.id; END;");
+
+  done:
+    return ret;
+}
+
+lms_db_audio_t *
+lms_db_audio_new(sqlite3 *db)
+{
+    lms_db_audio_t *lda;
+
+    if (_singleton) {
+        _singleton->_references++;
+        return _singleton;
+    }
+
+    if (!db)
+        return NULL;
+
+    if (_db_create_tables_if_required(db) != 0) {
+        fprintf(stderr, "ERROR: could not create tables.\n");
+        return NULL;
+    }
+
+    lda = calloc(1, sizeof(lms_db_audio_t));
+    lda->_references = 1;
+    lda->db = db;
+
+    return lda;
+}
+
+int
+lms_db_audio_start(lms_db_audio_t *lda)
+{
+    if (!lda)
+        return -1;
+
+    lda->insert_audio = lms_db_compile_stmt(lda->db,
+        "INSERT OR REPLACE INTO audios "
+        "(id, title, album_id, genre_id, length, trackno, rating) "
+        "VALUES (?, ?, ?, ?, ?, ?, ?)");
+    if (!lda->insert_audio)
+        return -2;
+
+    lda->insert_artist = lms_db_compile_stmt(lda->db,
+        "INSERT INTO audio_artists (name) VALUES (?)");
+    if (!lda->insert_artist)
+        return -3;
+
+    lda->insert_album = lms_db_compile_stmt(lda->db,
+        "INSERT INTO audio_albums (artist_id, name) VALUES (?, ?)");
+    if (!lda->insert_album)
+        return -4;
+
+    lda->insert_genre = lms_db_compile_stmt(lda->db,
+        "INSERT INTO audio_genres (name) VALUES (?)");
+    if (!lda->insert_genre)
+        return -5;
+
+    lda->get_artist = lms_db_compile_stmt(lda->db,
+        "SELECT id FROM audio_artists WHERE name = ? LIMIT 1");
+    if (!lda->get_artist)
+        return -6;
+
+    lda->get_album = lms_db_compile_stmt(lda->db,
+        "SELECT id FROM audio_albums WHERE name = ? AND artist_id = ? LIMIT 1");
+    if (!lda->get_album)
+        return -7;
+
+    lda->get_genre = lms_db_compile_stmt(lda->db,
+        "SELECT id FROM audio_genres WHERE name = ? LIMIT 1");
+    if (!lda->get_genre)
+        return -8;
+
+    return 0;
+}
+
+int
+lms_db_audio_free(lms_db_audio_t *lda)
+{
+    if (!lda)
+        return -1;
+
+    lda->_references--;
+    if (lda->_references > 0)
+        return 0;
+
+    if (lda->insert_audio)
+        lms_db_finalize_stmt(lda->insert_audio, "insert_audio");
+
+    if (lda->insert_artist)
+        lms_db_finalize_stmt(lda->insert_artist, "insert_artist");
+
+    if (lda->insert_album)
+        lms_db_finalize_stmt(lda->insert_album, "insert_album");
+
+    if (lda->insert_genre)
+        lms_db_finalize_stmt(lda->insert_genre, "insert_genre");
+
+    if (lda->get_artist)
+        lms_db_finalize_stmt(lda->get_artist, "get_artist");
+
+    if (lda->get_album)
+        lms_db_finalize_stmt(lda->get_album, "get_album");
+
+    if (lda->get_genre)
+        lms_db_finalize_stmt(lda->get_genre, "get_genre");
+
+    free(lda);
+    _singleton = NULL;
+
+    return 0;
+}
+
+static int
+_db_get_id_by_name(sqlite3_stmt *stmt, const struct lms_string_size *name, int64_t *id)
+{
+    int r, ret;
+
+    ret = lms_db_bind_text(stmt, 1, name->str, name->len);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r == SQLITE_DONE) {
+        ret = 1;
+        goto done;
+    }
+
+    if (r != SQLITE_ROW) {
+        fprintf(stderr, "ERROR: could not get id by name: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -2;
+        goto done;
+    }
+
+    *id = sqlite3_column_int64(stmt, 0);
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+
+}
+static int
+_db_insert_name(sqlite3_stmt *stmt, const struct lms_string_size *name, int64_t *id)
+{
+    int r, ret;
+
+    ret = lms_db_bind_text(stmt, 1, name->str, name->len);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert name: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -2;
+        goto done;
+    }
+
+    *id = sqlite3_last_insert_rowid(sqlite3_db_handle(stmt));
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+static int
+_db_get_artist(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id)
+{
+    return _db_get_id_by_name(lda->get_artist, &info->artist, artist_id);
+}
+
+static int
+_db_insert_artist(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id)
+{
+    int r;
+
+    if (!info->artist.str) /* fast path for unknown artist */
+        return 1;
+
+    r =_db_get_artist(lda, info, artist_id);
+    if (r == 0)
+        return 0;
+    else if (r < 0)
+        return -1;
+
+    return _db_insert_name(lda->insert_artist, &info->artist, artist_id);
+}
+
+static int
+_db_get_album(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id, int64_t *album_id)
+{
+    sqlite3_stmt *stmt;
+    int r, ret;
+
+    stmt = lda->get_album;
+
+    ret = lms_db_bind_text(stmt, 1, info->album.str, info->album.len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int64_or_null(stmt, 2, album_id);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r == SQLITE_DONE) {
+        ret = 1;
+        goto done;
+    }
+
+    if (r != SQLITE_ROW) {
+        fprintf(stderr, "ERROR: could not get album from table: %s\n",
+                sqlite3_errmsg(lda->db));
+        ret = -2;
+        goto done;
+    }
+
+    *album_id = sqlite3_column_int64(stmt, 0);
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+
+}
+
+static int
+_db_insert_album(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *album_id)
+{
+    int r, ret, ret_artist;
+    int64_t artist_id;
+    sqlite3_stmt *stmt;
+
+    if (!info->album.str) /* fast path for unknown album */
+        return 1;
+
+    ret_artist = _db_insert_artist(lda, info, &artist_id);
+    if (ret_artist < 0)
+        return -1;
+
+    r =_db_get_album(lda, info,
+                     (ret_artist == 0) ? &artist_id : NULL,
+                     album_id);
+    if (r == 0)
+        return 0;
+    else if (r < 0)
+        return -1;
+
+    stmt = lda->insert_album;
+    ret = lms_db_bind_int64_or_null(stmt, 1,
+                                    (ret_artist == 0) ? &artist_id : NULL);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 2, info->album.str, info->album.len);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert audio album: %s\n",
+                sqlite3_errmsg(lda->db));
+        ret = -3;
+        goto done;
+    }
+
+    *album_id = sqlite3_last_insert_rowid(lda->db);
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+static int
+_db_get_genre(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *genre_id)
+{
+    return _db_get_id_by_name(lda->get_genre, &info->genre, genre_id);
+}
+
+static int
+_db_insert_genre(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *genre_id)
+{
+    int r;
+
+    if (!info->genre.str) /* fast path for unknown genre */
+        return 1;
+
+    r =_db_get_genre(lda, info, genre_id);
+    if (r == 0)
+        return 0;
+    else if (r < 0)
+        return -1;
+
+    return _db_insert_name(lda->insert_genre, &info->genre, genre_id);
+}
+
+static int
+_db_insert_audio(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *album_id, int64_t *genre_id)
+{
+    sqlite3_stmt *stmt;
+    int r, ret;
+
+    stmt = lda->insert_audio;
+    ret = lms_db_bind_int64(stmt, 1, info->id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 2, info->title.str, info->title.len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int64_or_null(stmt, 3, album_id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int64_or_null(stmt, 4, genre_id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_double(stmt, 5, info->length);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 6, info->trackno);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 7, info->rating);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert audio info: %s\n",
+                sqlite3_errmsg(lda->db));
+        ret = -8;
+        goto done;
+    }
+
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+int
+lms_db_audio_add(lms_db_audio_t *lda, struct lms_audio_info *info)
+{
+    int64_t album_id, genre_id;
+    int ret_album, ret_genre;
+
+    if (!lda)
+        return -1;
+    if (!info)
+        return -2;
+    if (info->id < 1)
+        return -3;
+
+    ret_album = _db_insert_album(lda, info, &album_id);
+    if (ret_album < 0)
+        return -4;
+
+    ret_genre = _db_insert_genre(lda, info, &genre_id);
+    if (ret_genre < 0)
+        return -5;
+
+    return _db_insert_audio(lda, info,
+                            (ret_album == 0) ? &album_id : NULL,
+                            (ret_genre == 0) ? &genre_id : NULL);
+}