diff --git a/configure.ac b/configure.ac index e89f9d46..1c02939a 100644 --- a/configure.ac +++ b/configure.ac @@ -122,6 +122,8 @@ FORK_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0], [dnl Check that SQLite3 has the unlock notify API built-in AC_CHECK_FUNC([[sqlite3_unlock_notify]], [], [AC_MSG_ERROR([[SQLite3 was built without unlock notify support]])]) + dnl Check for sqlite3_expanded_sql (optional) + AC_CHECK_FUNCS([sqlite3_expanded_sql]) dnl Check that SQLite3 has been built with threadsafe operations AC_MSG_CHECKING([[if SQLite3 was built with threadsafe operations support]]) AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include diff --git a/forked-daapd.conf.in b/forked-daapd.conf.in index 5a6c16ea..44e403fc 100644 --- a/forked-daapd.conf.in +++ b/forked-daapd.conf.in @@ -95,11 +95,13 @@ library { # (changing this setting only takes effect after rescan, see the README) compilations = { "/Compilations" } - # Compilations usually have many artists, and if you don't want every - # artist to be listed when artist browsing in Remote, you can set - # a single name which will be used for all music in the compilation dir + # Compilations usually have many artists, and sometimes no album artist. + # If you don't want every artist to be listed in artist views, you can + # set a single name which will be used for all compilation tracks + # without an album artist, and for all tracks in the compilation + # directories. # (changing this setting only takes effect after rescan, see the README) - compilation_artist = "Various artists" + compilation_artist = "Various Artists" # If your album and artist lists are cluttered, you can choose to hide # albums and artists with only one track. The tracks will still be diff --git a/sqlext/sqlext.c b/sqlext/sqlext.c index 56f65a90..6ad16543 100644 --- a/sqlext/sqlext.c +++ b/sqlext/sqlext.c @@ -208,6 +208,34 @@ sqlext_daap_songalbumid_xfunc(sqlite3_context *pv, int n, sqlite3_value **ppv) sqlite3_result_int64(pv, result); } +static void +sqlext_daap_no_zero_xfunc(sqlite3_context *pv, int n, sqlite3_value **ppv) +{ + sqlite3_int64 new_value; + sqlite3_int64 old_value; + + if (n != 2) + { + sqlite3_result_error(pv, "daap_no_zero() requires 2 parameters, new_value and old_value", -1); + return; + } + + if ((sqlite3_value_type(ppv[0]) != SQLITE_INTEGER) + || (sqlite3_value_type(ppv[1]) != SQLITE_INTEGER)) + { + sqlite3_result_error(pv, "daap_no_zero() requires 2 integer parameters", -1); + return; + } + + new_value = sqlite3_value_int64(ppv[0]); + old_value = sqlite3_value_int64(ppv[1]); + + if (new_value != 0) + sqlite3_result_int64(pv, new_value); + else + sqlite3_result_int64(pv, old_value); +} + static int sqlext_daap_unicode_xcollation(void *notused, int llen, const void *left, int rlen, const void *right) { @@ -259,6 +287,15 @@ sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines return -1; } + ret = sqlite3_create_function(db, "daap_no_zero", 2, SQLITE_UTF8, NULL, sqlext_daap_no_zero_xfunc, NULL, NULL); + if (ret != SQLITE_OK) + { + if (pzErrMsg) + *pzErrMsg = sqlite3_mprintf("Could not create daap_no_zero function: %s\n", sqlite3_errmsg(db)); + + return -1; + } + ret = sqlite3_create_collation(db, "DAAP", SQLITE_UTF8, NULL, sqlext_daap_unicode_xcollation); if (ret != SQLITE_OK) { diff --git a/src/db.c b/src/db.c index 01b0e6c6..a77cd6ea 100644 --- a/src/db.c +++ b/src/db.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -54,28 +55,75 @@ #define STR(x) ((x) ? (x) : "") -/* Inotify cookies are uint32_t */ +// Inotify cookies are uint32_t #define INOTIFY_FAKE_COOKIE ((int64_t)1 << 32) +#define DB_TYPE_CHAR 1 +#define DB_TYPE_INT 2 +#define DB_TYPE_INT64 3 +#define DB_TYPE_STRING 4 + +// Flags that column value is set automatically by the db, e.g. by a trigger +#define DB_FLAG_AUTO (1 << 0) +// Flags that we will only update column value if we have non-zero value (to avoid zeroing e.g. rating) +#define DB_FLAG_NO_ZERO (1 << 1) + enum group_type { G_ALBUMS = 1, G_ARTISTS = 2, }; +enum fixup_type { + DB_FIXUP_STANDARD = 0, + DB_FIXUP_NO_SANITIZE, + DB_FIXUP_TITLE, + DB_FIXUP_ARTIST, + DB_FIXUP_ALBUM, + DB_FIXUP_ALBUM_ARTIST, + DB_FIXUP_GENRE, + DB_FIXUP_COMPOSER, + DB_FIXUP_TYPE, + DB_FIXUP_CODECTYPE, + DB_FIXUP_MEDIA_KIND, + DB_FIXUP_ITEM_KIND, + DB_FIXUP_TITLE_SORT, + DB_FIXUP_ARTIST_SORT, + DB_FIXUP_ALBUM_SORT, + DB_FIXUP_ALBUM_ARTIST_SORT, + DB_FIXUP_COMPOSER_SORT, + DB_FIXUP_TIME_ADDED, + DB_FIXUP_TIME_MODIFIED, +}; + struct db_unlock { int proceed; pthread_cond_t cond; pthread_mutex_t lck; }; -#define DB_TYPE_CHAR 1 -#define DB_TYPE_INT 2 -#define DB_TYPE_INT64 3 -#define DB_TYPE_STRING 4 +struct db_statements +{ + sqlite3_stmt *files_insert; + sqlite3_stmt *files_update; + sqlite3_stmt *files_ping; +}; struct col_type_map { - ssize_t offset; + char *name; + size_t offset; short type; + enum fixup_type fixup; + short flag; +}; + +struct fixup_ctx +{ + const struct col_type_map *map; + size_t map_size; + void *data; + struct media_file_info *mfi; + struct playlist_info *pli; + struct db_queue_item *queue_item; }; struct query_clause { @@ -98,68 +146,68 @@ struct browse_clause { */ static const 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(artwork), DB_TYPE_CHAR }, - { mfi_offsetof(rating), DB_TYPE_INT }, - { mfi_offsetof(play_count), DB_TYPE_INT }, - { mfi_offsetof(seek), 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(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 }, - { mfi_offsetof(media_kind), DB_TYPE_INT }, - { mfi_offsetof(tv_series_name), DB_TYPE_STRING }, - { mfi_offsetof(tv_episode_num_str), DB_TYPE_STRING }, - { mfi_offsetof(tv_network_name), DB_TYPE_STRING }, - { mfi_offsetof(tv_episode_sort), DB_TYPE_INT }, - { mfi_offsetof(tv_season_num), DB_TYPE_INT }, - { mfi_offsetof(songartistid), DB_TYPE_INT64 }, - { mfi_offsetof(songalbumid), DB_TYPE_INT64 }, - { mfi_offsetof(title_sort), DB_TYPE_STRING }, - { mfi_offsetof(artist_sort), DB_TYPE_STRING }, - { mfi_offsetof(album_sort), DB_TYPE_STRING }, - { mfi_offsetof(composer_sort), DB_TYPE_STRING }, - { mfi_offsetof(album_artist_sort), DB_TYPE_STRING }, - { mfi_offsetof(virtual_path), DB_TYPE_STRING }, - { mfi_offsetof(directory_id), DB_TYPE_INT }, - { mfi_offsetof(date_released), DB_TYPE_INT }, - { mfi_offsetof(skip_count), DB_TYPE_INT }, - { mfi_offsetof(time_skipped), DB_TYPE_INT }, + { "id", mfi_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_AUTO }, + { "path", mfi_offsetof(path), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "virtual_path", mfi_offsetof(virtual_path), DB_TYPE_STRING }, + { "fname", mfi_offsetof(fname), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "directory_id", mfi_offsetof(directory_id), DB_TYPE_INT }, + { "title", mfi_offsetof(title), DB_TYPE_STRING, DB_FIXUP_TITLE }, + { "artist", mfi_offsetof(artist), DB_TYPE_STRING, DB_FIXUP_ARTIST }, + { "album", mfi_offsetof(album), DB_TYPE_STRING, DB_FIXUP_ALBUM }, + { "album_artist", mfi_offsetof(album_artist), DB_TYPE_STRING, DB_FIXUP_ALBUM_ARTIST }, + { "genre", mfi_offsetof(genre), DB_TYPE_STRING, DB_FIXUP_GENRE }, + { "comment", mfi_offsetof(comment), DB_TYPE_STRING }, + { "type", mfi_offsetof(type), DB_TYPE_STRING, DB_FIXUP_TYPE }, + { "composer", mfi_offsetof(composer), DB_TYPE_STRING, DB_FIXUP_COMPOSER }, + { "orchestra", mfi_offsetof(orchestra), DB_TYPE_STRING }, + { "conductor", mfi_offsetof(conductor), DB_TYPE_STRING }, + { "grouping", mfi_offsetof(grouping), DB_TYPE_STRING }, + { "url", mfi_offsetof(url), DB_TYPE_STRING }, + { "bitrate", mfi_offsetof(bitrate), DB_TYPE_INT }, + { "samplerate", mfi_offsetof(samplerate), DB_TYPE_INT }, + { "song_length", mfi_offsetof(song_length), DB_TYPE_INT }, + { "file_size", mfi_offsetof(file_size), DB_TYPE_INT64 }, + { "year", mfi_offsetof(year), DB_TYPE_INT }, + { "date_released", mfi_offsetof(date_released), DB_TYPE_INT }, + { "track", mfi_offsetof(track), DB_TYPE_INT }, + { "total_tracks", mfi_offsetof(total_tracks), DB_TYPE_INT }, + { "disc", mfi_offsetof(disc), DB_TYPE_INT }, + { "total_discs", mfi_offsetof(total_discs), DB_TYPE_INT }, + { "bpm", mfi_offsetof(bpm), DB_TYPE_INT }, + { "compilation", mfi_offsetof(compilation), DB_TYPE_CHAR }, + { "artwork", mfi_offsetof(artwork), DB_TYPE_CHAR }, + { "rating", mfi_offsetof(rating), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_ZERO }, + { "play_count", mfi_offsetof(play_count), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_ZERO }, + { "skip_count", mfi_offsetof(skip_count), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_ZERO }, + { "seek", mfi_offsetof(seek), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_ZERO }, + { "data_kind", mfi_offsetof(data_kind), DB_TYPE_INT }, + { "media_kind", mfi_offsetof(media_kind), DB_TYPE_INT, DB_FIXUP_MEDIA_KIND }, + { "item_kind", mfi_offsetof(item_kind), DB_TYPE_INT, DB_FIXUP_ITEM_KIND }, + { "description", mfi_offsetof(description), DB_TYPE_STRING }, + { "db_timestamp", mfi_offsetof(db_timestamp), DB_TYPE_INT }, + { "time_added", mfi_offsetof(time_added), DB_TYPE_INT, DB_FIXUP_TIME_ADDED }, + { "time_modified", mfi_offsetof(time_modified), DB_TYPE_INT, DB_FIXUP_TIME_MODIFIED }, + { "time_played", mfi_offsetof(time_played), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_ZERO }, + { "time_skipped", mfi_offsetof(time_skipped), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_ZERO }, + { "disabled", mfi_offsetof(disabled), DB_TYPE_INT }, + { "sample_count", mfi_offsetof(sample_count), DB_TYPE_INT64 }, + { "codectype", mfi_offsetof(codectype), DB_TYPE_STRING, DB_FIXUP_CODECTYPE }, + { "idx", mfi_offsetof(idx), DB_TYPE_INT }, + { "has_video", mfi_offsetof(has_video), DB_TYPE_INT }, + { "contentrating", mfi_offsetof(contentrating), DB_TYPE_INT }, + { "bits_per_sample", mfi_offsetof(bits_per_sample), DB_TYPE_INT }, + { "tv_series_name", mfi_offsetof(tv_series_name), DB_TYPE_STRING }, + { "tv_episode_num_str", mfi_offsetof(tv_episode_num_str), DB_TYPE_STRING }, + { "tv_network_name", mfi_offsetof(tv_network_name), DB_TYPE_STRING }, + { "tv_episode_sort", mfi_offsetof(tv_episode_sort), DB_TYPE_INT }, + { "tv_season_num", mfi_offsetof(tv_season_num), DB_TYPE_INT }, + { "songartistid", mfi_offsetof(songartistid), DB_TYPE_INT64, DB_FIXUP_STANDARD, DB_FLAG_AUTO }, + { "songalbumid", mfi_offsetof(songalbumid), DB_TYPE_INT64, DB_FIXUP_STANDARD, DB_FLAG_AUTO }, + { "title_sort", mfi_offsetof(title_sort), DB_TYPE_STRING, DB_FIXUP_TITLE_SORT }, + { "artist_sort", mfi_offsetof(artist_sort), DB_TYPE_STRING, DB_FIXUP_ARTIST_SORT }, + { "album_sort", mfi_offsetof(album_sort), DB_TYPE_STRING, DB_FIXUP_ALBUM_SORT }, + { "album_artist_sort", mfi_offsetof(album_artist_sort), DB_TYPE_STRING, DB_FIXUP_ALBUM_ARTIST_SORT }, + { "composer_sort", mfi_offsetof(composer_sort), DB_TYPE_STRING, DB_FIXUP_COMPOSER_SORT }, }; /* This list must be kept in sync with @@ -168,24 +216,57 @@ static const struct col_type_map mfi_cols_map[] = */ static const 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(query), DB_TYPE_STRING }, - { pli_offsetof(db_timestamp), DB_TYPE_INT }, - { pli_offsetof(disabled), DB_TYPE_INT }, - { pli_offsetof(path), DB_TYPE_STRING }, - { pli_offsetof(index), DB_TYPE_INT }, - { pli_offsetof(special_id), DB_TYPE_INT }, - { pli_offsetof(virtual_path), DB_TYPE_STRING }, - { pli_offsetof(parent_id), DB_TYPE_INT }, - { pli_offsetof(directory_id), DB_TYPE_INT }, - { pli_offsetof(query_order),DB_TYPE_STRING }, - { pli_offsetof(query_limit), DB_TYPE_INT }, + { "id", pli_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_AUTO }, + { "title", pli_offsetof(title), DB_TYPE_STRING, DB_FIXUP_TITLE }, + { "type", pli_offsetof(type), DB_TYPE_INT }, + { "query", pli_offsetof(query), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "db_timestamp", pli_offsetof(db_timestamp), DB_TYPE_INT }, + { "disabled", pli_offsetof(disabled), DB_TYPE_INT }, + { "path", pli_offsetof(path), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "idx", pli_offsetof(index), DB_TYPE_INT }, + { "special_id", pli_offsetof(special_id), DB_TYPE_INT }, + { "virtual_path", pli_offsetof(virtual_path), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "parent_id", pli_offsetof(parent_id), DB_TYPE_INT }, + { "directory_id", pli_offsetof(directory_id), DB_TYPE_INT }, + { "query_order", pli_offsetof(query_order), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "query_limit", pli_offsetof(query_limit), DB_TYPE_INT }, /* items is computed on the fly */ }; +/* This list must be kept in sync with + * - the order of the columns in the queue table + * - the type and name of the fields in struct db_queue_item + */ +static const struct col_type_map qi_cols_map[] = + { + { "id", qi_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_AUTO }, + { "file_id", qi_offsetof(id), DB_TYPE_INT }, + { "pos", qi_offsetof(pos), DB_TYPE_INT }, + { "shuffle_pos", qi_offsetof(shuffle_pos), DB_TYPE_INT }, + { "data_kind", qi_offsetof(data_kind), DB_TYPE_INT }, + { "media_kind", qi_offsetof(media_kind), DB_TYPE_INT, DB_FIXUP_MEDIA_KIND }, + { "song_length", qi_offsetof(song_length), DB_TYPE_INT }, + { "path", qi_offsetof(path), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "virtual_path", qi_offsetof(virtual_path), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "title", qi_offsetof(title), DB_TYPE_STRING, DB_FIXUP_TITLE }, + { "artist", qi_offsetof(artist), DB_TYPE_STRING, DB_FIXUP_ARTIST }, + { "album_artist", qi_offsetof(album_artist), DB_TYPE_STRING, DB_FIXUP_ALBUM_ARTIST }, + { "album", qi_offsetof(album), DB_TYPE_STRING, DB_FIXUP_ALBUM }, + { "genre", qi_offsetof(genre), DB_TYPE_STRING, DB_FIXUP_GENRE }, + { "songalbumid", qi_offsetof(songalbumid), DB_TYPE_INT64 }, + { "time_modified", qi_offsetof(time_modified), DB_TYPE_INT }, + { "artist_sort", qi_offsetof(artist_sort), DB_TYPE_STRING, DB_FIXUP_ARTIST_SORT }, + { "album_sort", qi_offsetof(album_sort), DB_TYPE_STRING, DB_FIXUP_ALBUM_SORT }, + { "album_artist_sort", qi_offsetof(album_artist_sort), DB_TYPE_STRING, DB_FIXUP_ALBUM_ARTIST_SORT }, + { "year", qi_offsetof(year), DB_TYPE_INT }, + { "track", qi_offsetof(track), DB_TYPE_INT }, + { "disc", qi_offsetof(disc), DB_TYPE_INT }, + { "artwork_url", qi_offsetof(artwork_url), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, + { "queue_version", qi_offsetof(queue_version), DB_TYPE_INT }, + { "composer", qi_offsetof(composer), DB_TYPE_STRING, DB_FIXUP_COMPOSER }, + }; + /* This list must be kept in sync with * - the order of the columns in the files table * - the name of the fields in struct db_media_file_info @@ -194,10 +275,13 @@ static const ssize_t dbmfi_cols_map[] = { dbmfi_offsetof(id), dbmfi_offsetof(path), + dbmfi_offsetof(virtual_path), dbmfi_offsetof(fname), + dbmfi_offsetof(directory_id), dbmfi_offsetof(title), dbmfi_offsetof(artist), dbmfi_offsetof(album), + dbmfi_offsetof(album_artist), dbmfi_offsetof(genre), dbmfi_offsetof(comment), dbmfi_offsetof(type), @@ -211,6 +295,7 @@ static const ssize_t dbmfi_cols_map[] = dbmfi_offsetof(song_length), dbmfi_offsetof(file_size), dbmfi_offsetof(year), + dbmfi_offsetof(date_released), dbmfi_offsetof(track), dbmfi_offsetof(total_tracks), dbmfi_offsetof(disc), @@ -220,14 +305,17 @@ static const ssize_t dbmfi_cols_map[] = dbmfi_offsetof(artwork), dbmfi_offsetof(rating), dbmfi_offsetof(play_count), + dbmfi_offsetof(skip_count), dbmfi_offsetof(seek), dbmfi_offsetof(data_kind), + dbmfi_offsetof(media_kind), dbmfi_offsetof(item_kind), dbmfi_offsetof(description), + dbmfi_offsetof(db_timestamp), dbmfi_offsetof(time_added), dbmfi_offsetof(time_modified), dbmfi_offsetof(time_played), - dbmfi_offsetof(db_timestamp), + dbmfi_offsetof(time_skipped), dbmfi_offsetof(disabled), dbmfi_offsetof(sample_count), dbmfi_offsetof(codectype), @@ -235,8 +323,6 @@ static const ssize_t dbmfi_cols_map[] = dbmfi_offsetof(has_video), dbmfi_offsetof(contentrating), dbmfi_offsetof(bits_per_sample), - dbmfi_offsetof(album_artist), - dbmfi_offsetof(media_kind), dbmfi_offsetof(tv_series_name), dbmfi_offsetof(tv_episode_num_str), dbmfi_offsetof(tv_network_name), @@ -247,13 +333,8 @@ static const ssize_t dbmfi_cols_map[] = dbmfi_offsetof(title_sort), dbmfi_offsetof(artist_sort), dbmfi_offsetof(album_sort), - dbmfi_offsetof(composer_sort), dbmfi_offsetof(album_artist_sort), - dbmfi_offsetof(virtual_path), - dbmfi_offsetof(directory_id), - dbmfi_offsetof(date_released), - dbmfi_offsetof(skip_count), - dbmfi_offsetof(time_skipped), + dbmfi_offsetof(composer_sort), }; /* This list must be kept in sync with @@ -303,9 +384,9 @@ static const ssize_t dbgri_cols_map[] = */ static const 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 }, + { "wd", wi_offsetof(wd), DB_TYPE_INT, DB_FLAG_AUTO }, + { "cookie", wi_offsetof(cookie), DB_TYPE_INT }, + { "path", wi_offsetof(path), DB_TYPE_STRING }, }; /* Sort clauses, used for ORDER BY */ @@ -351,6 +432,7 @@ struct media_kind_label { const char *label; }; + /* Keep in sync with enum media_kind */ static const struct media_kind_label media_kind_labels[] = { @@ -412,7 +494,9 @@ struct rng_ctx shuffle_rng; static char *db_path; static bool db_rating_updates; + static __thread sqlite3 *hdl; +static __thread struct db_statements db_statements; /* Forward */ @@ -624,42 +708,8 @@ free_queue_item(struct db_queue_item *queue_item, int content_only) memset(queue_item, 0, sizeof(struct db_queue_item)); } -void -unicode_fixup_mfi(struct media_file_info *mfi) -{ - char *ret; - char **field; - int i; - - for (i = 0; i < ARRAY_SIZE(mfi_cols_map); i++) - { - if (mfi_cols_map[i].type != DB_TYPE_STRING) - continue; - - switch (mfi_cols_map[i].offset) - { - case mfi_offsetof(path): - case mfi_offsetof(fname): - case mfi_offsetof(codectype): - continue; - } - - field = (char **) ((char *)mfi + mfi_cols_map[i].offset); - - if (!*field) - continue; - - ret = unicode_fixup_string(*field, "ascii"); - if (ret != *field) - { - free(*field); - *field = ret; - } - } -} - static void -sort_tag_create(char **sort_tag, char *src_tag) +sort_tag_create(char **sort_tag, const char *src_tag) { const uint8_t *i_ptr; const uint8_t *n_ptr; @@ -759,221 +809,321 @@ sort_tag_create(char **sort_tag, char *src_tag) *sort_tag = (char *)u8_normalize(UNINORM_NFD, (uint8_t *)&out, u8_strlen(out) + 1, NULL, &len); } -void -fixup_tags_mfi(struct media_file_info *mfi) +static void +fixup_sanitize(char **tag, enum fixup_type fixup, struct fixup_ctx *ctx) +{ + char *ret; + + if (!tag || !*tag) + return; + + switch (fixup) + { + case DB_FIXUP_NO_SANITIZE: + case DB_FIXUP_CODECTYPE: + break; // Don't touch the above + + default: + trim(*tag); + + // By default we set empty strings to NULL + if (*tag[0] == '\0') + { + free(*tag); + *tag = NULL; + break; + } + + ret = unicode_fixup_string(*tag, "ascii"); + if (ret != *tag) + { + free(*tag); + *tag = ret; + } + } + +} + +static void +fixup_defaults(char **tag, enum fixup_type fixup, struct fixup_ctx *ctx) { - cfg_t *lib; - size_t len; - char *tag; - char *sep = " - "; char *ca; - if (mfi->genre && (strlen(mfi->genre) == 0)) + switch(fixup) { - free(mfi->genre); - mfi->genre = NULL; - } + case DB_FIXUP_TITLE: + if (*tag) + break; - if (mfi->artist && (strlen(mfi->artist) == 0)) - { - free(mfi->artist); - mfi->artist = NULL; - } + // fname is left untouched by fixup_sanitize() for obvious reasons, so ensure it is proper UTF-8 + if (ctx->mfi && ctx->mfi->fname) + { + *tag = unicode_fixup_string(ctx->mfi->fname, "ascii"); + if (*tag == ctx->mfi->fname) + *tag = strdup(ctx->mfi->fname); + } + else if (ctx->pli && ctx->pli->path) + *tag = strdup(ctx->pli->path); + else if (ctx->queue_item && ctx->queue_item->path) + *tag = strdup(ctx->queue_item->path); + else + *tag = strdup("Unknown title"); + break; - if (mfi->title && (strlen(mfi->title) == 0)) - { - free(mfi->title); - mfi->title = NULL; - } + case DB_FIXUP_ARTIST: + if (*tag) + break; - /* - * Default to mpeg4 video/audio for unknown file types - * in an attempt to allow streaming of DRM-afflicted files - */ - if (mfi->codectype && strcmp(mfi->codectype, "unkn") == 0) + if (ctx->mfi && ctx->mfi->album_artist) + *tag = strdup(ctx->mfi->album_artist); + else if (ctx->mfi && ctx->mfi->orchestra && ctx->mfi->conductor) + *tag = safe_asprintf("%s - %s", ctx->mfi->orchestra, ctx->mfi->conductor); + else if (ctx->mfi && ctx->mfi->orchestra) + *tag = strdup(ctx->mfi->orchestra); + else if (ctx->mfi && ctx->mfi->conductor) + *tag = strdup(ctx->mfi->conductor); + else if (ctx->mfi && ctx->mfi->tv_series_name) + *tag = strdup(ctx->mfi->tv_series_name); + else + *tag = strdup("Unknown artist"); + break; + + case DB_FIXUP_ALBUM: + if (*tag) + break; + + if (ctx->mfi && ctx->mfi->tv_series_name) + *tag = safe_asprintf("%s, Season %u", ctx->mfi->tv_series_name, ctx->mfi->tv_season_num); + else + *tag = strdup("Unknown album"); + break; + + case DB_FIXUP_ALBUM_ARTIST: // Will be set after artist, because artist (must) come first in the col_maps + if (ctx->mfi && ctx->mfi->media_kind == MEDIA_KIND_PODCAST) + { + free(*tag); + *tag = strdup(""); + } + + if (*tag) + break; + + if (ctx->mfi && ctx->mfi->compilation && (ca = cfg_getstr(cfg_getsec(cfg, "library"), "compilation_artist"))) + *tag = strdup(ca); // If ca is empty string then the artist will not be shown in artist view + else if (ctx->mfi && ctx->mfi->artist) + *tag = strdup(ctx->mfi->artist); + else if (ctx->queue_item && ctx->queue_item->artist) + *tag = strdup(ctx->queue_item->artist); + else + *tag = strdup("Unknown artist"); + break; + + case DB_FIXUP_GENRE: + if (*tag) + break; + + *tag = strdup("Unknown genre"); + break; + + case DB_FIXUP_MEDIA_KIND: + if (ctx->mfi && ctx->mfi->tv_series_name) + ctx->mfi->media_kind = MEDIA_KIND_TVSHOW; + else if (ctx->mfi && !ctx->mfi->media_kind) + ctx->mfi->media_kind = MEDIA_KIND_MUSIC; + else if (ctx->queue_item && !ctx->queue_item->media_kind) + ctx->queue_item->media_kind = MEDIA_KIND_MUSIC; + + break; + + case DB_FIXUP_ITEM_KIND: + if (ctx->mfi && !ctx->mfi->item_kind) + ctx->mfi->item_kind = 2; // music + + break; + + case DB_FIXUP_TIME_ADDED: + if (ctx->mfi && ctx->mfi->time_added == 0) + ctx->mfi->time_added = ctx->mfi->db_timestamp; + break; + + case DB_FIXUP_TIME_MODIFIED: + if (ctx->mfi && ctx->mfi->time_modified == 0) + ctx->mfi->time_modified = ctx->mfi->db_timestamp; + break; + + case DB_FIXUP_CODECTYPE: + case DB_FIXUP_TYPE: + // Default to mpeg4 video/audio for unknown file types in an attempt to allow streaming of DRM-afflicted files + if (ctx->mfi && ctx->mfi->codectype && strcmp(ctx->mfi->codectype, "unkn") == 0) + { + if (ctx->mfi->has_video) + { + strcpy(ctx->mfi->codectype, "mp4v"); + strcpy(ctx->mfi->type, "m4v"); + } + else + { + strcpy(ctx->mfi->codectype, "mp4a"); + strcpy(ctx->mfi->type, "m4a"); + } + } + break; + + default: + break; + } +} + +static void +fixup_sort_tags(char **tag, enum fixup_type fixup, struct fixup_ctx *ctx) +{ + switch(fixup) { - if (mfi->has_video) + case DB_FIXUP_TITLE_SORT: + if (ctx->mfi) + sort_tag_create(tag, ctx->mfi->title); + break; + + case DB_FIXUP_ARTIST_SORT: + if (ctx->mfi) + sort_tag_create(tag, ctx->mfi->artist); + else if (ctx->queue_item) + sort_tag_create(tag, ctx->queue_item->artist); + break; + + case DB_FIXUP_ALBUM_SORT: + if (ctx->mfi) + sort_tag_create(tag, ctx->mfi->album); + else if (ctx->queue_item) + sort_tag_create(tag, ctx->queue_item->album); + break; + + case DB_FIXUP_ALBUM_ARTIST_SORT: + if (ctx->mfi) + sort_tag_create(tag, ctx->mfi->album_artist); + else if (ctx->queue_item) + sort_tag_create(tag, ctx->queue_item->album_artist); + break; + + case DB_FIXUP_COMPOSER_SORT: + if (ctx->mfi) + sort_tag_create(tag, ctx->mfi->composer); + break; + + default: + break; + } +} + +static void +fixup_tags(struct fixup_ctx *ctx) +{ + void (*fixup_func[])(char **, enum fixup_type, struct fixup_ctx *) = { fixup_sanitize, fixup_defaults, fixup_sort_tags }; + char **tag; + int i; + int j; + + for (i = 0; i < ARRAY_SIZE(fixup_func); i++) + { + for (j = 0; j < ctx->map_size; j++) { - strcpy(mfi->codectype, "mp4v"); - strcpy(mfi->type, "m4v"); - } - else - { - strcpy(mfi->codectype, "mp4a"); - strcpy(mfi->type, "m4a"); - } - } - - if (!mfi->artist) - { - if (mfi->orchestra && mfi->conductor) - { - len = strlen(mfi->orchestra) + strlen(sep) + strlen(mfi->conductor); - tag = (char *)malloc(len + 1); - if (tag) + switch (ctx->map[j].type) { - sprintf(tag,"%s%s%s", mfi->orchestra, sep, mfi->conductor); - mfi->artist = tag; - } - } - else if (mfi->orchestra) - { - mfi->artist = strdup(mfi->orchestra); - } - else if (mfi->conductor) - { - mfi->artist = strdup(mfi->conductor); - } - } + case DB_TYPE_STRING: + tag = (char **) ((char *)ctx->data + ctx->map[j].offset); + fixup_func[i](tag, ctx->map[j].fixup, ctx); + break; - /* Handle TV shows, try to present prettier metadata */ - if (mfi->tv_series_name && strlen(mfi->tv_series_name) != 0) - { - mfi->media_kind = MEDIA_KIND_TVSHOW; /* tv show */ - - /* Default to artist = series_name */ - if (mfi->artist && strlen(mfi->artist) == 0) - { - free(mfi->artist); - mfi->artist = NULL; - } - - if (!mfi->artist) - mfi->artist = strdup(mfi->tv_series_name); - - /* Default to album = ", Season " */ - if (mfi->album && strlen(mfi->album) == 0) - { - free(mfi->album); - mfi->album = NULL; - } - - if (!mfi->album) - { - len = snprintf(NULL, 0, "%s, Season %u", mfi->tv_series_name, mfi->tv_season_num); - - mfi->album = (char *)malloc(len + 1); - if (mfi->album) - sprintf(mfi->album, "%s, Season %u", mfi->tv_series_name, mfi->tv_season_num); + case DB_TYPE_CHAR: + case DB_TYPE_INT: + case DB_TYPE_INT64: + fixup_func[i](NULL, ctx->map[j].fixup, ctx); + break; + } } } +} - /* Check the 4 top-tags are filled */ - if (!mfi->artist) - mfi->artist = strdup("Unknown artist"); - if (!mfi->album) - mfi->album = strdup("Unknown album"); - if (!mfi->genre) - mfi->genre = strdup("Unknown genre"); - if (!mfi->title) - { - /* fname is left untouched by unicode_fixup_mfi() for - * obvious reasons, so ensure it is proper UTF-8 - */ - mfi->title = unicode_fixup_string(mfi->fname, "ascii"); - if (mfi->title == mfi->fname) - mfi->title = strdup(mfi->fname); - } +static void +fixup_tags_mfi(struct media_file_info *mfi) +{ + struct fixup_ctx ctx = { 0 }; + ctx.data = mfi; + ctx.mfi = mfi; + ctx.map = mfi_cols_map; + ctx.map_size = ARRAY_SIZE(mfi_cols_map); - /* Ensure sort tags are filled, manipulated and normalized */ - sort_tag_create(&mfi->artist_sort, mfi->artist); - sort_tag_create(&mfi->album_sort, mfi->album); - sort_tag_create(&mfi->title_sort, mfi->title); + fixup_tags(&ctx); +} - /* We need to set album_artist according to media type and config */ - if (mfi->compilation) /* Compilation */ - { - lib = cfg_getsec(cfg, "library"); - ca = cfg_getstr(lib, "compilation_artist"); - if (ca && mfi->album_artist) - { - free(mfi->album_artist); - mfi->album_artist = strdup(ca); - } - else if (ca && !mfi->album_artist) - { - mfi->album_artist = strdup(ca); - } - else if (!ca && !mfi->album_artist) - { - mfi->album_artist = strdup(""); - mfi->album_artist_sort = strdup(""); - } - } - else if (mfi->media_kind == MEDIA_KIND_PODCAST) /* Podcast */ - { - if (mfi->album_artist) - free(mfi->album_artist); - mfi->album_artist = strdup(""); - mfi->album_artist_sort = strdup(""); - } - else if (!mfi->album_artist) /* Regular media without album_artist */ - { - mfi->album_artist = strdup(mfi->artist); - } +static void +fixup_tags_pli(struct playlist_info *pli) +{ + struct fixup_ctx ctx = { 0 }; + ctx.data = pli; + ctx.pli = pli; + ctx.map = pli_cols_map; + ctx.map_size = ARRAY_SIZE(pli_cols_map); - if (!mfi->album_artist_sort && (strcmp(mfi->album_artist, mfi->artist) == 0)) - mfi->album_artist_sort = strdup(mfi->artist_sort); - else - sort_tag_create(&mfi->album_artist_sort, mfi->album_artist); - - /* Composer is not one of our mandatory tags, so take extra care */ - if (mfi->composer_sort || mfi->composer) - sort_tag_create(&mfi->composer_sort, mfi->composer); + fixup_tags(&ctx); } static void fixup_tags_queue_item(struct db_queue_item *queue_item) { - if (queue_item->genre && (strlen(queue_item->genre) == 0)) + struct fixup_ctx ctx = { 0 }; + ctx.data = queue_item; + ctx.queue_item = queue_item; + ctx.map = qi_cols_map; + ctx.map_size = ARRAY_SIZE(qi_cols_map); + + fixup_tags(&ctx); +} + +static int +bind_mfi(sqlite3_stmt *stmt, struct media_file_info *mfi) +{ + char **strptr; + char *ptr; + int i; + int n; + + for (i = 0, n = 1; i < ARRAY_SIZE(mfi_cols_map); i++) { - free(queue_item->genre); - queue_item->genre = NULL; + if (mfi_cols_map[i].flag & DB_FLAG_AUTO) + continue; + + ptr = (char *)mfi + mfi_cols_map[i].offset; + strptr = (char **)((char *)mfi + mfi_cols_map[i].offset); + + switch (mfi_cols_map[i].type) + { + case DB_TYPE_CHAR: + case DB_TYPE_INT: + sqlite3_bind_int(stmt, n, *((uint32_t *)ptr)); + break; + + case DB_TYPE_INT64: + sqlite3_bind_int64(stmt, n, *((uint64_t *)ptr)); + break; + + case DB_TYPE_STRING: + sqlite3_bind_text(stmt, n, *strptr, -1, SQLITE_STATIC); // TODO should we use _TRANSIENT? + break; + + default: + DPRINTF(E_LOG, L_DB, "BUG: Unknown type %d in mfi column map\n", mfi_cols_map[i].type); + return -1; + } + + n++; } - if (queue_item->artist && (strlen(queue_item->artist) == 0)) - { - free(queue_item->artist); - queue_item->artist = NULL; - } + // This binds the final "WHERE id = ?" if it is an update + if (mfi->id) + sqlite3_bind_int(stmt, n, mfi->id); - if (queue_item->title && (strlen(queue_item->title) == 0)) - { - free(queue_item->title); - queue_item->title = NULL; - } - - /* Check the 4 top-tags are filled */ - if (!queue_item->artist) - queue_item->artist = strdup("Unknown artist"); - if (!queue_item->album) - queue_item->album = strdup("Unknown album"); - if (!queue_item->genre) - queue_item->genre = strdup("Unknown genre"); - if (!queue_item->title) - queue_item->title = strdup(queue_item->path); - - /* Ensure sort tags are filled, manipulated and normalized */ - sort_tag_create(&queue_item->artist_sort, queue_item->artist); - sort_tag_create(&queue_item->album_sort, queue_item->album); - - /* We need to set album_artist according to media type and config */ - if (queue_item->media_kind == MEDIA_KIND_PODCAST) /* Podcast */ - { - if (queue_item->album_artist) - free(queue_item->album_artist); - queue_item->album_artist = strdup(""); - queue_item->album_artist_sort = strdup(""); - } - else if (!queue_item->album_artist) /* Regular media without album_artist */ - { - queue_item->album_artist = strdup(queue_item->artist); - } - - if (!queue_item->album_artist_sort && queue_item->artist_sort && (strcmp(queue_item->album_artist, queue_item->artist) == 0)) - queue_item->album_artist_sort = strdup(queue_item->artist_sort); - else - sort_tag_create(&queue_item->album_artist_sort, queue_item->album_artist); + return 0; } @@ -1065,6 +1215,37 @@ db_blocking_prepare_v2(const char *query, int len, sqlite3_stmt **stmt, const ch return ret; } +static int +db_statement_run(sqlite3_stmt *stmt) +{ + int ret; + +#ifdef HAVE_SQLITE3_EXPANDED_SQL + char *query; + if (logger_severity() >= E_DBG) + { + query = sqlite3_expanded_sql(stmt); + DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); + sqlite3_free(query); + } +#else + DPRINTF(E_DBG, L_DB, "Running query (prepared statement)\n"); +#endif + + while ((ret = db_blocking_step(stmt)) == SQLITE_ROW) + ; /* EMPTY */ + + if (ret != SQLITE_DONE) + { + DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return (ret == SQLITE_DONE) ? sqlite3_changes(hdl) : -1; +} + /* Modelled after sqlite3_exec() */ static int @@ -1499,7 +1680,10 @@ db_build_query_check(struct query_params *qp, char *count, char *query) qp->results = db_get_one_int(count); if (qp->results < 0) - goto failed; + { + DPRINTF(E_LOG, L_DB, "No results for count\n"); + goto failed; + } sqlite3_free(count); @@ -2415,16 +2599,11 @@ db_file_ping(int id) int db_file_ping_bypath(const char *path, time_t mtime_max) { -#define Q_TMPL "UPDATE files SET db_timestamp = %" PRIi64 ", disabled = 0 WHERE path = '%q' AND db_timestamp >= %" PRIi64 ";" - char *query; - int ret; + sqlite3_bind_int64(db_statements.files_ping, 1, (int64_t)time(NULL)); + sqlite3_bind_text(db_statements.files_ping, 2, path, -1, SQLITE_STATIC); + sqlite3_bind_int64(db_statements.files_ping, 3, (int64_t)mtime_max); - query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), path, (int64_t)mtime_max); - - ret = db_query_run(query, 1, 0); - - return ((ret < 0) ? -1 : sqlite3_changes(hdl)); -#undef Q_TMPL + return db_statement_run(db_statements.files_ping); } void @@ -2808,41 +2987,8 @@ db_file_fetch_byvirtualpath(const char *virtual_path) int db_file_add(struct media_file_info *mfi) { -#define Q_TMPL "INSERT INTO files (" \ - " 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, artwork," \ - " rating, play_count, seek, data_kind, item_kind, description," \ - " time_added, time_modified, time_played, db_timestamp," \ - " disabled, sample_count, codectype, idx, has_video," \ - " contentrating, bits_per_sample, album_artist, media_kind," \ - " tv_series_name, tv_episode_num_str, tv_network_name," \ - " tv_episode_sort, tv_season_num, songartistid, songalbumid," \ - " title_sort, artist_sort, album_sort, composer_sort," \ - " album_artist_sort, virtual_path, directory_id, date_released,"\ - " skip_count, time_skipped" \ - ") VALUES (" \ - " NULL, '%q', '%q', TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), %Q," \ - " TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), %Q, %d," \ - " %d, %d, %" PRIi64 ", %d, %d," \ - " %d, %d, %d, %d, %d, %d," \ - " %d, %d, %d, %d, %d, %Q," \ - " %" PRIi64 ", %" PRIi64 ", %" PRIi64 ", %" PRIi64 "," \ - " %d, %" PRIi64 ", %Q, %d, %d," \ - " %d, %d, TRIM(%Q), %d," \ - " TRIM(%Q), TRIM(%Q), TRIM(%Q)," \ - " %d, %d, daap_songalbumid(LOWER(TRIM(%Q)), ''), daap_songalbumid(LOWER(TRIM(%Q)), LOWER(TRIM(%Q)))," \ - " TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q)," \ - " TRIM(%Q), TRIM(%Q), %d, %d," \ - " %d, %" PRIi64 "" \ - ");" - - char *query; - char *errmsg; int ret; - if (mfi->id != 0) { DPRINTF(E_WARN, L_DB, "Trying to add file with non-zero id; use db_file_update()?\n"); @@ -2851,75 +2997,24 @@ db_file_add(struct media_file_info *mfi) mfi->db_timestamp = (uint64_t)time(NULL); - if (mfi->time_added == 0) - mfi->time_added = mfi->db_timestamp; + fixup_tags_mfi(mfi); - if (mfi->time_modified == 0) - mfi->time_modified = mfi->db_timestamp; + ret = bind_mfi(db_statements.files_insert, mfi); + if (ret < 0) + return -1; - 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->artwork, - mfi->rating, mfi->play_count, mfi->seek, 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->codectype, mfi->index, mfi->has_video, - mfi->contentrating, mfi->bits_per_sample, mfi->album_artist, mfi->media_kind, - mfi->tv_series_name, mfi->tv_episode_num_str, mfi->tv_network_name, - mfi->tv_episode_sort, mfi->tv_season_num, mfi->album_artist, mfi->album_artist, mfi->album, - mfi->title_sort, mfi->artist_sort, mfi->album_sort, mfi->composer_sort, - mfi->album_artist_sort, mfi->virtual_path, mfi->directory_id, mfi->date_released, - mfi->skip_count, (int64_t)mfi->time_skipped); - - 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 = db_exec(query, &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); + ret = db_statement_run(db_statements.files_insert); + if (ret < 0) + return -1; library_update_trigger(LISTENER_DATABASE); return 0; - -#undef Q_TMPL } int db_file_update(struct media_file_info *mfi) { -#define Q_TMPL "UPDATE files SET path = '%q', fname = '%q', title = TRIM(%Q), artist = TRIM(%Q), album = TRIM(%Q), genre = TRIM(%Q)," \ - " comment = TRIM(%Q), type = %Q, composer = TRIM(%Q), orchestra = TRIM(%Q), conductor = TRIM(%Q), grouping = TRIM(%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, artwork = %d, rating = %d, seek = %d, data_kind = %d, item_kind = %d," \ - " description = %Q, time_modified = %" PRIi64 "," \ - " db_timestamp = %" PRIi64 ", disabled = %" PRIi64 ", sample_count = %" PRIi64 "," \ - " codectype = %Q, idx = %d, has_video = %d," \ - " bits_per_sample = %d, album_artist = TRIM(%Q)," \ - " media_kind = %d, tv_series_name = TRIM(%Q), tv_episode_num_str = TRIM(%Q)," \ - " tv_network_name = TRIM(%Q), tv_episode_sort = %d, tv_season_num = %d," \ - " songartistid = daap_songalbumid(LOWER(TRIM(%Q)), ''), songalbumid = daap_songalbumid(LOWER(TRIM(%Q)), LOWER(TRIM(%Q)))," \ - " title_sort = TRIM(%Q), artist_sort = TRIM(%Q), album_sort = TRIM(%Q), composer_sort = TRIM(%Q), album_artist_sort = TRIM(%Q)," \ - " virtual_path = TRIM(%Q), directory_id = %d, date_released = %d, skip_count = %d, time_skipped = %" PRIi64 "" \ - " WHERE id = %d;" - - char *query; - char *errmsg; int ret; if (mfi->id == 0) @@ -2930,52 +3025,19 @@ db_file_update(struct media_file_info *mfi) mfi->db_timestamp = (uint64_t)time(NULL); - if (mfi->time_modified == 0) - mfi->time_modified = mfi->db_timestamp; + fixup_tags_mfi(mfi); - 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->artwork, mfi->rating, mfi->seek, mfi->data_kind, mfi->item_kind, - mfi->description, (int64_t)mfi->time_modified, - (int64_t)mfi->db_timestamp, (int64_t)mfi->disabled, mfi->sample_count, - mfi->codectype, mfi->index, mfi->has_video, - mfi->bits_per_sample, mfi->album_artist, - mfi->media_kind, mfi->tv_series_name, mfi->tv_episode_num_str, - mfi->tv_network_name, mfi->tv_episode_sort, mfi->tv_season_num, - mfi->album_artist, mfi->album_artist, mfi->album, - mfi->title_sort, mfi->artist_sort, mfi->album_sort, - mfi->composer_sort, mfi->album_artist_sort, - mfi->virtual_path, mfi->directory_id, mfi->date_released, mfi->skip_count, (int64_t)mfi->time_skipped, - mfi->id); + ret = bind_mfi(db_statements.files_update, mfi); + if (ret < 0) + return -1; - 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 = db_exec(query, &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); + ret = db_statement_run(db_statements.files_update); + if (ret < 0) + return -1; library_update_trigger(LISTENER_DATABASE); return 0; - -#undef Q_TMPL } void @@ -3486,6 +3548,8 @@ db_pl_add(struct playlist_info *pli, int *id) char *errmsg; int ret; + fixup_tags_pli(pli); + /* Check duplicates */ query = sqlite3_mprintf(QDUP_TMPL, pli->title, STR(pli->path)); if (!query) @@ -3579,6 +3643,8 @@ db_pl_update(struct playlist_info *pli) char *query; int ret; + fixup_tags_pli(pli); + query = sqlite3_mprintf(Q_TMPL, pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path), pli->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->directory_id, @@ -6692,10 +6758,8 @@ db_pragma_set_mmap_size(int mmap_size) #undef Q_TMPL } - - -int -db_perthread_init(void) +static int +db_open(void) { char *errmsg; int ret; @@ -6781,6 +6845,108 @@ db_perthread_init(void) mmap_size = db_pragma_get_mmap_size(); DPRINTF(E_DBG, L_DB, "Database mmap_size: %d\n", mmap_size); } + + return 0; +} + +static int +db_statements_prepare(void) +{ + char *query; + char keystr[2048]; + char valstr[1024]; + int ret; + int i; + + // Prepare "INSERT INTO files" statement + memset(keystr, 0, sizeof(keystr)); + memset(valstr, 0, sizeof(valstr)); + for (i = 0; i < ARRAY_SIZE(mfi_cols_map); i++) + { + if (mfi_cols_map[i].flag & DB_FLAG_AUTO) + continue; + + CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s, ", mfi_cols_map[i].name)); + CHECK_ERR(L_DB, safe_snprintf_cat(valstr, sizeof(valstr), "?, ")); + } + + // Terminate at the ending ", " + *(strrchr(keystr, ',')) = '\0'; + *(strrchr(valstr, ',')) = '\0'; + + CHECK_NULL(L_DB, query = db_mprintf("INSERT INTO files (%s) VALUES (%s);", keystr, valstr)); + + ret = db_blocking_prepare_v2(query, -1, &db_statements.files_insert, NULL); + if (ret != SQLITE_OK) + { + DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl)); + free(query); + return -1; + } + + free(query); + + // Prepare "UPDATE files" statement + memset(keystr, 0, sizeof(keystr)); + for (i = 0; i < ARRAY_SIZE(mfi_cols_map); i++) + { + if (mfi_cols_map[i].flag & DB_FLAG_AUTO) + continue; + + if (mfi_cols_map[i].flag & DB_FLAG_NO_ZERO) + CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s = daap_no_zero(?, %s), ", mfi_cols_map[i].name, mfi_cols_map[i].name)); + else + CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s = ?, ", mfi_cols_map[i].name)); + } + + // Terminate at the ending ", " + *(strrchr(keystr, ',')) = '\0'; + + CHECK_NULL(L_DB, query = db_mprintf("UPDATE files SET %s WHERE %s = ?;", keystr, mfi_cols_map[0].name)); + + ret = db_blocking_prepare_v2(query, -1, &db_statements.files_update, NULL); + if (ret != SQLITE_OK) + { + DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl)); + free(query); + return -1; + } + free(query); + + // Prepare "UPDATE files SET db_timestamp" statement + CHECK_NULL(L_DB, query = db_mprintf("UPDATE files SET db_timestamp = ?, disabled = 0 WHERE path = ? AND db_timestamp >= ?;")); + + ret = db_blocking_prepare_v2(query, -1, &db_statements.files_ping, NULL); + if (ret != SQLITE_OK) + { + DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl)); + free(query); + return -1; + } + + free(query); + + return 0; +} + +int +db_perthread_init(void) +{ + int ret; + + ret = db_open(); + if (ret < 0) + return -1; + + ret = db_statements_prepare(); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Could not prepare statements\n"); + + sqlite3_close(hdl); + return -1; + } + return 0; } @@ -6800,8 +6966,6 @@ db_perthread_deinit(void) } - - static int db_check_version(void) { @@ -6826,7 +6990,7 @@ db_check_version(void) db_ver = db_ver_major * 100 + db_ver_minor; - if (db_ver_major < 10) + if (db_ver_major < 17) { DPRINTF(E_FATAL, L_DB, "Database schema v%d too old, cannot upgrade\n", db_ver_major); @@ -6852,6 +7016,7 @@ db_check_version(void) return -1; } + // Will drop indices and triggers ret = db_upgrade(hdl, db_ver); if (ret < 0) { @@ -6882,6 +7047,21 @@ db_check_version(void) return -1; } + ret = db_init_triggers(hdl); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Database upgrade errored out, rolling back changes ...\n"); + ret = sqlite3_exec(hdl, "ROLLBACK TRANSACTION;", NULL, NULL, &errmsg); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "DB error while running 'ROLLBACK TRANSACTION': %s\n", errmsg); + + sqlite3_free(errmsg); + } + + return -1; + } + ret = sqlite3_exec(hdl, "COMMIT TRANSACTION;", NULL, NULL, &errmsg); if (ret != SQLITE_OK) { @@ -6965,9 +7145,12 @@ db_init(void) return -1; } - ret = db_perthread_init(); + ret = db_open(); if (ret < 0) - return ret; + { + DPRINTF(E_FATAL, L_DB, "Could not open database\n"); + return -1; + } ret = db_check_version(); if (ret < 0) diff --git a/src/db.h b/src/db.h index 1ed9dc92..afaa938b 100644 --- a/src/db.h +++ b/src/db.h @@ -140,12 +140,16 @@ db_data_kind_label(enum data_kind data_kind); /* Note that fields marked as integers in the metadata map in filescanner_ffmpeg must be uint32_t here */ struct media_file_info { + uint32_t id; + char *path; - uint32_t index; + char *virtual_path; char *fname; + uint32_t directory_id; /* Id of directory */ char *title; char *artist; char *album; + char *album_artist; char *genre; char *comment; char *type; /* daap.songformat */ @@ -160,6 +164,7 @@ struct media_file_info { uint32_t song_length; int64_t file_size; uint32_t year; /* TDRC */ + uint32_t date_released; uint32_t track; /* TRCK */ uint32_t total_tracks; @@ -167,44 +172,44 @@ struct media_file_info { uint32_t disc; /* TPOS */ uint32_t total_discs; + uint32_t bpm; /* TBPM */ + uint32_t compilation; + char artwork; + uint32_t rating; + + uint32_t play_count; + uint32_t skip_count; + uint32_t seek; + + uint32_t data_kind; /* dmap.datakind (asdk) */ + uint32_t media_kind; + uint32_t item_kind; /* song or movie */ + + char *description; /* daap.songdescription */ + + uint32_t db_timestamp; uint32_t time_added; /* FIXME: time_t */ uint32_t time_modified; uint32_t time_played; - - uint32_t play_count; - uint32_t seek; - uint32_t rating; - uint32_t db_timestamp; + uint32_t time_skipped; uint32_t disabled; - uint32_t bpm; /* TBPM */ - uint32_t id; - - char *description; /* daap.songdescription */ + uint64_t sample_count; //TODO [unused] sample count is never set and therefor always 0 char *codectype; /* song.codectype, 4 chars max (32 bits) */ - uint32_t item_kind; /* song or movie */ - uint32_t data_kind; /* dmap.datakind (asdk) */ - uint64_t sample_count; //TODO [unused] sample count is never set and therefor always 0 - uint32_t compilation; - char artwork; + uint32_t idx; - /* iTunes 5+ */ - uint32_t contentrating; + uint32_t has_video; /* iTunes 6.0.2 */ + uint32_t contentrating;/* iTunes 5+ */ - /* iTunes 6.0.2 */ - uint32_t has_video; uint32_t bits_per_sample; - uint32_t media_kind; - uint32_t tv_episode_sort; - uint32_t tv_season_num; char *tv_series_name; char *tv_episode_num_str; /* com.apple.itunes.episode-num-str, used as a unique episode identifier */ char *tv_network_name; - - char *album_artist; + uint32_t tv_episode_sort; + uint32_t tv_season_num; int64_t songartistid; int64_t songalbumid; @@ -212,16 +217,8 @@ struct media_file_info { char *title_sort; char *artist_sort; char *album_sort; - char *composer_sort; char *album_artist_sort; - - char *virtual_path; - - uint32_t directory_id; /* Id of directory */ - uint32_t date_released; - - uint32_t skip_count; - uint32_t time_skipped; + char *composer_sort; }; #define mfi_offsetof(field) offsetof(struct media_file_info, field) @@ -308,10 +305,13 @@ struct db_group_info { struct db_media_file_info { char *id; char *path; + char *virtual_path; char *fname; + char *directory_id; char *title; char *artist; char *album; + char *album_artist; char *genre; char *comment; char *type; @@ -325,6 +325,7 @@ struct db_media_file_info { char *song_length; char *file_size; char *year; + char *date_released; char *track; char *total_tracks; char *disc; @@ -334,14 +335,17 @@ struct db_media_file_info { char *artwork; char *rating; char *play_count; + char *skip_count; char *seek; char *data_kind; + char *media_kind; char *item_kind; char *description; + char *db_timestamp; char *time_added; char *time_modified; char *time_played; - char *db_timestamp; + char *time_skipped; char *disabled; char *sample_count; char *codectype; @@ -349,8 +353,6 @@ struct db_media_file_info { char *has_video; char *contentrating; char *bits_per_sample; - char *album_artist; - char *media_kind; char *tv_episode_sort; char *tv_season_num; char *tv_series_name; @@ -361,13 +363,8 @@ struct db_media_file_info { char *title_sort; char *artist_sort; char *album_sort; - char *composer_sort; char *album_artist_sort; - char *virtual_path; - char *directory_id; - char *date_released; - char *skip_count; - char *time_skipped; + char *composer_sort; }; #define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field) @@ -425,8 +422,7 @@ struct directory_enum { void *stmt; }; -struct db_queue_item -{ +struct db_queue_item { /* A unique id for this queue item. If the same item appears multiple times in the queue each corresponding queue item has its own id. */ uint32_t id; @@ -434,18 +430,16 @@ struct db_queue_item /* Id of the file/item in the files database */ uint32_t file_id; - /* Length of the item in ms */ - uint32_t song_length; + uint32_t pos; + uint32_t shuffle_pos; /* Data type of the item */ enum data_kind data_kind; /* Media type of the item */ enum media_kind media_kind; - uint32_t seek; - - uint32_t pos; - uint32_t shuffle_pos; + /* Length of the item in ms */ + uint32_t song_length; char *path; char *virtual_path; @@ -453,7 +447,6 @@ struct db_queue_item char *title; char *artist; char *album_artist; - char *composer; char *album; char *genre; @@ -471,8 +464,15 @@ struct db_queue_item char *artwork_url; uint32_t queue_version; + + char *composer; + + /* Not saved in queue table */ + uint32_t seek; }; +#define qi_offsetof(field) offsetof(struct db_queue_item, field) + struct db_queue_add_info { int queue_version; @@ -510,12 +510,6 @@ free_query_params(struct query_params *qp, int content_only); void free_queue_item(struct db_queue_item *queue_item, int content_only); -void -unicode_fixup_mfi(struct media_file_info *mfi); - -void -fixup_tags_mfi(struct media_file_info *mfi); - /* Maintenance and DB hygiene */ void db_hook_post_scan(void); diff --git a/src/db_init.c b/src/db_init.c index eba7116d..595d23ae 100644 --- a/src/db_init.c +++ b/src/db_init.c @@ -36,10 +36,13 @@ "CREATE TABLE IF NOT EXISTS files (" \ " id INTEGER PRIMARY KEY NOT NULL," \ " path VARCHAR(4096) NOT NULL," \ + " virtual_path VARCHAR(4096) DEFAULT NULL," \ " fname VARCHAR(255) NOT NULL," \ + " directory_id INTEGER DEFAULT 0," \ " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " album VARCHAR(1024) NOT NULL COLLATE DAAP," \ + " album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \ " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ " comment VARCHAR(4096) DEFAULT NULL COLLATE DAAP," \ " type VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ @@ -53,6 +56,7 @@ " song_length INTEGER DEFAULT 0," \ " file_size INTEGER DEFAULT 0," \ " year INTEGER DEFAULT 0," \ + " date_released INTEGER DEFAULT 0," \ " track INTEGER DEFAULT 0," \ " total_tracks INTEGER DEFAULT 0," \ " disc INTEGER DEFAULT 0," \ @@ -62,14 +66,17 @@ " artwork INTEGER DEFAULT 0," \ " rating INTEGER DEFAULT 0," \ " play_count INTEGER DEFAULT 0," \ + " skip_count INTEGER DEFAULT 0," \ " seek INTEGER DEFAULT 0," \ " data_kind INTEGER DEFAULT 0," \ + " media_kind INTEGER DEFAULT 0," \ " item_kind INTEGER DEFAULT 0," \ " description INTEGER DEFAULT 0," \ + " db_timestamp INTEGER DEFAULT 0," \ " time_added INTEGER DEFAULT 0," \ " time_modified INTEGER DEFAULT 0," \ " time_played INTEGER DEFAULT 0," \ - " db_timestamp INTEGER DEFAULT 0," \ + " time_skipped INTEGER DEFAULT 0," \ " disabled INTEGER DEFAULT 0," \ " sample_count INTEGER DEFAULT 0," \ " codectype VARCHAR(5) DEFAULT NULL," \ @@ -77,25 +84,18 @@ " has_video INTEGER DEFAULT 0," \ " contentrating INTEGER DEFAULT 0," \ " bits_per_sample INTEGER DEFAULT 0," \ - " album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \ - " media_kind INTEGER NOT NULL," \ " tv_series_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " tv_episode_num_str VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " tv_network_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " tv_episode_sort INTEGER NOT NULL," \ " tv_season_num INTEGER NOT NULL," \ - " songartistid INTEGER NOT NULL," \ - " songalbumid INTEGER NOT NULL," \ + " songartistid INTEGER DEFAULT 0," \ + " songalbumid INTEGER DEFAULT 0," \ " title_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " virtual_path VARCHAR(4096) DEFAULT NULL," \ - " directory_id INTEGER DEFAULT 0," \ - " date_released INTEGER DEFAULT 0," \ - " skip_count INTEGER DEFAULT 0," \ - " time_skipped INTEGER DEFAULT 0" \ + " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP" \ ");" #define T_PL \ @@ -194,20 +194,6 @@ " composer VARCHAR(1024) DEFAULT NULL" \ ");" -#define TRG_GROUPS_INSERT_FILES \ - "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \ - " BEGIN" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \ - " END;" - -#define TRG_GROUPS_UPDATE_FILES \ - "CREATE TRIGGER update_groups_update_file AFTER UPDATE OF songalbumid ON files FOR EACH ROW" \ - " BEGIN" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \ - " END;" - #define Q_PL1 \ "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \ " VALUES(1, 'Library', 0, '1 = 1', 0, '', 0, 0);" @@ -278,19 +264,18 @@ static const struct db_init_query db_init_table_queries[] = { T_DIRECTORIES, "create table directories" }, { T_QUEUE, "create table queue" }, - { TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" }, - { TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" }, - { Q_PL1, "create default playlist" }, { Q_PL2, "create default smart playlist 'Music'" }, { Q_PL3, "create default smart playlist 'Movies'" }, { Q_PL4, "create default smart playlist 'TV Shows'" }, { Q_PL5, "create default smart playlist 'Podcasts'" }, { Q_PL6, "create default smart playlist 'Audiobooks'" }, + { Q_DIR1, "create default root directory '/'" }, { Q_DIR2, "create default base directory '/file:'" }, { Q_DIR3, "create default base directory '/http:'" }, { Q_DIR4, "create default base directory '/spotify:'" }, + { Q_QUEUE_VERSION, "initialize queue version" }, }; @@ -411,6 +396,41 @@ static const struct db_init_query db_init_index_queries[] = { I_QUEUE_SHUFFLEPOS, "create queue shuffle pos index" }, }; + +/* Triggers must be prefixed with trg_ for db_drop_triggers() to id them */ + +#define TRG_FILES_INSERT_SONGIDS \ + "CREATE TRIGGER trg_files_insert_songids AFTER INSERT ON files FOR EACH ROW" \ + " BEGIN" \ + " UPDATE files SET songartistid = daap_songalbumid(LOWER(NEW.album_artist), ''), " \ + " songalbumid = daap_songalbumid(LOWER(NEW.album_artist), LOWER(NEW.album))" \ + " WHERE id = NEW.id;" \ + " END;" + +#define TRG_FILES_UPDATE_SONGIDS \ + "CREATE TRIGGER trg_files_update_songids AFTER UPDATE OF album_artist, album ON files FOR EACH ROW" \ + " BEGIN" \ + " UPDATE files SET songartistid = daap_songalbumid(LOWER(NEW.album_artist), ''), " \ + " songalbumid = daap_songalbumid(LOWER(NEW.album_artist), LOWER(NEW.album))" \ + " WHERE id = NEW.id;" \ + " END;" + +#define TRG_GROUPS_UPDATE \ + "CREATE TRIGGER trg_groups_update AFTER UPDATE OF songartistid, songalbumid ON files FOR EACH ROW" \ + " WHEN (NEW.songartistid != 0 AND NEW.songalbumid != 0)" \ + " BEGIN" \ + " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ + " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \ + " END;" + +static const struct db_init_query db_init_trigger_queries[] = + { + { TRG_FILES_INSERT_SONGIDS, "create trigger trg_files_insert_songids" }, + { TRG_FILES_UPDATE_SONGIDS, "create trigger trg_files_update_songids" }, + { TRG_GROUPS_UPDATE, "create trigger trg_groups_update" }, + }; + + int db_init_indices(sqlite3 *hdl) { @@ -435,6 +455,30 @@ db_init_indices(sqlite3 *hdl) return 0; } +int +db_init_triggers(sqlite3 *hdl) +{ + char *errmsg; + int i; + int ret; + + for (i = 0; i < (sizeof(db_init_trigger_queries) / sizeof(db_init_trigger_queries[0])); i++) + { + DPRINTF(E_DBG, L_DB, "DB init trigger query: %s\n", db_init_trigger_queries[i].desc); + + ret = sqlite3_exec(hdl, db_init_trigger_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; + } + } + + return 0; +} + int db_init_tables(sqlite3 *hdl) { diff --git a/src/db_init.h b/src/db_init.h index ec3f0217..7cb62afa 100644 --- a/src/db_init.h +++ b/src/db_init.h @@ -25,12 +25,15 @@ * version of the database? If yes, then it is a minor upgrade, if no, then it * is a major upgrade. In other words minor version upgrades permit downgrading * forked-daapd after the database was upgraded. */ -#define SCHEMA_VERSION_MAJOR 19 -#define SCHEMA_VERSION_MINOR 12 +#define SCHEMA_VERSION_MAJOR 20 +#define SCHEMA_VERSION_MINOR 00 int db_init_indices(sqlite3 *hdl); +int +db_init_triggers(sqlite3 *hdl); + int db_init_tables(sqlite3 *hdl); diff --git a/src/db_upgrade.c b/src/db_upgrade.c index e623b5f6..c8fa77e0 100644 --- a/src/db_upgrade.c +++ b/src/db_upgrade.c @@ -108,6 +108,69 @@ db_drop_indices(sqlite3 *hdl) #undef Q_INDEX } +static int +db_drop_triggers(sqlite3 *hdl) +{ +#define Q_TRIGGER "SELECT name FROM sqlite_master WHERE type == 'trigger' AND name LIKE 'trg_%';" +#define Q_TMPL "DROP TRIGGER %q;" + sqlite3_stmt *stmt; + char *errmsg; + char *query; + char *trigger[256]; + int ret; + int i; + int n; + + DPRINTF(E_DBG, L_DB, "Running query '%s'\n", Q_TRIGGER); + + ret = sqlite3_prepare_v2(hdl, Q_TRIGGER, strlen(Q_TRIGGER) + 1, &stmt, NULL); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); + return -1; + } + + n = 0; + while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) + { + trigger[n] = strdup((char *)sqlite3_column_text(stmt, 0)); + n++; + } + + if (ret != SQLITE_DONE) + { + DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); + + sqlite3_finalize(stmt); + return -1; + } + + sqlite3_finalize(stmt); + + for (i = 0; i < n; i++) + { + query = sqlite3_mprintf(Q_TMPL, trigger[i]); + free(trigger[i]); + + DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); + + ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "DB error while running '%s': %s\n", query, errmsg); + + sqlite3_free(errmsg); + return -1; + } + + sqlite3_free(query); + } + + return 0; +#undef Q_TMPL +#undef Q_TRIGGER +} + static int db_generic_upgrade(sqlite3 *hdl, const struct db_upgrade_query *queries, unsigned int nqueries) @@ -133,982 +196,132 @@ db_generic_upgrade(sqlite3 *hdl, const struct db_upgrade_query *queries, unsigne return 0; } -/* Upgrade the files table to the new schema by dumping and reloading the - * table. A bit tedious. +/* The below implements relevant parts of SQLITE's recommended 12 steps to + * altering a table. It is not required to use this function if you just want to + * add a column). The steps: + * 1. If foreign key constraints are enabled, disable them using PRAGMA + * foreign_keys=OFF. + * 2. Start a transaction. + * 3. Remember the format of all indexes and triggers associated with table X. + * This information will be needed in step 8 below. One way to do this is to + * run a query like the following: SELECT type, sql FROM sqlite_master WHERE + * tbl_name='X'. + * 4. Use CREATE TABLE to construct a new table "new_X" that is in the desired + * revised format of table X. Make sure that the name "new_X" does not + * collide with any existing table name, of course. + * 5. Transfer content from X into new_X using a statement like: INSERT INTO + * new_X SELECT ... FROM X. + * 6. Drop the old table X: DROP TABLE X. + * 7. Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X. + * 8. Use CREATE INDEX and CREATE TRIGGER to reconstruct indexes and triggers + * associated with table X. Perhaps use the old format of the triggers and + * indexes saved from step 3 above as a guide, making changes as appropriate + * for the alteration. + * 9. If any views refer to table X in a way that is affected by the schema + * change, then drop those views using DROP VIEW and recreate them with + * whatever changes are necessary to accommodate the schema change using + * CREATE VIEW. + * 10. If foreign key constraints were originally enabled then run PRAGMA + * foreign_key_check to verify that the schema change did not break any + * foreign key constraints. + * 11. Commit the transaction started in step 2. + * 12. If foreign keys constraints were originally enabled, reenable them now. + * Source: https://www.sqlite.org/lang_altertable.html */ static int -db_upgrade_files_table(sqlite3 *hdl, const char *dumpquery, const char *newtablequery) +db_table_upgrade(sqlite3 *hdl, const char *name, const char *newtablequery) { - struct stat sb; - FILE *fp; - sqlite3_stmt *stmt; - const unsigned char *dumprow; - char *dump; - char *errmsg; - int fd; - int ret; - - DPRINTF(E_LOG, L_DB, "Upgrading files table...\n"); - - fp = tmpfile(); - if (!fp) - { - DPRINTF(E_LOG, L_DB, "Could not create temporary file for files table dump: %s\n", strerror(errno)); - return -1; - } - - DPRINTF(E_LOG, L_DB, "Dumping old files table...\n"); - - /* dump */ - ret = sqlite3_prepare_v2(hdl, dumpquery, strlen(dumpquery) + 1, &stmt, NULL); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); - - ret = -1; - goto out_fclose; - } - - while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) - { - dumprow = sqlite3_column_text(stmt, 0); - - ret = fprintf(fp, "%s\n", dumprow); - if (ret < 0) - { - DPRINTF(E_LOG, L_DB, "Could not write dump: %s\n", strerror(errno)); - - sqlite3_finalize(stmt); - - ret = -1; - goto out_fclose; - } - } - - if (ret != SQLITE_DONE) - { - DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); - - sqlite3_finalize(stmt); - - ret = -1; - goto out_fclose; - } - - sqlite3_finalize(stmt); - - /* Seek back to start of dump file */ - ret = fseek(fp, 0, SEEK_SET); - if (ret < 0) - { - DPRINTF(E_LOG, L_DB, "Could not seek back to start of dump: %s\n", strerror(errno)); - - ret = -1; - goto out_fclose; - } - - /* Map dump file */ - fd = fileno(fp); - if (fd < 0) - { - DPRINTF(E_LOG, L_DB, "Could not obtain file descriptor: %s\n", strerror(errno)); - - ret = -1; - goto out_fclose; - } - - ret = fstat(fd, &sb); - if (ret < 0) - { - DPRINTF(E_LOG, L_DB, "Could not stat dump file: %s\n", strerror(errno)); - - ret = -1; - goto out_fclose; - } - - if (sb.st_size == 0) - dump = NULL; - else - { - dump = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); - if (dump == MAP_FAILED) - { - DPRINTF(E_LOG, L_DB, "Could not map dump file: %s\n", strerror(errno)); - - ret = -1; - goto out_fclose; - } - } - - /* Drop remnants from last upgrade if still present */ - DPRINTF(E_LOG, L_DB, "Clearing old backups...\n"); - - ret = sqlite3_exec(hdl, "DROP TABLE IF EXISTS files_backup;", NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Error clearing old backup - will continue anyway: %s\n", errmsg); - - sqlite3_free(errmsg); - } - - /* Move old table out of the way */ - DPRINTF(E_LOG, L_DB, "Moving old files table out of the way...\n"); - - ret = sqlite3_exec(hdl, "ALTER TABLE files RENAME TO files_backup;", NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Error making backup of old files table: %s\n", errmsg); - - sqlite3_free(errmsg); - - ret = -1; - goto out_munmap; - } - - /* Create new table */ - DPRINTF(E_LOG, L_DB, "Creating new files table...\n"); - - ret = sqlite3_exec(hdl, newtablequery, NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Error creating new files table: %s\n", errmsg); - - sqlite3_free(errmsg); - - ret = -1; - goto out_munmap; - } - - /* Reload dump */ - DPRINTF(E_LOG, L_DB, "Reloading new files table...\n"); - - if (dump) - { - ret = sqlite3_exec(hdl, dump, NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Error reloading files table data: %s\n", errmsg); - - sqlite3_free(errmsg); - - ret = -1; - goto out_munmap; - } - } - - /* Delete old files table */ - DPRINTF(E_LOG, L_DB, "Deleting backup files table...\n"); - - ret = sqlite3_exec(hdl, "DROP TABLE files_backup;", NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Error dropping backup files table: %s\n", errmsg); - - sqlite3_free(errmsg); - /* Not an issue, but takes up space in the database */ - } - - DPRINTF(E_LOG, L_DB, "Upgrade of files table complete!\n"); - - out_munmap: - if (dump) - { - if (munmap(dump, sb.st_size) < 0) - DPRINTF(E_LOG, L_DB, "Could not unmap dump file: %s\n", strerror(errno)); - } - - out_fclose: - fclose(fp); - - return ret; -} - -/* Upgrade from schema v10 to v11 */ - -#define U_V11_SPEAKERS \ - "CREATE TABLE speakers(" \ - " id INTEGER PRIMARY KEY NOT NULL," \ - " selected INTEGER NOT NULL," \ - " volume INTEGER NOT NULL" \ - ");" - -#define U_V11_SCVER \ - "UPDATE admin SET value = '11' WHERE key = 'schema_version';" - -static const struct db_upgrade_query db_upgrade_v11_queries[] = - { - { U_V11_SPEAKERS, "create new table speakers" }, - { U_V11_SCVER, "set schema_version to 11" }, - }; - -static int -db_upgrade_v11(sqlite3 *hdl) -{ -#define Q_NEWSPK "INSERT INTO speakers (id, selected, volume) VALUES (%" PRIi64 ", 1, 75);" -#define Q_SPKVOL "UPDATE speakers SET volume = %d;" sqlite3_stmt *stmt; char *query; char *errmsg; - const char *strid; - uint64_t *spkids; - int volume; - int count; - int i; - int qret; int ret; - /* Get saved speakers */ - query = "SELECT COUNT(*) FROM admin WHERE key = 'player:active-spk';"; - ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL); + DPRINTF(E_LOG, L_DB, "Upgrading %s table...\n", name); + + // Step 1: Skipped, no foreign key constraints + // Step 2: Skipped, we are already in a transaction + // Step 3: Nothing to do, we already know our indexes and triggers + // Step 4: Create the new table using table definition from db_init, but with + // new_ prefixed to the name + CHECK_NULL(L_DB, query = sqlite3_mprintf(newtablequery)); + + ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg); if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); + goto error; - goto clear_vars; - } - qret = sqlite3_step(stmt); - if (qret != SQLITE_ROW) - { - DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); + sqlite3_free(query); - sqlite3_finalize(stmt); - - goto clear_vars; - } - - count = sqlite3_column_int(stmt, 0); - sqlite3_finalize(stmt); - - if (count == 0) - goto clear_vars; - else if (count < 0) - return -1; - - spkids = calloc(count, sizeof(uint64_t)); - if (!spkids) - { - DPRINTF(E_LOG, L_DB, "Out of memory for speaker IDs\n"); - - return -1; - } - - query = "SELECT value FROM admin WHERE key = 'player:active-spk';"; - - DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); + // Step 5: Transfer content - note: no support for changed column names or dropped columns! + // This will select the column names from our new table (which where given to us in newtablequery) + CHECK_NULL(L_DB, query = sqlite3_mprintf("SELECT group_concat(name) FROM pragma_table_info('new_%s');", name)); 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)); - - goto out_free_ids; - } - - i = 0; - ret = 0; - while ((qret = sqlite3_step(stmt)) == SQLITE_ROW) - { - strid = (const char *)sqlite3_column_text(stmt, 0); - - ret = safe_hextou64(strid, spkids + i); - if (ret < 0) - { - DPRINTF(E_LOG, L_DB, "Could not convert speaker ID: %s\n", strid); - break; - } - - i++; - } - - sqlite3_finalize(stmt); - - if ((ret == 0) && (qret != SQLITE_DONE)) - { - DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); - - goto out_free_ids; - } - else if (ret < 0) - goto out_free_ids; - - /* Get saved volume */ - query = "SELECT value FROM admin WHERE key = 'player:volume';"; - - 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)); - - goto out_free_ids; + errmsg = sqlite3_mprintf("%s", sqlite3_errmsg(hdl)); + goto error; } ret = sqlite3_step(stmt); if (ret != SQLITE_ROW) { - DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); + if (ret == SQLITE_DONE) + errmsg = sqlite3_mprintf("Getting col names from pragma_table_info returned nothing"); + else + errmsg = sqlite3_mprintf("%s", sqlite3_errmsg(hdl)); sqlite3_finalize(stmt); - goto out_free_ids; + goto error; } - volume = sqlite3_column_int(stmt, 0); - - sqlite3_finalize(stmt); - - /* Add speakers to the table */ - for (i = 0; i < count; i++) - { - query = sqlite3_mprintf(Q_NEWSPK, spkids[i]); - if (!query) - { - DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); - - goto out_free_ids; - } - - DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); - - ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - DPRINTF(E_LOG, L_DB, "Error adding speaker: %s\n", errmsg); - - sqlite3_free(errmsg); - sqlite3_free(query); - } - - free(spkids); - - /* Update with volume */ - query = sqlite3_mprintf(Q_SPKVOL, volume); - 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_exec(hdl, query, NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - DPRINTF(E_LOG, L_DB, "Error adding speaker: %s\n", errmsg); - - sqlite3_free(errmsg); sqlite3_free(query); - /* Clear old config keys */ - clear_vars: - query = "DELETE FROM admin WHERE key = 'player:volume' OR key = 'player:active-spk';"; + CHECK_NULL(L_DB, query = sqlite3_mprintf("INSERT INTO new_%s SELECT %s FROM %s;", name, sqlite3_column_text(stmt, 0), name)); - DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); + sqlite3_finalize(stmt); ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg); if (ret != SQLITE_OK) - DPRINTF(E_LOG, L_DB, "Error adding speaker: %s\n", errmsg); + goto error; + sqlite3_free(query); + + // Step 6: Drop old table + CHECK_NULL(L_DB, query = sqlite3_mprintf("DROP TABLE %s;", name)); + + ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) + goto error; + + sqlite3_free(query); + + // Step 7: Give the new table the final name + CHECK_NULL(L_DB, query = sqlite3_mprintf("ALTER TABLE new_%s RENAME TO %s;", name, name)); + + ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) + goto error; + + sqlite3_free(query); + + // Step 8: Skipped, will be done by db_check_version in db.c + // Step 9: Skipped, no views + // Step 10: Skipped, no foreign key constraints + // Step 11: Skipped, our caller takes care of COMMIT + // Step 12: Skipped, no foreign key constraints + + DPRINTF(E_LOG, L_DB, "Upgrade of %s table complete!\n", name); + + return 0; + + error: + DPRINTF(E_LOG, L_DB, "DB error %d running query '%s': %s\n", ret, query, errmsg); + sqlite3_free(query); sqlite3_free(errmsg); - return 0; - - out_free_ids: - free(spkids); - return -1; - -#undef Q_NEWSPK -#undef Q_SPKVOL } - -/* Upgrade from schema v11 to v12 */ - -#define U_V12_NEW_FILES_TABLE \ - "CREATE TABLE IF NOT EXISTS files (" \ - " id INTEGER PRIMARY KEY NOT NULL," \ - " path VARCHAR(4096) NOT NULL," \ - " fname VARCHAR(255) NOT NULL," \ - " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album VARCHAR(1024) NOT NULL COLLATE DAAP," \ - " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ - " comment VARCHAR(4096) DEFAULT NULL COLLATE DAAP," \ - " type VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ - " composer VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " orchestra VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " conductor VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " grouping VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " 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," \ - " 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) NOT NULL COLLATE DAAP," \ - " media_kind INTEGER NOT NULL," \ - " tv_series_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_episode_num_str VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_network_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_episode_sort INTEGER NOT NULL," \ - " tv_season_num INTEGER NOT NULL," \ - " songalbumid INTEGER NOT NULL," \ - " title_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP" \ - ");" - -#define U_V12_TRG1 \ - "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \ - " BEGIN" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ - " END;" - -#define U_V12_TRG2 \ - "CREATE TRIGGER update_groups_update_file AFTER UPDATE OF songalbumid ON files FOR EACH ROW" \ - " BEGIN" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ - " END;" - -#define U_V12_SCVER \ - "UPDATE admin SET value = '12' WHERE key = 'schema_version';" - -static const struct db_upgrade_query db_upgrade_v12_queries[] = - { - { U_V12_TRG1, "create trigger update_groups_new_file" }, - { U_V12_TRG2, "create trigger update_groups_update_file" }, - - { U_V12_SCVER, "set schema_version to 12" }, - }; - -static int -db_upgrade_v12(sqlite3 *hdl) -{ -#define Q_DUMP "SELECT 'INSERT INTO files " \ - "(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," \ - " codectype, idx, has_video, contentrating, bits_per_sample, album_artist," \ - " media_kind, tv_series_name, tv_episode_num_str, tv_network_name, tv_episode_sort, tv_season_num, " \ - " songalbumid, title_sort, artist_sort, album_sort, composer_sort, album_artist_sort)" \ - " VALUES (' || id || ', ' || QUOTE(path) || ', ' || QUOTE(fname) || ', ' || QUOTE(title) || ', '" \ - " || QUOTE(artist) || ', ' || QUOTE(album) || ', ' || QUOTE(genre) || ', ' || QUOTE(comment) || ', '" \ - " || QUOTE(type) || ', ' || QUOTE(composer) || ', ' || QUOTE(orchestra) || ', ' || QUOTE(conductor) || ', '" \ - " || QUOTE(grouping) || ', ' || QUOTE(url) || ', ' || bitrate || ', ' || samplerate || ', '" \ - " || song_length || ', ' || file_size || ', ' || year || ', ' || track || ', ' || total_tracks || ', '" \ - " || disc || ', ' || total_discs || ', ' || bpm || ', ' || compilation || ', ' || rating || ', '" \ - " || play_count || ', ' || data_kind || ', ' || item_kind || ', ' || QUOTE(description) || ', '" \ - " || time_added || ', ' || time_modified || ', ' || time_played || ', 1, '" \ - " || disabled || ', ' || sample_count || ', ' || QUOTE(codectype) || ', ' || idx || ', '" \ - " || has_video || ', ' || contentrating || ', ' || bits_per_sample || ', ' || QUOTE(album_artist) || ', '" \ - " || media_kind || ', ' || QUOTE(tv_series_name) || ', ' || QUOTE(tv_episode_num_str) || ', '" \ - " || QUOTE(tv_network_name) || ', ' || tv_episode_sort || ', ' || tv_season_num || ', '" \ - " || songalbumid || ', ' || QUOTE(title) || ', ' || QUOTE(artist) || ', ' || QUOTE(album) || ', '" \ - " || QUOTE(composer) || ', ' || QUOTE(album_artist) || ');' FROM files;" - - return db_upgrade_files_table(hdl, Q_DUMP, U_V12_NEW_FILES_TABLE); - -#undef Q_DUMP -} - - -/* Upgrade from schema v12 to v13 */ - -#define U_V13_PL2 \ - "UPDATE playlists SET query = 'f.media_kind = 1' where id = 2;" - -#define U_V13_PL3 \ - "UPDATE playlists SET query = 'f.media_kind = 2' where id = 3;" - -#define U_V13_PL4 \ - "UPDATE playlists SET query = 'f.media_kind = 64' where id = 4;" - -#define U_V13_SCVER \ - "UPDATE admin SET value = '13' WHERE key = 'schema_version';" - -static const struct db_upgrade_query db_upgrade_v13_queries[] = - { - { U_V13_PL2, "update default smart playlist 'Music'" }, - { U_V13_PL3, "update default smart playlist 'Movies'" }, - { U_V13_PL4, "update default smart playlist 'TV Shows'" }, - - { U_V13_SCVER, "set schema_version to 13" }, - }; - -/* Upgrade from schema v13 to v14 */ -/* Adds seek, songartistid, and two new smart playlists */ - -#define U_V14_NEW_FILES_TABLE \ - "CREATE TABLE IF NOT EXISTS files (" \ - " id INTEGER PRIMARY KEY NOT NULL," \ - " path VARCHAR(4096) NOT NULL," \ - " fname VARCHAR(255) NOT NULL," \ - " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album VARCHAR(1024) NOT NULL COLLATE DAAP," \ - " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ - " comment VARCHAR(4096) DEFAULT NULL COLLATE DAAP," \ - " type VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ - " composer VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " orchestra VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " conductor VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " grouping VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " 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," \ - " seek 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," \ - " 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) NOT NULL COLLATE DAAP," \ - " media_kind INTEGER NOT NULL," \ - " tv_series_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_episode_num_str VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_network_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_episode_sort INTEGER NOT NULL," \ - " tv_season_num INTEGER NOT NULL," \ - " songartistid INTEGER NOT NULL," \ - " songalbumid INTEGER NOT NULL," \ - " title_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP" \ - ");" - -#define U_V14_DELETE_PL5_1 \ - "DELETE FROM playlists WHERE id=5;" - -#define U_V14_DELETE_PL5_2 \ - "DELETE FROM playlistitems WHERE playlistid=5;" - -#define U_V14_DELETE_PL6_1 \ - "DELETE FROM playlists WHERE id=6;" - -#define U_V14_DELETE_PL6_2 \ - "DELETE FROM playlistitems WHERE playlistid=6;" - -#define U_V14_TRG1 \ - "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \ - " BEGIN" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \ - " END;" - -#define U_V14_TRG2 \ - "CREATE TRIGGER update_groups_update_file AFTER UPDATE OF songalbumid ON files FOR EACH ROW" \ - " BEGIN" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \ - " END;" - -#define U_V14_PL5 \ - "INSERT OR IGNORE INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \ - " VALUES(5, 'Podcasts', 1, 'f.media_kind = 4', 0, '', 0, 1);" - -#define U_V14_PL6 \ - "INSERT OR IGNORE INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \ - " VALUES(6, 'Audiobooks', 1, 'f.media_kind = 8', 0, '', 0, 7);" - -#define U_V14_SCVER \ - "UPDATE admin SET value = '14' WHERE key = 'schema_version';" - -static const struct db_upgrade_query db_upgrade_v14_queries[] = - { - { U_V14_DELETE_PL5_1, "delete playlist id 5 table playlists" }, - { U_V14_DELETE_PL5_2, "delete playlist id 5 table playlistitems" }, - { U_V14_DELETE_PL6_1, "delete playlist id 6 table playlists" }, - { U_V14_DELETE_PL6_2, "delete playlist id 6 table playlistitems" }, - - { U_V14_TRG1, "create trigger update_groups_new_file" }, - { U_V14_TRG2, "create trigger update_groups_update_file" }, - - { U_V14_PL5, "create default smart playlist 'Podcasts' table playlists" }, - { U_V14_PL6, "create default smart playlist 'Audiobooks' table playlists" }, - - { U_V14_SCVER, "set schema_version to 14" }, - }; - -static int -db_upgrade_v14(sqlite3 *hdl) -{ -#define Q_DUMP "SELECT 'INSERT INTO files " \ - "(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, seek, data_kind, item_kind," \ - " description, time_added, time_modified, time_played, db_timestamp, disabled, sample_count," \ - " codectype, idx, has_video, contentrating, bits_per_sample, album_artist," \ - " media_kind, tv_series_name, tv_episode_num_str, tv_network_name, tv_episode_sort, tv_season_num, " \ - " songartistid, songalbumid, " \ - " title_sort, artist_sort, album_sort, composer_sort, album_artist_sort)" \ - " VALUES (' || id || ', ' || QUOTE(path) || ', ' || QUOTE(fname) || ', ' || QUOTE(title) || ', '" \ - " || QUOTE(artist) || ', ' || QUOTE(album) || ', ' || QUOTE(genre) || ', ' || QUOTE(comment) || ', '" \ - " || QUOTE(type) || ', ' || QUOTE(composer) || ', ' || QUOTE(orchestra) || ', ' || QUOTE(conductor) || ', '" \ - " || QUOTE(grouping) || ', ' || QUOTE(url) || ', ' || bitrate || ', ' || samplerate || ', '" \ - " || song_length || ', ' || file_size || ', ' || year || ', ' || track || ', ' || total_tracks || ', '" \ - " || disc || ', ' || total_discs || ', ' || bpm || ', ' || compilation || ', ' || rating || ', '" \ - " || play_count || ', 0, ' || data_kind || ', ' || item_kind || ', ' || QUOTE(description) || ', '" \ - " || time_added || ', ' || time_modified || ', ' || time_played || ', ' || db_timestamp || ', '" \ - " || disabled || ', ' || sample_count || ', ' || QUOTE(codectype) || ', ' || idx || ', '" \ - " || has_video || ', ' || contentrating || ', ' || bits_per_sample || ', ' || QUOTE(album_artist) || ', '" \ - " || media_kind || ', ' || QUOTE(tv_series_name) || ', ' || QUOTE(tv_episode_num_str) || ', '" \ - " || QUOTE(tv_network_name) || ', ' || tv_episode_sort || ', ' || tv_season_num || ', " \ - " daap_songalbumid(' || QUOTE(album_artist) || ', ''''), ' || songalbumid || ', '" \ - " || QUOTE(title_sort) || ', ' || QUOTE(artist_sort) || ', ' || QUOTE(album_sort) || ', '" \ - " || QUOTE(composer_sort) || ', ' || QUOTE(album_artist_sort) || ');' FROM files;" - - return db_upgrade_files_table(hdl, Q_DUMP, U_V14_NEW_FILES_TABLE); - -#undef Q_DUMP -} - -/* Upgrade from schema v14 to v15 */ -/* Adds artwork field - nothing else */ - -#define U_V15_NEW_FILES_TABLE \ - "CREATE TABLE IF NOT EXISTS files (" \ - " id INTEGER PRIMARY KEY NOT NULL," \ - " path VARCHAR(4096) NOT NULL," \ - " fname VARCHAR(255) NOT NULL," \ - " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album VARCHAR(1024) NOT NULL COLLATE DAAP," \ - " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ - " comment VARCHAR(4096) DEFAULT NULL COLLATE DAAP," \ - " type VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ - " composer VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " orchestra VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " conductor VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " grouping VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " 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," \ - " artwork INTEGER DEFAULT 0," \ - " rating INTEGER DEFAULT 0," \ - " play_count INTEGER DEFAULT 0," \ - " seek 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," \ - " 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) NOT NULL COLLATE DAAP," \ - " media_kind INTEGER NOT NULL," \ - " tv_series_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_episode_num_str VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_network_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " tv_episode_sort INTEGER NOT NULL," \ - " tv_season_num INTEGER NOT NULL," \ - " songartistid INTEGER NOT NULL," \ - " songalbumid INTEGER NOT NULL," \ - " title_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP" \ - ");" - -#define U_V15_TRG1 \ - "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \ - " BEGIN" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \ - " END;" - -#define U_V15_TRG2 \ - "CREATE TRIGGER update_groups_update_file AFTER UPDATE OF songalbumid ON files FOR EACH ROW" \ - " BEGIN" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \ - " INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \ - " END;" - -#define U_V15_SCVER \ - "UPDATE admin SET value = '15' WHERE key = 'schema_version';" - -static const struct db_upgrade_query db_upgrade_v15_queries[] = - { - { U_V15_TRG1, "create trigger update_groups_new_file" }, - { U_V15_TRG2, "create trigger update_groups_update_file" }, - - { U_V15_SCVER, "set schema_version to 15" }, - }; - -static int -db_upgrade_v15(sqlite3 *hdl) -{ -#define Q_DUMP "SELECT 'INSERT INTO files " \ - "(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, artwork, rating, play_count, seek, data_kind, item_kind," \ - " description, time_added, time_modified, time_played, db_timestamp, disabled, sample_count," \ - " codectype, idx, has_video, contentrating, bits_per_sample, album_artist," \ - " media_kind, tv_series_name, tv_episode_num_str, tv_network_name, tv_episode_sort, tv_season_num, " \ - " songartistid, songalbumid, " \ - " title_sort, artist_sort, album_sort, composer_sort, album_artist_sort)" \ - " VALUES (' || id || ', ' || QUOTE(path) || ', ' || QUOTE(fname) || ', ' || QUOTE(title) || ', '" \ - " || QUOTE(artist) || ', ' || QUOTE(album) || ', ' || QUOTE(genre) || ', ' || QUOTE(comment) || ', '" \ - " || QUOTE(type) || ', ' || QUOTE(composer) || ', ' || QUOTE(orchestra) || ', ' || QUOTE(conductor) || ', '" \ - " || QUOTE(grouping) || ', ' || QUOTE(url) || ', ' || bitrate || ', ' || samplerate || ', '" \ - " || song_length || ', ' || file_size || ', ' || year || ', ' || track || ', ' || total_tracks || ', '" \ - " || disc || ', ' || total_discs || ', ' || bpm || ', ' || compilation || ', 0, ' || rating || ', '" \ - " || play_count || ', ' || seek || ', ' || data_kind || ', ' || item_kind || ', ' || QUOTE(description) || ', '" \ - " || time_added || ', ' || time_modified || ', ' || time_played || ', ' || db_timestamp || ', '" \ - " || disabled || ', ' || sample_count || ', ' || QUOTE(codectype) || ', ' || idx || ', '" \ - " || has_video || ', ' || contentrating || ', ' || bits_per_sample || ', ' || QUOTE(album_artist) || ', '" \ - " || media_kind || ', ' || QUOTE(tv_series_name) || ', ' || QUOTE(tv_episode_num_str) || ', '" \ - " || QUOTE(tv_network_name) || ', ' || tv_episode_sort || ', ' || tv_season_num || ', '" \ - " || songartistid ||', ' || songalbumid || ', '" \ - " || QUOTE(title_sort) || ', ' || QUOTE(artist_sort) || ', ' || QUOTE(album_sort) || ', '" \ - " || QUOTE(composer_sort) || ', ' || QUOTE(album_artist_sort) || ');' FROM files;" - - return db_upgrade_files_table(hdl, Q_DUMP, U_V15_NEW_FILES_TABLE); - -#undef Q_DUMP -} - -/* Upgrade from schema v15 to v15.01 */ -/* Improved indices (will be generated by generic schema update) */ - -#define U_V1501_SCVER_MAJOR \ - "INSERT INTO admin (key, value) VALUES ('schema_version_major', '15');" -#define U_V1501_SCVER_MINOR \ - "INSERT INTO admin (key, value) VALUES ('schema_version_minor', '01');" - -static const struct db_upgrade_query db_upgrade_v1501_queries[] = - { - { U_V1501_SCVER_MAJOR, "set schema_version_major to 15" }, - { U_V1501_SCVER_MINOR, "set schema_version_minor to 01" }, - }; - -/* Upgrade from schema v15.01 to v16 */ - -#define U_V16_CREATE_VIEW_FILELIST \ - "CREATE VIEW IF NOT EXISTS filelist as" \ - " SELECT " \ - " virtual_path, time_modified, 3 as type " \ - " FROM files WHERE disabled = 0" \ - " UNION " \ - " SELECT " \ - " virtual_path, db_timestamp, 1 as type " \ - " FROM playlists WHERE disabled = 0 AND type = 0" \ - ";" - -#define U_V16_ALTER_TBL_FILES_ADD_COL \ - "ALTER TABLE files ADD COLUMN virtual_path VARCHAR(4096) DEFAULT NULL;" - -#define U_V16_ALTER_TBL_PL_ADD_COL \ - "ALTER TABLE playlists ADD COLUMN virtual_path VARCHAR(4096) DEFAULT NULL;" - -#define D_V1600_SCVER \ - "DELETE FROM admin WHERE key = 'schema_version';" -#define U_V1600_SCVER_MAJOR \ - "UPDATE admin SET value = '16' WHERE key = 'schema_version_major';" -#define U_V1600_SCVER_MINOR \ - "UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';" - -static const struct db_upgrade_query db_upgrade_v16_queries[] = - { - { U_V16_ALTER_TBL_FILES_ADD_COL, "alter table files add column virtual_path" }, - { U_V16_ALTER_TBL_PL_ADD_COL, "alter table playlists add column virtual_path" }, - { U_V16_CREATE_VIEW_FILELIST, "create new view filelist" }, - - { D_V1600_SCVER, "delete schema_version" }, - { U_V1600_SCVER_MAJOR, "set schema_version_major to 16" }, - { U_V1600_SCVER_MINOR, "set schema_version_minor to 00" }, - }; - -static int -db_upgrade_v16(sqlite3 *hdl) -{ - sqlite3_stmt *stmt; - char *query; - char *uquery; - char *errmsg; - char *artist; - char *album; - char *title; - int id; - char *path; - int type; - char virtual_path[PATH_MAX]; - int ret; - - query = "SELECT id, album_artist, album, title, path FROM files;"; - - 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; - } - - while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) - { - id = sqlite3_column_int(stmt, 0); - artist = (char *)sqlite3_column_text(stmt, 1); - album = (char *)sqlite3_column_text(stmt, 2); - title = (char *)sqlite3_column_text(stmt, 3); - path = (char *)sqlite3_column_text(stmt, 4); - - if (strncmp(path, "http:", strlen("http:")) == 0) - { - snprintf(virtual_path, PATH_MAX, "/http:/%s", title); - } - else if (strncmp(path, "spotify:", strlen("spotify:")) == 0) - { - snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", artist, album, title); - } - else - { - snprintf(virtual_path, PATH_MAX, "/file:%s", path); - } - - uquery = sqlite3_mprintf("UPDATE files SET virtual_path = '%q' WHERE id = %d;", virtual_path, id); - ret = sqlite3_exec(hdl, uquery, NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Error updating files: %s\n", errmsg); - } - - sqlite3_free(uquery); - sqlite3_free(errmsg); - } - - sqlite3_finalize(stmt); - - - query = "SELECT id, title, path, type FROM playlists;"; - - 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; - } - - while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) - { - id = sqlite3_column_int(stmt, 0); - title = (char *)sqlite3_column_text(stmt, 1); - path = (char *)sqlite3_column_text(stmt, 2); - type = sqlite3_column_int(stmt, 3); - - if (type == 0) /* Excludes default/Smart playlists and playlist folders */ - { - if (strncmp(path, "spotify:", strlen("spotify:")) == 0) - snprintf(virtual_path, PATH_MAX, "/spotify:/%s", title); - else - snprintf(virtual_path, PATH_MAX, "/file:%s", path); - - uquery = sqlite3_mprintf("UPDATE playlists SET virtual_path = '%q' WHERE id = %d;", virtual_path, id); - - ret = sqlite3_exec(hdl, uquery, NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - DPRINTF(E_LOG, L_DB, "Error updating playlists: %s\n", errmsg); - - sqlite3_free(uquery); - sqlite3_free(errmsg); - } - } - - sqlite3_finalize(stmt); - - return 0; -} - -/* Upgrade from schema v16.00 to v17.00 */ -/* Expand data model to allow for nested playlists and change default playlist - * enumeration - */ - -#define U_V17_PL_PARENTID_ADD \ - "ALTER TABLE playlists ADD COLUMN parent_id INTEGER DEFAULT 0;" -#define U_V17_PL_TYPE_CHANGE \ - "UPDATE playlists SET type = 2 WHERE type = 1;" - -#define U_V17_SCVER_MAJOR \ - "UPDATE admin SET value = '17' WHERE key = 'schema_version_major';" -#define U_V17_SCVER_MINOR \ - "UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';" - -static const struct db_upgrade_query db_upgrade_v17_queries[] = - { - { U_V17_PL_PARENTID_ADD,"expanding table playlists with parent_id column" }, - { U_V17_PL_TYPE_CHANGE, "changing numbering of default playlists 1 -> 2" }, - - { U_V17_SCVER_MAJOR, "set schema_version_major to 17" }, - { U_V17_SCVER_MINOR, "set schema_version_minor to 00" }, - }; - /* Upgrade from schema v17.00 to v18.00 */ /* Change playlist type enumeration and recreate filelist view (include smart * playlists in view) @@ -1674,6 +887,7 @@ static const struct db_upgrade_query db_upgrade_v1909_queries[] = { U_V1909_SCVER_MINOR, "set schema_version_minor to 09" }, }; + // Clean up after bug in commit fde0a281 (schema 19.09) #define U_V1910_CLEANUP_TIME_SKIPPED \ "UPDATE files SET time_skipped = 0 WHERE time_skipped > 2000000000;" @@ -1688,6 +902,7 @@ static const struct db_upgrade_query db_upgrade_v1910_queries[] = { U_V1910_SCVER_MINOR, "set schema_version_minor to 10" }, }; + #define U_v1911_ALTER_QUEUE_ADD_COMPOSER \ "ALTER TABLE queue ADD COLUMN composer VARCHAR(1024) DEFAULT NULL;" @@ -1726,6 +941,98 @@ static const struct db_upgrade_query db_upgrade_v1912_queries[] = }; +#define U_V20_NEW_FILES_TABLE \ + "CREATE TABLE new_files (" \ + " id INTEGER PRIMARY KEY NOT NULL," \ + " path VARCHAR(4096) NOT NULL," \ + " virtual_path VARCHAR(4096) DEFAULT NULL," \ + " fname VARCHAR(255) NOT NULL," \ + " directory_id INTEGER DEFAULT 0," \ + " title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album VARCHAR(1024) NOT NULL COLLATE DAAP," \ + " album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \ + " genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ + " comment VARCHAR(4096) DEFAULT NULL COLLATE DAAP," \ + " type VARCHAR(255) DEFAULT NULL COLLATE DAAP," \ + " composer VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " orchestra VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " conductor VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " grouping VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " 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," \ + " date_released 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," \ + " artwork INTEGER DEFAULT 0," \ + " rating INTEGER DEFAULT 0," \ + " play_count INTEGER DEFAULT 0," \ + " skip_count INTEGER DEFAULT 0," \ + " seek INTEGER DEFAULT 0," \ + " data_kind INTEGER DEFAULT 0," \ + " media_kind INTEGER DEFAULT 0," \ + " item_kind INTEGER DEFAULT 0," \ + " description INTEGER DEFAULT 0," \ + " db_timestamp INTEGER DEFAULT 0," \ + " time_added INTEGER DEFAULT 0," \ + " time_modified INTEGER DEFAULT 0," \ + " time_played INTEGER DEFAULT 0," \ + " time_skipped INTEGER DEFAULT 0," \ + " disabled INTEGER DEFAULT 0," \ + " sample_count 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," \ + " tv_series_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " tv_episode_num_str VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " tv_network_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " tv_episode_sort INTEGER NOT NULL," \ + " tv_season_num INTEGER NOT NULL," \ + " songartistid INTEGER DEFAULT 0," \ + " songalbumid INTEGER DEFAULT 0," \ + " title_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ + " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP" \ + ");" + +static int +db_upgrade_v20(sqlite3 *hdl) +{ + return db_table_upgrade(hdl, "files", U_V20_NEW_FILES_TABLE); +} + +#define U_V2000_DROP_TRG1 \ + "DROP TRIGGER IF EXISTS update_groups_new_file;" +#define U_V2000_DROP_TRG2 \ + "DROP TRIGGER IF EXISTS update_groups_update_file;" + +#define U_V2000_SCVER_MAJOR \ + "UPDATE admin SET value = '20' WHERE key = 'schema_version_major';" +#define U_V2000_SCVER_MINOR \ + "UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';" + +static const struct db_upgrade_query db_upgrade_v2000_queries[] = + { + { U_V2000_DROP_TRG1, "drop trigger update_groups_new_file" }, + { U_V2000_DROP_TRG2, "drop trigger update_groups_update_file" }, + + { U_V2000_SCVER_MAJOR, "set schema_version_major to 20" }, + { U_V2000_SCVER_MINOR, "set schema_version_minor to 00" }, + }; + + int db_upgrade(sqlite3 *hdl, int db_ver) { @@ -1735,84 +1042,12 @@ db_upgrade(sqlite3 *hdl, int db_ver) if (ret < 0) return -1; + ret = db_drop_triggers(hdl); + if (ret < 0) + return -1; + switch (db_ver) { - case 1000: - ret = db_generic_upgrade(hdl, db_upgrade_v11_queries, ARRAY_SIZE(db_upgrade_v11_queries)); - if (ret < 0) - return -1; - - ret = db_upgrade_v11(hdl); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1100: - ret = db_upgrade_v12(hdl); - if (ret < 0) - return -1; - - ret = db_generic_upgrade(hdl, db_upgrade_v12_queries, ARRAY_SIZE(db_upgrade_v12_queries)); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1200: - ret = db_generic_upgrade(hdl, db_upgrade_v13_queries, ARRAY_SIZE(db_upgrade_v13_queries)); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1300: - ret = db_upgrade_v14(hdl); - if (ret < 0) - return -1; - - ret = db_generic_upgrade(hdl, db_upgrade_v14_queries, ARRAY_SIZE(db_upgrade_v14_queries)); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1400: - ret = db_upgrade_v15(hdl); - if (ret < 0) - return -1; - - ret = db_generic_upgrade(hdl, db_upgrade_v15_queries, ARRAY_SIZE(db_upgrade_v15_queries)); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1500: - ret = db_generic_upgrade(hdl, db_upgrade_v1501_queries, ARRAY_SIZE(db_upgrade_v1501_queries)); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1501: - ret = db_generic_upgrade(hdl, db_upgrade_v16_queries, ARRAY_SIZE(db_upgrade_v16_queries)); - if (ret < 0) - return -1; - - ret = db_upgrade_v16(hdl); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1600: - ret = db_generic_upgrade(hdl, db_upgrade_v17_queries, ARRAY_SIZE(db_upgrade_v17_queries)); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - case 1700: ret = db_generic_upgrade(hdl, db_upgrade_v18_queries, ARRAY_SIZE(db_upgrade_v18_queries)); if (ret < 0) @@ -1920,6 +1155,17 @@ db_upgrade(sqlite3 *hdl, int db_ver) if (ret < 0) return -1; + /* FALLTHROUGH */ + + case 1912: + ret = db_upgrade_v20(hdl); + if (ret < 0) + return -1; + + ret = db_generic_upgrade(hdl, db_upgrade_v2000_queries, ARRAY_SIZE(db_upgrade_v2000_queries)); + if (ret < 0) + return -1; + break; default: diff --git a/src/lastfm.c b/src/lastfm.c index d72538c3..6e18620b 100644 --- a/src/lastfm.c +++ b/src/lastfm.c @@ -154,7 +154,7 @@ response_process(struct http_client_ctx *ctx, char **errmsg) DPRINTF(E_DBG, L_LASTFM, "LastFM response:\n%s\n", body); if (errmsg) - *errmsg = trimwhitespace(mxmlGetOpaque(e_node)); + *errmsg = atrim(mxmlGetOpaque(e_node)); mxmlDelete(tree); return -1; @@ -180,7 +180,7 @@ response_process(struct http_client_ctx *ctx, char **errmsg) return -1; } - sk = trimwhitespace(mxmlGetOpaque(s_node)); + sk = atrim(mxmlGetOpaque(s_node)); if (sk) { DPRINTF(E_LOG, L_LASTFM, "Got session key from LastFM: %s\n", sk); diff --git a/src/library.c b/src/library.c index 1b5b20bc..dac3bfb6 100644 --- a/src/library.c +++ b/src/library.c @@ -128,14 +128,6 @@ library_add_media(struct media_file_info *mfi) mfi->path, mfi->directory_id, mfi->virtual_path); } - if (!mfi->item_kind) - mfi->item_kind = 2; /* music */ - if (!mfi->media_kind) - mfi->media_kind = MEDIA_KIND_MUSIC; /* music */ - - unicode_fixup_mfi(mfi); - fixup_tags_mfi(mfi); - if (mfi->id == 0) db_file_add(mfi); else diff --git a/src/library/filescanner.c b/src/library/filescanner.c index 8a03ff15..72681596 100644 --- a/src/library/filescanner.c +++ b/src/library/filescanner.c @@ -509,14 +509,18 @@ process_regular_file(const char *file, struct stat *sb, int type, int flags, int else { mfi.data_kind = DATA_KIND_FILE; + mfi.file_size = sb->st_size; if (type & F_SCAN_TYPE_AUDIOBOOK) mfi.media_kind = MEDIA_KIND_AUDIOBOOK; else if (type & F_SCAN_TYPE_PODCAST) mfi.media_kind = MEDIA_KIND_PODCAST; - mfi.compilation = (type & F_SCAN_TYPE_COMPILATION); - mfi.file_size = sb->st_size; + if (type & F_SCAN_TYPE_COMPILATION) + { + mfi.compilation = 1; + mfi.album_artist = safe_strdup(cfg_getstr(cfg_getsec(cfg, "library"), "compilation_artist")); + } ret = scan_metadata_ffmpeg(file, &mfi); if (ret < 0) @@ -1686,7 +1690,6 @@ queue_add_stream(const char *path, int position, char reshuffle, uint32_t item_i memset(&mfi, 0, sizeof(struct media_file_info)); scan_metadata_stream(path, &mfi); - unicode_fixup_mfi(&mfi); map_media_file_to_queue_item(&item, &mfi); diff --git a/src/library/filescanner_itunes.c b/src/library/filescanner_itunes.c index c21427ce..4a891947 100644 --- a/src/library/filescanner_itunes.c +++ b/src/library/filescanner_itunes.c @@ -548,7 +548,6 @@ process_track_file(plist_t trk) mfi->album_artist = strdup(mfi->artist); } - unicode_fixup_mfi(mfi); db_file_update(mfi); free_mfi(mfi, 0); diff --git a/src/misc.c b/src/misc.c index f74fe924..2262c7f2 100644 --- a/src/misc.c +++ b/src/misc.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #ifndef CLOCK_REALTIME @@ -341,6 +342,7 @@ safe_asprintf(const char *fmt, ...) { char *ret = NULL; va_list va; + va_start(va, fmt); if (vasprintf(&ret, fmt, va) < 0) { @@ -348,9 +350,34 @@ safe_asprintf(const char *fmt, ...) abort(); } va_end(va); + return ret; } +int +safe_snprintf_cat(char *dst, size_t n, const char *fmt, ...) +{ + size_t dstlen; + va_list va; + int ret; + + if (!dst || !fmt) + return -1; + + dstlen = strlen(dst); + if (n < dstlen) + return -1; + + va_start(va, fmt); + ret = vsnprintf(dst + dstlen, n - dstlen, fmt, va); + va_end(va); + + if (ret >= 0 && ret < n - dstlen) + return 0; + else + return -1; +} + /* Key/value functions */ struct keyval * @@ -586,7 +613,7 @@ m_readfile(const char *path, int num_lines) goto error; } - lines[i] = trimwhitespace(line); + lines[i] = atrim(line); if (!lines[i] || (strlen(lines[i]) == 0)) { DPRINTF(E_LOG, L_MISC, "Line %d in '%s' is invalid\n", i+1, path); @@ -643,40 +670,58 @@ unicode_fixup_string(char *str, const char *fromcode) } char * -trimwhitespace(const char *str) +trim(char *str) { - char *ptr; - char *start; - char *out; + size_t start; // Position of first non-space char + size_t term; // Position of 0-terminator if (!str) return NULL; - // Find the beginning - while (isspace(*str)) - str++; + start = 0; + term = strlen(str); - if (*str == 0) // All spaces? - return strdup(""); + while ((start < term) && isspace(str[start])) + start++; + while ((term > start) && isspace(str[term - 1])) + term--; - // Make copy, because we will need to insert a null terminator - start = strdup(str); - if (!start) + str[term] = '\0'; + + // Shift chars incl. terminator + if (start) + memmove(str, str + start, term - start + 1); + + return str; +} + +char * +atrim(const char *str) +{ + size_t start; // Position of first non-space char + size_t term; // Position of 0-terminator + size_t size; + char *result; + + if (!str) return NULL; - // Find the end - ptr = start + strlen(start) - 1; - while (ptr > start && isspace(*ptr)) - ptr--; + start = 0; + term = strlen(str); - // Insert null terminator - *(ptr+1) = 0; + while ((start < term) && isspace(str[start])) + start++; + while ((term > start) && isspace(str[term - 1])) + term--; - out = strdup(start); + size = term - start + 1; - free(start); + result = malloc(size); - return out; + memcpy(result, str + start, size); + result[size - 1] = '\0'; + + return result; } void diff --git a/src/misc.h b/src/misc.h index 65915540..edc63c19 100644 --- a/src/misc.h +++ b/src/misc.h @@ -58,6 +58,10 @@ safe_strdup(const char *str); char * safe_asprintf(const char *fmt, ...); +int +safe_snprintf_cat(char *dst, size_t n, const char *fmt, ...); + + /* Key/value functions */ struct keyval * keyval_alloc(void); @@ -87,8 +91,13 @@ m_readfile(const char *path, int num_lines); char * unicode_fixup_string(char *str, const char *fromcode); +// Modifies str so it is trimmed. Returns pointer to str. char * -trimwhitespace(const char *str); +trim(char *str); + +// Copies the trimmed part of str to a newly allocated string (caller must free) +char * +atrim(const char *str); void swap_pointers(char **a, char **b); @@ -105,8 +114,7 @@ b64_encode(const uint8_t *in, size_t len); uint64_t murmur_hash64(const void *key, int len, uint32_t seed); - -/* Checks if the address is in a network that is configured as trusted */ +// Checks if the address is in a network that is configured as trusted bool peer_address_is_trusted(const char *addr);