diff --git a/sqlext/sqlext.c b/sqlext/sqlext.c index b8a3bacc..56f65a90 100644 --- a/sqlext/sqlext.c +++ b/sqlext/sqlext.c @@ -244,257 +244,6 @@ sqlext_daap_unicode_xcollation(void *notused, int llen, const void *left, int rl return rpp; } - -/* Taken from "extension-functions.c" by Liam Healy (2010-02-06 15:45:07) - http://www.sqlite.org/contrib/download/extension-functions.c?get=25 */ -/* LMH from sqlite3 3.3.13 */ -/* -** This table maps from the first byte of a UTF-8 character to the number -** of trailing bytes expected. A value '4' indicates that the table key -** is not a legal first byte for a UTF-8 character. -*/ -static const uint8_t xtra_utf8_bytes[256] = { -/* 0xxxxxxx */ -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - -/* 10wwwwww */ -4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, -4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, -4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, -4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - -/* 110yyyyy */ -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - -/* 1110zzzz */ -2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - -/* 11110yyy */ -3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, -}; - - -/* -** This table maps from the number of trailing bytes in a UTF-8 character -** to an integer constant that is effectively calculated for each character -** read by a naive implementation of a UTF-8 character reader. The code -** in the READ_UTF8 macro explains things best. -*/ -static const int xtra_utf8_bits[] = { - 0, - 12416, /* (0xC0 << 6) + (0x80) */ - 925824, /* (0xE0 << 12) + (0x80 << 6) + (0x80) */ - 63447168 /* (0xF0 << 18) + (0x80 << 12) + (0x80 << 6) + 0x80 */ -}; - -/* -** If a UTF-8 character contains N bytes extra bytes (N bytes follow -** the initial byte so that the total character length is N+1) then -** masking the character with utf8_mask[N] must produce a non-zero -** result. Otherwise, we have an (illegal) overlong encoding. -*/ -static const int utf_mask[] = { - 0x00000000, - 0xffffff80, - 0xfffff800, - 0xffff0000, -}; - -/* LMH salvaged from sqlite3 3.3.13 source code src/utf.c */ -#define READ_UTF8(zIn, c) { \ - int xtra; \ - c = *(zIn)++; \ - xtra = xtra_utf8_bytes[c]; \ - switch( xtra ){ \ - case 4: c = (int)0xFFFD; break; \ - case 3: c = (c<<6) + *(zIn)++; \ - case 2: c = (c<<6) + *(zIn)++; \ - case 1: c = (c<<6) + *(zIn)++; \ - c -= xtra_utf8_bits[xtra]; \ - if( (utf_mask[xtra]&c)==0 \ - || (c&0xFFFFF800)==0xD800 \ - || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ - } \ -} - -static int sqlite3ReadUtf8(const unsigned char *z) -{ - int c; - READ_UTF8(z, c); - return c; -} - -/* - * X is a pointer to the first byte of a UTF-8 character. Increment - * X so that it points to the next character. This only works right - * if X points to a well-formed UTF-8 string. - */ -#define sqliteNextChar(X) while( (0xc0&*++(X))==0x80 ){} -#define sqliteCharVal(X) sqlite3ReadUtf8(X) - -/* - * Given a string z1, retutns the (0 based) index of it's first occurence - * in z2 after the first s characters. - * Returns -1 when there isn't a match. - * updates p to point to the character where the match occured. - * This is an auxiliary function. -*/ -static int _substr(const char* z1, const char* z2, int s, const char** p) -{ - int c = 0; - int rVal = -1; - const char* zt1; - const char* zt2; - int c1, c2; - - if ('\0' == *z1) - { - return -1; - } - - while ((sqliteCharVal((unsigned char *)z2) != 0) && (c++) < s) - { - sqliteNextChar(z2); - } - - c = 0; - while ((sqliteCharVal((unsigned char * )z2)) != 0) - { - zt1 = z1; - zt2 = z2; - - do - { - c1 = sqliteCharVal((unsigned char * )zt1); - c2 = sqliteCharVal((unsigned char * )zt2); - sqliteNextChar(zt1); - sqliteNextChar(zt2); - } while (c1 == c2 && c1 != 0 && c2 != 0); - - if (c1 == 0) - { - rVal = c; - break; - } - - sqliteNextChar(z2); - ++c; - } - if (p) - { - *p = z2; - } - return rVal >= 0 ? rVal + s : rVal; -} - -/* - * Taken from "extension-functions.c" (function charindexFunc) by Liam Healy (2010-02-06 15:45:07) - * http://www.sqlite.org/contrib/download/extension-functions.c?get=25 - * - * Given 2 input strings (s1,s2) and an integer (n) searches from the nth character - * for the string s1. Returns the position where the match occured. - * Characters are counted from 1. - * 0 is returned when no match occurs. - */ -static void sqlext_daap_charindex_xfunc(sqlite3_context *context, int argc, sqlite3_value **argv) -{ - const uint8_t *z1; /* s1 string */ - uint8_t *z2; /* s2 string */ - int s = 0; - int rVal = 0; - - //assert(argc == 3 || argc == 2); - if (argc != 2 && argc != 3) - { - sqlite3_result_error(context, "daap_charindex() requires 2 or 3 parameters", -1); - return; - } - - if ( SQLITE_NULL == sqlite3_value_type(argv[0]) || SQLITE_NULL == sqlite3_value_type(argv[1])) - { - sqlite3_result_null(context); - return; - } - - z1 = sqlite3_value_text(argv[0]); - if (z1 == 0) - return; - z2 = (uint8_t*) sqlite3_value_text(argv[1]); - if (argc == 3) - { - s = sqlite3_value_int(argv[2]) - 1; - if (s < 0) - { - s = 0; - } - } - else - { - s = 0; - } - - rVal = _substr((char *) z1, (char *) z2, s, NULL); - sqlite3_result_int(context, rVal + 1); -} - -/* - * Taken from "extension-functions.c" (function leftFunc) by Liam Healy (2010-02-06 15:45:07) - * http://www.sqlite.org/contrib/download/extension-functions.c?get=25 - * - * Given a string (s) and an integer (n) returns the n leftmost (UTF-8) characters - * if the string has a length<=n or is NULL this function is NOP - */ -static void sqlext_daap_leftstr_xfunc(sqlite3_context *context, int argc, sqlite3_value **argv) -{ - int c = 0; - int cc = 0; - int l = 0; - const unsigned char *z; /* input string */ - const unsigned char *zt; - unsigned char *rz; /* output string */ - - //assert( argc==2); - if (argc != 2 && argc != 3) - { - sqlite3_result_error(context, "daap_leftstr() requires 2 parameters", -1); - return; - } - - if ( SQLITE_NULL == sqlite3_value_type(argv[0]) || SQLITE_NULL == sqlite3_value_type(argv[1])) - { - sqlite3_result_null(context); - return; - } - - z = sqlite3_value_text(argv[0]); - l = sqlite3_value_int(argv[1]); - zt = z; - - while ( sqliteCharVal(zt) && c++ < l) - sqliteNextChar(zt); - - cc = zt - z; - - rz = sqlite3_malloc(zt - z + 1); - if (!rz) - { - sqlite3_result_error_nomem(context); - return; - } - strncpy((char*) rz, (char*) z, zt - z); - *(rz + cc) = '\0'; - sqlite3_result_text(context, (char*) rz, -1, SQLITE_TRANSIENT); - sqlite3_free(rz); -} - int sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) { @@ -519,23 +268,5 @@ sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines return -1; } - ret = sqlite3_create_function(db, "daap_leftstr", 2, SQLITE_UTF8, NULL, sqlext_daap_leftstr_xfunc, NULL, NULL); - if (ret != SQLITE_OK) - { - if (pzErrMsg) - *pzErrMsg = sqlite3_mprintf("Could not create daap_leftstr function: %s\n", sqlite3_errmsg(db)); - - return -1; - } - - ret = sqlite3_create_function(db, "daap_charindex", 3, SQLITE_UTF8, NULL, sqlext_daap_charindex_xfunc, NULL, NULL); - if (ret != SQLITE_OK) - { - if (pzErrMsg) - *pzErrMsg = sqlite3_mprintf("Could not create daap_charindex function: %s\n", sqlite3_errmsg(db)); - - return -1; - } - return 0; } diff --git a/src/Makefile.am b/src/Makefile.am index b7e914e3..9bd27399 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -71,6 +71,7 @@ forked_daapd_LDADD = -lrt \ forked_daapd_SOURCES = main.c \ db.c db.h \ + db_upgrade.c db_upgrade.h \ logger.c logger.h \ conffile.c conffile.h \ cache.c cache.h \ diff --git a/src/db.c b/src/db.c index 1cbf6f15..8d84d7f8 100644 --- a/src/db.c +++ b/src/db.c @@ -44,6 +44,7 @@ #include "cache.h" #include "misc.h" #include "db.h" +#include "db_upgrade.h" #define STR(x) ((x) ? (x) : "") @@ -136,6 +137,8 @@ static const struct col_type_map mfi_cols_map[] = { 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 }, }; /* This list must be kept in sync with @@ -155,6 +158,7 @@ static const struct col_type_map pli_cols_map[] = { 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 }, /* items is computed on the fly */ }; @@ -223,6 +227,8 @@ static const ssize_t dbmfi_cols_map[] = dbmfi_offsetof(composer_sort), dbmfi_offsetof(album_artist_sort), dbmfi_offsetof(virtual_path), + dbmfi_offsetof(directory_id), + dbmfi_offsetof(date_released), }; /* This list must be kept in sync with @@ -242,6 +248,7 @@ static const ssize_t dbpli_cols_map[] = dbpli_offsetof(special_id), dbpli_offsetof(virtual_path), dbpli_offsetof(parent_id), + dbpli_offsetof(directory_id), /* items is computed on the fly */ }; @@ -329,18 +336,6 @@ db_escape_string(const char *str) return ret; } -void -free_fi(struct filelist_info *fi, int content_only) -{ - if (fi->virtual_path) - free(fi->virtual_path); - - if (!content_only) - free(fi); - else - memset(fi, 0, sizeof(struct filelist_info)); -} - void free_pi(struct pairing_info *pi, int content_only) { @@ -495,6 +490,18 @@ free_pli(struct playlist_info *pli, int content_only) memset(pli, 0, sizeof(struct playlist_info)); } +void +free_di(struct directory_info *di, int content_only) +{ + if (di->virtual_path) + free(di->virtual_path); + + if (!content_only) + free(di); + else + memset(di, 0, sizeof(struct directory_info)); +} + /* Unlock notification support */ static void @@ -707,12 +714,13 @@ db_purge_cruft(time_t ref) char *errmsg; int i; int ret; - char *queries[3] = { NULL, NULL, NULL }; - char *queries_tmpl[3] = + char *queries[4] = { NULL, NULL, NULL, NULL }; + char *queries_tmpl[4] = { "DELETE FROM playlistitems WHERE playlistid IN (SELECT id FROM playlists p WHERE p.type <> %d AND p.db_timestamp < %" PRIi64 ");", "DELETE FROM playlists WHERE type <> %d AND db_timestamp < %" PRIi64 ";", - "DELETE FROM files WHERE -1 <> %d AND db_timestamp < %" PRIi64 ";" + "DELETE FROM files WHERE -1 <> %d AND db_timestamp < %" PRIi64 ";", + "DELETE FROM directories WHERE id > 4 AND -1 <> %d AND db_timestamp < %" PRIi64 ";" }; if (sizeof(queries) != sizeof(queries_tmpl)) @@ -1949,89 +1957,6 @@ db_query_fetch_string_sort(struct query_params *qp, char **string, char **sortst return 0; } -/* Filelist */ - -int -db_mpd_start_query_filelist(struct query_params *qp, char *parentpath) -{ - char *query; - int ret; - - query = sqlite3_mprintf( - "SELECT " - " CASE WHEN daap_charindex('/', virtual_path, LENGTH(%Q)+1) = 0 " - " THEN " - " virtual_path " - " ELSE " - " daap_leftstr(virtual_path, daap_charindex('/', virtual_path, LENGTH(%Q)+1)-1) " - " END AS path, " - " MAX(time_modified), " - " CASE WHEN daap_charindex('/', virtual_path, LENGTH(%Q)+1) = 0 " - " THEN " - " type " - " ELSE " - " 2 " - " END AS ftype " - "FROM filelist " - "WHERE virtual_path LIKE '%q%%' " - "GROUP BY ftype, path " - "ORDER BY ftype, path;", parentpath, parentpath, parentpath, parentpath); - - if (!query) - { - DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); - return -1; - } - - DPRINTF(E_DBG, L_DB, "Starting query '%s'\n", query); - - ret = db_blocking_prepare_v2(query, -1, &qp->stmt, NULL); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); - - sqlite3_free(query); - return -1; - } - - sqlite3_free(query); - - return 0; -} - -int -db_mpd_query_fetch_filelist(struct query_params *qp, struct filelist_info *fi) -{ - int ret; - - memset(fi, 0, sizeof(struct filelist_info)); - - if (!qp->stmt) - { - DPRINTF(E_LOG, L_DB, "Query not started!\n"); - return -1; - } - - ret = db_blocking_step(qp->stmt); - if (ret == SQLITE_DONE) - { - DPRINTF(E_DBG, L_DB, "End of query results\n"); - fi->virtual_path = NULL; - return 0; - } - else if (ret != SQLITE_ROW) - { - DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); - return -1; - } - - fi->virtual_path = strdup((char *)sqlite3_column_text(qp->stmt, 0)); - fi->time_modified = sqlite3_column_int(qp->stmt, 1); - fi->type = sqlite3_column_int(qp->stmt, 2); - - return 0; -} - /* Files */ int @@ -2282,6 +2207,7 @@ db_file_id_bymatch(char *path) #undef Q_TMPL } +//TODO [cleanup] unused function(?) int db_file_id_byfilebase(char *filename, char *base) { @@ -2608,8 +2534,8 @@ db_file_add(struct media_file_info *mfi) " 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" \ - " ) " \ + " title_sort, artist_sort, album_sort, composer_sort, album_artist_sort, virtual_path," \ + " directory_id) " \ " 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," \ @@ -2617,7 +2543,7 @@ db_file_add(struct media_file_info *mfi) " %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));" + " TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), TRIM(%Q), %d);" char *query; char *errmsg; @@ -2652,7 +2578,7 @@ db_file_add(struct media_file_info *mfi) 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->composer_sort, mfi->album_artist_sort, mfi->virtual_path, mfi->directory_id); if (!query) { @@ -2697,10 +2623,9 @@ db_file_update(struct media_file_info *mfi) " 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)" \ + " virtual_path = TRIM(%Q), directory_id = %d" \ " WHERE id = %d;" -// struct media_file_info *oldmfi; char *query; char *errmsg; int ret; @@ -2711,18 +2636,6 @@ db_file_update(struct media_file_info *mfi) return -1; } - /* - oldmfi = db_file_fetch_byid(mfi->id); - - if (!oldmfi) - { - DPRINTF(E_WARN, L_DB, "File with id '%d' does not exist\n", mfi->id); - return -1; - } - - free_mfi(oldmfi, 0); - */ - mfi->db_timestamp = (uint64_t)time(NULL); if (mfi->time_modified == 0) @@ -2742,7 +2655,7 @@ db_file_update(struct media_file_info *mfi) 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->composer_sort, mfi->album_artist_sort, mfi->virtual_path, mfi->directory_id, mfi->id); if (!query) @@ -2829,15 +2742,20 @@ db_file_delete_bypath(char *path) void db_file_disable_bypath(char *path, char *strip, uint32_t cookie) { -#define Q_TMPL "UPDATE files SET path = substr(path, %d), disabled = %" PRIi64 " WHERE path = '%q';" +#define Q_TMPL "UPDATE files SET path = substr(path, %d), virtual_path = substr(virtual_path, %d), disabled = %" PRIi64 " WHERE path = '%q';" char *query; int64_t disabled; int striplen; + int striplenvpath; disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; striplen = strlen(strip) + 1; + if (strlen(strip) > 0) + striplenvpath = strlen(strip) + strlen("/file:/"); + else + striplenvpath = 0; - query = sqlite3_mprintf(Q_TMPL, striplen, disabled, path); + query = sqlite3_mprintf(Q_TMPL, striplen, striplenvpath, disabled, path); db_query_run(query, 1, 1); #undef Q_TMPL @@ -2846,15 +2764,20 @@ db_file_disable_bypath(char *path, char *strip, uint32_t cookie) void db_file_disable_bymatch(char *path, char *strip, uint32_t cookie) { -#define Q_TMPL "UPDATE files SET path = substr(path, %d), disabled = %" PRIi64 " WHERE path LIKE '%q/%%';" +#define Q_TMPL "UPDATE files SET path = substr(path, %d), virtual_path = substr(virtual_path, %d), disabled = %" PRIi64 " WHERE path LIKE '%q/%%';" char *query; int64_t disabled; int striplen; + int striplenvpath; disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; striplen = strlen(strip) + 1; + if (strlen(strip) > 0) + striplenvpath = strlen(strip) + strlen("/file:/"); + else + striplenvpath = 0; - query = sqlite3_mprintf(Q_TMPL, striplen, disabled, path); + query = sqlite3_mprintf(Q_TMPL, striplen, striplenvpath, disabled, path); db_query_run(query, 1, 1); #undef Q_TMPL @@ -2863,11 +2786,11 @@ db_file_disable_bymatch(char *path, char *strip, uint32_t cookie) int db_file_enable_bycookie(uint32_t cookie, char *path) { -#define Q_TMPL "UPDATE files SET path = '%q' || path, disabled = 0 WHERE disabled = %" PRIi64 ";" +#define Q_TMPL "UPDATE files SET path = '%q' || path, virtual_path = '/file:%q' || virtual_path, disabled = 0 WHERE disabled = %" PRIi64 ";" char *query; int ret; - query = sqlite3_mprintf(Q_TMPL, path, (int64_t)cookie); + query = sqlite3_mprintf(Q_TMPL, path, path, (int64_t)cookie); ret = db_query_run(query, 1, 1); @@ -2875,6 +2798,21 @@ db_file_enable_bycookie(uint32_t cookie, char *path) #undef Q_TMPL } +int +db_file_update_directoryid(char *path, int dir_id) +{ +#define Q_TMPL "UPDATE files SET directory_id = %d WHERE path = %Q;" + char *query; + int ret; + + query = sqlite3_mprintf(Q_TMPL, dir_id, path); + + ret = db_query_run(query, 1, 0); + + return ((ret < 0) ? -1 : sqlite3_changes(hdl)); +#undef Q_TMPL +} + /* Playlists */ int @@ -3248,8 +3186,8 @@ int db_pl_add(struct playlist_info *pli, int *id) { #define QDUP_TMPL "SELECT COUNT(*) FROM playlists p WHERE p.title = TRIM(%Q) AND p.path = '%q';" -#define QADD_TMPL "INSERT INTO playlists (title, type, query, db_timestamp, disabled, path, idx, special_id, parent_id, virtual_path)" \ - " VALUES (TRIM(%Q), %d, '%q', %" PRIi64 ", %d, '%q', %d, %d, %d, '%q');" +#define QADD_TMPL "INSERT INTO playlists (title, type, query, db_timestamp, disabled, path, idx, special_id, parent_id, virtual_path, directory_id)" \ + " VALUES (TRIM(%Q), %d, '%q', %" PRIi64 ", %d, '%q', %d, %d, %d, '%q', %d);" char *query; char *errmsg; int ret; @@ -3275,7 +3213,7 @@ db_pl_add(struct playlist_info *pli, int *id) /* Add */ query = sqlite3_mprintf(QADD_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->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->directory_id); if (!query) { @@ -3340,14 +3278,14 @@ int db_pl_update(struct playlist_info *pli) { #define Q_TMPL "UPDATE playlists SET title = TRIM(%Q), type = %d, query = '%q', db_timestamp = %" PRIi64 ", disabled = %d, " \ - " path = '%q', idx = %d, special_id = %d, parent_id = %d, virtual_path = '%q' " \ + " path = '%q', idx = %d, special_id = %d, parent_id = %d, virtual_path = '%q', directory_id = %d " \ " WHERE id = %d;" char *query; int ret; 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->id); + pli->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->directory_id, pli->id); ret = db_query_run(query, 1, 0); @@ -3402,15 +3340,20 @@ db_pl_delete_bypath(char *path) void db_pl_disable_bypath(char *path, char *strip, uint32_t cookie) { -#define Q_TMPL "UPDATE playlists SET path = substr(path, %d), disabled = %" PRIi64 " WHERE path = '%q';" +#define Q_TMPL "UPDATE playlists SET path = substr(path, %d), virtual_path = substr(virtual_path, %d), disabled = %" PRIi64 " WHERE path = '%q';" char *query; int64_t disabled; int striplen; + int striplenvpath; disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; striplen = strlen(strip) + 1; + if (strlen(strip) > 0) + striplenvpath = strlen(strip) + strlen("/file:/"); + else + striplenvpath = 0; - query = sqlite3_mprintf(Q_TMPL, striplen, disabled, path); + query = sqlite3_mprintf(Q_TMPL, striplen, striplenvpath, disabled, path); db_query_run(query, 1, 0); #undef Q_TMPL @@ -3419,15 +3362,20 @@ db_pl_disable_bypath(char *path, char *strip, uint32_t cookie) void db_pl_disable_bymatch(char *path, char *strip, uint32_t cookie) { -#define Q_TMPL "UPDATE playlists SET path = substr(path, %d), disabled = %" PRIi64 " WHERE path LIKE '%q/%%';" +#define Q_TMPL "UPDATE playlists SET path = substr(path, %d), virtual_path = substr(virtual_path, %d), disabled = %" PRIi64 " WHERE path LIKE '%q/%%';" char *query; int64_t disabled; int striplen; + int striplenvpath; disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; striplen = strlen(strip) + 1; + if (strlen(strip) > 0) + striplenvpath = strlen(strip) + strlen("/file:/"); + else + striplenvpath = 0; - query = sqlite3_mprintf(Q_TMPL, striplen, disabled, path); + query = sqlite3_mprintf(Q_TMPL, striplen, striplenvpath, disabled, path); db_query_run(query, 1, 0); #undef Q_TMPL @@ -3436,11 +3384,11 @@ db_pl_disable_bymatch(char *path, char *strip, uint32_t cookie) int db_pl_enable_bycookie(uint32_t cookie, char *path) { -#define Q_TMPL "UPDATE playlists SET path = '%q' || path, disabled = 0 WHERE disabled = %" PRIi64 ";" +#define Q_TMPL "UPDATE playlists SET path = '%q' || path, virtual_path = '/file:%q' || virtual_path, disabled = 0 WHERE disabled = %" PRIi64 ";" char *query; int ret; - query = sqlite3_mprintf(Q_TMPL, path, (int64_t)cookie); + query = sqlite3_mprintf(Q_TMPL, path, path, (int64_t)cookie); ret = db_query_run(query, 1, 0); @@ -3568,6 +3516,290 @@ db_group_persistentid_byid(int id, int64_t *persistentid) } +/* Directories */ +int +db_directory_id_byvirtualpath(char *virtual_path) +{ +#define Q_TMPL "SELECT d.id FROM directories d WHERE d.virtual_path = '%q';" + char *query; + int ret; + + query = sqlite3_mprintf(Q_TMPL, virtual_path); + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + + return 0; + } + + ret = db_file_id_byquery(query); + + sqlite3_free(query); + + return ret; + +#undef Q_TMPL +} + +int +db_directory_enum_start(struct directory_enum *de) +{ +#define Q_TMPL "SELECT * FROM directories WHERE disabled = 0 AND parent_id = %d ORDER BY virtual_path;" + char *query; + int ret; + + de->stmt = NULL; + + query = sqlite3_mprintf(Q_TMPL, de->parent_id); + + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + + return -1; + } + + DPRINTF(E_DBG, L_DB, "Starting enum '%s'\n", query); + + ret = db_blocking_prepare_v2(query, -1, &de->stmt, NULL); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); + + sqlite3_free(query); + return -1; + } + + sqlite3_free(query); + + return 0; + +#undef Q_TMPL +} + +int +db_directory_enum_fetch(struct directory_enum *de, struct directory_info *di) +{ + uint64_t disabled; + int ret; + + memset(di, 0, sizeof(struct directory_info)); + + if (!de->stmt) + { + DPRINTF(E_LOG, L_DB, "Directory enum not started!\n"); + return -1; + } + + ret = db_blocking_step(de->stmt); + if (ret == SQLITE_DONE) + { + DPRINTF(E_DBG, L_DB, "End of directory enum results\n"); + return 0; + } + else if (ret != SQLITE_ROW) + { + DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); + return -1; + } + + di->id = sqlite3_column_int(de->stmt, 0); + di->virtual_path = (char *)sqlite3_column_text(de->stmt, 1); + di->db_timestamp = sqlite3_column_int(de->stmt, 2); + disabled = sqlite3_column_int64(de->stmt, 3); + di->disabled = (disabled != 0); + di->parent_id = sqlite3_column_int(de->stmt, 4); + + return 0; +} + +void +db_directory_enum_end(struct directory_enum *de) +{ + if (!de->stmt) + return; + + sqlite3_finalize(de->stmt); + de->stmt = NULL; +} + +static int +db_directory_add(struct directory_info *di, int *id) +{ +#define QADD_TMPL "INSERT INTO directories (virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (TRIM(%Q), %d, %d, %d);" + + char *query; + char *errmsg; + int ret; + + query = sqlite3_mprintf(QADD_TMPL, di->virtual_path, di->db_timestamp, di->disabled, di->parent_id); + + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + return -1; + } + + DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); + + 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); + + *id = (int)sqlite3_last_insert_rowid(hdl); + if (*id == 0) + { + DPRINTF(E_LOG, L_DB, "Successful insert but no last_insert_rowid!\n"); + return -1; + } + + DPRINTF(E_DBG, L_DB, "Added directory %s with id %d\n", di->virtual_path, *id); + + return 0; + +#undef QADD_TMPL +} + +static int +db_directory_update(struct directory_info *di) +{ +#define QADD_TMPL "UPDATE directories SET virtual_path = TRIM(%Q), db_timestamp = %d, disabled = %d, parent_id = %d" \ + " WHERE id = %d;" + char *query; + char *errmsg; + int ret; + + /* Add */ + query = sqlite3_mprintf(QADD_TMPL, di->virtual_path, di->db_timestamp, di->disabled, di->parent_id, di->id); + + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + return -1; + } + + DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); + + 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); + + DPRINTF(E_DBG, L_DB, "Updated directory %s with id %d\n", di->virtual_path, di->id); + + return 0; + +#undef QADD_TMPL +} + +int +db_directory_addorupdate(char *virtual_path, int disabled, int parent_id) +{ + struct directory_info di; + int id; + int ret; + + id = db_directory_id_byvirtualpath(virtual_path); + + di.id = id; + di.parent_id = parent_id; + di.virtual_path = virtual_path; + di.disabled = disabled; + di.db_timestamp = (uint64_t)time(NULL); + + if (di.id == 0) + ret = db_directory_add(&di, &id); + else + ret = db_directory_update(&di); + + if (ret < 0 || id <= 0) + { + DPRINTF(E_LOG, L_DB, "Insert or update of directory failed '%s'\n", virtual_path); + return -1; + } + + return id; +} + +void +db_directory_ping_bymatch(char *path) +{ +#define Q_TMPL_DIR "UPDATE directories SET db_timestamp = %" PRIi64 " WHERE virtual_path = '/file:%q' OR virtual_path LIKE '/file:%q/%%';" + char *query; + + query = sqlite3_mprintf(Q_TMPL_DIR, (int64_t)time(NULL), path, path); + + db_query_run(query, 1, 1); +#undef Q_TMPL_DIR +} + +void +db_directory_disable_bymatch(char *path, char *strip, uint32_t cookie) +{ +#define Q_TMPL "UPDATE directories SET virtual_path = substr(virtual_path, %d), disabled = %" PRIi64 " WHERE virtual_path = '/file:%q' OR virtual_path LIKE '/file:%q/%%';" + char *query; + int64_t disabled; + int striplen; + + disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; + if (strlen(strip) > 0) + striplen = strlen(strip) + strlen("/file:/"); + else + striplen = 0; + + query = sqlite3_mprintf(Q_TMPL, striplen, disabled, path, path, path); + + db_query_run(query, 1, 1); +#undef Q_TMPL +} + +int +db_directory_enable_bycookie(uint32_t cookie, char *path) +{ +#define Q_TMPL "UPDATE directories SET virtual_path = '/file:%q' || virtual_path, disabled = 0 WHERE disabled = %" PRIi64 ";" + char *query; + int ret; + + query = sqlite3_mprintf(Q_TMPL, path, (int64_t)cookie); + + ret = db_query_run(query, 1, 0); + + return ((ret < 0) ? -1 : sqlite3_changes(hdl)); +#undef Q_TMPL +} + +int +db_directory_enable_bypath(char *path) +{ +#define Q_TMPL "UPDATE directories SET disabled = 0 WHERE virtual_path = %Q;" + char *query; + int ret; + + query = sqlite3_mprintf(Q_TMPL, path); + + ret = db_query_run(query, 1, 0); + + return ((ret < 0) ? -1 : sqlite3_changes(hdl)); +#undef Q_TMPL +} + + /* Remotes */ static int db_pairing_delete_byremote(char *remote_id) @@ -3656,11 +3888,13 @@ db_pairing_fetch_byguid(struct pairing_info *pi) void db_spotify_purge(void) { - char *queries[3] = + char *queries[5] = { "DELETE FROM files WHERE path LIKE 'spotify:%%';", "DELETE FROM playlistitems WHERE filepath LIKE 'spotify:%%';", "DELETE FROM playlists WHERE path LIKE 'spotify:%%';", + "DELETE FROM directories WHERE virtual_path LIKE '/spotify:/%%';", + "UPDATE directories SET disabled = 4294967296 WHERE virtual_path = '/spotify:';", }; int i; int ret; @@ -4634,7 +4868,9 @@ db_perthread_deinit(void) " 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" \ + " virtual_path VARCHAR(4096) DEFAULT NULL," \ + " directory_id INTEGER DEFAULT 0," \ + " date_released INTEGER DEFAULT 0" \ ");" #define T_PL \ @@ -4649,7 +4885,8 @@ db_perthread_deinit(void) " idx INTEGER NOT NULL," \ " special_id INTEGER DEFAULT 0," \ " virtual_path VARCHAR(4096)," \ - " parent_id INTEGER DEFAULT 0" \ + " parent_id INTEGER DEFAULT 0," \ + " directory_id INTEGER DEFAULT 0" \ ");" #define T_PLITEMS \ @@ -4679,7 +4916,8 @@ db_perthread_deinit(void) "CREATE TABLE IF NOT EXISTS speakers(" \ " id INTEGER PRIMARY KEY NOT NULL," \ " selected INTEGER NOT NULL," \ - " volume INTEGER NOT NULL" \ + " volume INTEGER NOT NULL," \ + " name VARCHAR(255) DEFAULT NULL" \ ");" #define T_INOTIFY \ @@ -4689,16 +4927,14 @@ db_perthread_deinit(void) " path VARCHAR(4096) NOT NULL" \ ");" -#define V_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 IN (2, 3)" \ - ";" +#define T_DIRECTORIES \ + "CREATE TABLE IF NOT EXISTS directories (" \ + " id INTEGER PRIMARY KEY NOT NULL," \ + " virtual_path VARCHAR(4096) NOT NULL," \ + " db_timestamp INTEGER DEFAULT 0," \ + " disabled INTEGER DEFAULT 0," \ + " parent_id INTEGER DEFAULT 0" \ + ");" #define TRG_GROUPS_INSERT_FILES \ "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \ @@ -4744,16 +4980,30 @@ db_perthread_deinit(void) " VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);" */ + +#define Q_DIR1 \ + "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (1, '/', 0, 0, 0);" +#define Q_DIR2 \ + "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (2, '/file:', 0, 0, 1);" +#define Q_DIR3 \ + "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (3, '/http:', 0, 0, 1);" +#define Q_DIR4 \ + "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (4, '/spotify:', 0, 4294967296, 1);" + /* Rule of thumb: Will the current version of forked-daapd work with the new * 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 18 -#define SCHEMA_VERSION_MINOR 01 +#define SCHEMA_VERSION_MAJOR 19 +#define SCHEMA_VERSION_MINOR 00 #define Q_SCVER_MAJOR \ - "INSERT INTO admin (key, value) VALUES ('schema_version_major', '18');" + "INSERT INTO admin (key, value) VALUES ('schema_version_major', '19');" #define Q_SCVER_MINOR \ - "INSERT INTO admin (key, value) VALUES ('schema_version_minor', '01');" + "INSERT INTO admin (key, value) VALUES ('schema_version_minor', '00');" struct db_init_query { char *query; @@ -4770,8 +5020,7 @@ static const struct db_init_query db_init_table_queries[] = { T_PAIRINGS, "create table pairings" }, { T_SPEAKERS, "create table speakers" }, { T_INOTIFY, "create table inotify" }, - - { V_FILELIST, "create view filelist" }, + { T_DIRECTORIES, "create table directories" }, { TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" }, { TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" }, @@ -4782,6 +5031,10 @@ static const struct db_init_query db_init_table_queries[] = { 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_SCVER_MAJOR, "set schema version major" }, { Q_SCVER_MINOR, "set schema version minor" }, @@ -4831,12 +5084,18 @@ static const struct db_init_query db_init_table_queries[] = #define I_FILELIST \ "CREATE INDEX IF NOT EXISTS idx_filelist ON files(disabled, virtual_path, time_modified);" +#define I_FILE_DIR \ + "CREATE INDEX IF NOT EXISTS idx_file_dir ON files(disabled, directory_id);" + #define I_PL_PATH \ "CREATE INDEX IF NOT EXISTS idx_pl_path ON playlists(path);" #define I_PL_DISABLED \ "CREATE INDEX IF NOT EXISTS idx_pl_disabled ON playlists(disabled, type, virtual_path, db_timestamp);" +#define I_PL_DIR \ + "CREATE INDEX IF NOT EXISTS idx_pl_dir ON files(disabled, directory_id);" + #define I_FILEPATH \ "CREATE INDEX IF NOT EXISTS idx_filepath ON playlistitems(filepath ASC);" @@ -4849,6 +5108,12 @@ static const struct db_init_query db_init_table_queries[] = #define I_PAIRING \ "CREATE INDEX IF NOT EXISTS idx_pairingguid ON pairings(guid);" +#define I_DIR_VPATH \ + "CREATE INDEX IF NOT EXISTS idx_dir_vpath ON directories(disabled, virtual_path);" + +#define I_DIR_PARENT \ + "CREATE INDEX IF NOT EXISTS idx_dir_parentid ON directories(parent_id);" + static const struct db_init_query db_init_index_queries[] = { { I_RESCAN, "create rescan index" }, @@ -4864,9 +5129,11 @@ static const struct db_init_query db_init_index_queries[] = { I_TITLE, "create title index" }, { I_ALBUM, "create album index" }, { I_FILELIST, "create filelist index" }, + { I_FILE_DIR, "create file dir index" }, { I_PL_PATH, "create playlist path index" }, { I_PL_DISABLED, "create playlist state index" }, + { I_PL_DIR, "create playlist dir index" }, { I_FILEPATH, "create file path index" }, { I_PLITEMID, "create playlist id index" }, @@ -4874,6 +5141,9 @@ static const struct db_init_query db_init_index_queries[] = { I_GRP_PERSIST, "create groups persistentid index" }, { I_PAIRING, "create pairing guid index" }, + + { I_DIR_VPATH, "create directories disabled_virtualpath index" }, + { I_DIR_PARENT, "create directories parentid index" }, }; static int @@ -4899,70 +5169,6 @@ db_create_indices(void) return 0; } - -static int -db_drop_indices(void) -{ -#define Q_INDEX "SELECT name FROM sqlite_master WHERE type == 'index' AND name LIKE 'idx_%';" -#define Q_TMPL "DROP INDEX %q;" - sqlite3_stmt *stmt; - char *errmsg; - char *query; - char *index[256]; - int ret; - int i; - int n; - - DPRINTF(E_DBG, L_DB, "Running query '%s'\n", Q_INDEX); - - ret = sqlite3_prepare_v2(hdl, Q_INDEX, strlen(Q_INDEX) + 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) - { - index[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, index[i]); - free(index[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_INDEX -} - static int db_create_tables(void) { @@ -4989,1164 +5195,7 @@ db_create_tables(void) return ret; } -static int -db_generic_upgrade(const struct db_init_query *queries, int nqueries) -{ - char *errmsg; - int i; - int ret; - for (i = 0; i < nqueries; i++, queries++) - { - DPRINTF(E_DBG, L_DB, "DB upgrade query: %s\n", queries->desc); - - ret = sqlite3_exec(hdl, queries->query, NULL, NULL, &errmsg); - if (ret != SQLITE_OK) - { - DPRINTF(E_FATAL, L_DB, "DB upgrade error: %s\n", errmsg); - - sqlite3_free(errmsg); - return -1; - } - } - - return 0; -} - -/* Upgrade the files table to the new schema by dumping and reloading the - * table. A bit tedious. - */ -static int -db_upgrade_files_table(const char *dumpquery, 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_init_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(void) -{ -#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 */ - count = db_get_count("SELECT COUNT(*) FROM admin WHERE key = 'player:active-spk';"); - if (count == 0) - goto clear_vars; - else if (count < 0) - return -1; - - spkids = (uint64_t *)malloc(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); - - 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; - } - - ret = sqlite3_step(stmt); - if (ret != SQLITE_ROW) - { - DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); - - sqlite3_finalize(stmt); - goto out_free_ids; - } - - 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';"; - - 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); - - 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_init_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(void) -{ -#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(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_init_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_init_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(void) -{ -#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(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_init_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(void) -{ -#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(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_init_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_init_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(void) -{ - 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_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_init_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) - */ - -#define U_V18_PL_TYPE_CHANGE_PLAIN \ - "UPDATE playlists SET type = 3 WHERE type = 0;" -#define U_V18_PL_TYPE_CHANGE_SPECIAL \ - "UPDATE playlists SET type = 0 WHERE type = 2;" -#define U_V18_DROP_VIEW_FILELIST \ - "DROP VIEW IF EXISTS filelist;" -#define U_V18_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 IN (2, 3)" \ - ";" - -#define U_V18_SCVER_MAJOR \ - "UPDATE admin SET value = '18' WHERE key = 'schema_version_major';" -#define U_V18_SCVER_MINOR \ - "UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';" - -static const struct db_init_query db_upgrade_v18_queries[] = - { - { U_V18_PL_TYPE_CHANGE_PLAIN, "changing numbering of plain playlists 0 -> 3" }, - { U_V18_PL_TYPE_CHANGE_SPECIAL, "changing numbering of default playlists 2 -> 0" }, - { U_V18_DROP_VIEW_FILELIST, "dropping view filelist" }, - { U_V18_CREATE_VIEW_FILELIST, "creating view filelist" }, - - { U_V18_SCVER_MAJOR, "set schema_version_major to 18" }, - { U_V18_SCVER_MINOR, "set schema_version_minor to 00" }, - }; - -/* Upgrade from schema v18.00 to v18.01 */ -/* Change virtual_path for playlists: remove file extension - */ - -#define U_V1801_UPDATE_PLAYLISTS_M3U \ - "UPDATE playlists SET virtual_path = replace(virtual_path, '.m3u', '');" -#define U_V1801_UPDATE_PLAYLISTS_PLS \ - "UPDATE playlists SET virtual_path = replace(virtual_path, '.pls', '');" -#define U_V1801_UPDATE_PLAYLISTS_SMARTPL \ - "UPDATE playlists SET virtual_path = replace(virtual_path, '.smartpl', '');" - -#define U_V1801_SCVER_MAJOR \ - "UPDATE admin SET value = '18' WHERE key = 'schema_version_major';" -#define U_V1801_SCVER_MINOR \ - "UPDATE admin SET value = '01' WHERE key = 'schema_version_minor';" - -static const struct db_init_query db_upgrade_v1801_queries[] = - { - { U_V1801_UPDATE_PLAYLISTS_M3U, "update table playlists" }, - { U_V1801_UPDATE_PLAYLISTS_PLS, "update table playlists" }, - { U_V1801_UPDATE_PLAYLISTS_SMARTPL, "update table playlists" }, - - { U_V1801_SCVER_MAJOR, "set schema_version_major to 18" }, - { U_V1801_SCVER_MINOR, "set schema_version_minor to 01" }, - }; - -static int -db_upgrade(int db_ver) -{ - int ret; - - ret = db_drop_indices(); - if (ret < 0) - return -1; - - switch (db_ver) - { - case 1000: - ret = db_generic_upgrade(db_upgrade_v11_queries, sizeof(db_upgrade_v11_queries) / sizeof(db_upgrade_v11_queries[0])); - if (ret < 0) - return -1; - - ret = db_upgrade_v11(); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1100: - ret = db_upgrade_v12(); - if (ret < 0) - return -1; - - ret = db_generic_upgrade(db_upgrade_v12_queries, sizeof(db_upgrade_v12_queries) / sizeof(db_upgrade_v12_queries[0])); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1200: - ret = db_generic_upgrade(db_upgrade_v13_queries, sizeof(db_upgrade_v13_queries) / sizeof(db_upgrade_v13_queries[0])); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1300: - ret = db_upgrade_v14(); - if (ret < 0) - return -1; - - ret = db_generic_upgrade(db_upgrade_v14_queries, sizeof(db_upgrade_v14_queries) / sizeof(db_upgrade_v14_queries[0])); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1400: - ret = db_upgrade_v15(); - if (ret < 0) - return -1; - - ret = db_generic_upgrade(db_upgrade_v15_queries, sizeof(db_upgrade_v15_queries) / sizeof(db_upgrade_v15_queries[0])); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1500: - ret = db_generic_upgrade(db_upgrade_v1501_queries, sizeof(db_upgrade_v1501_queries) / sizeof(db_upgrade_v1501_queries[0])); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1501: - ret = db_generic_upgrade(db_upgrade_v16_queries, sizeof(db_upgrade_v16_queries) / sizeof(db_upgrade_v16_queries[0])); - if (ret < 0) - return -1; - - ret = db_upgrade_v16(); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1600: - ret = db_generic_upgrade(db_upgrade_v17_queries, sizeof(db_upgrade_v17_queries) / sizeof(db_upgrade_v17_queries[0])); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1700: - ret = db_generic_upgrade(db_upgrade_v18_queries, sizeof(db_upgrade_v18_queries) / sizeof(db_upgrade_v18_queries[0])); - if (ret < 0) - return -1; - - /* FALLTHROUGH */ - - case 1800: - ret = db_generic_upgrade(db_upgrade_v1801_queries, sizeof(db_upgrade_v1801_queries) / sizeof(db_upgrade_v1801_queries[0])); - if (ret < 0) - return -1; - - break; - - default: - DPRINTF(E_FATAL, L_DB, "No upgrade path from the current DB schema\n"); - return -1; - } - - ret = db_create_indices(); - if (ret < 0) - return -1; - - return 0; -} static int db_check_version(void) @@ -6209,7 +5258,22 @@ db_check_version(void) return -1; } - ret = db_upgrade(db_ver); + ret = db_upgrade(hdl, db_ver); + 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 = db_create_indices(); if (ret < 0) { DPRINTF(E_LOG, L_DB, "Database upgrade errored out, rolling back changes ...\n"); diff --git a/src/db.h b/src/db.h index fe6f7ad0..23ad96fa 100644 --- a/src/db.h +++ b/src/db.h @@ -61,12 +61,6 @@ enum query_type { #define ARTWORK_SPOTIFY 6 #define ARTWORK_HTTP 7 -enum filelistitem_type { - F_PLAYLIST = 1, - F_DIR = 2, - F_FILE = 3, -}; - struct query_params { /* Query parameters, filled in by caller */ enum query_type type; @@ -188,6 +182,9 @@ struct media_file_info { char *album_artist_sort; char *virtual_path; + + uint32_t directory_id; /* Id of directory */ + uint32_t date_released; }; #define mfi_offsetof(field) offsetof(struct media_file_info, field) @@ -215,6 +212,7 @@ struct playlist_info { uint32_t special_id; /* iTunes identifies certain 'special' playlists with special meaning */ char *virtual_path; /* virtual path of underlying playlist */ uint32_t parent_id; /* Id of parent playlist if the playlist is nested */ + uint32_t directory_id; /* Id of directory */ }; #define pli_offsetof(field) offsetof(struct playlist_info, field) @@ -233,6 +231,7 @@ struct db_playlist_info { char *special_id; char *virtual_path; char *parent_id; + char *directory_id; }; #define dbpli_offsetof(field) offsetof(struct db_playlist_info, field) @@ -322,16 +321,12 @@ struct db_media_file_info { char *composer_sort; char *album_artist_sort; char *virtual_path; + char *directory_id; + char *date_released; }; #define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field) -struct filelist_info { - char *virtual_path; - uint32_t time_modified; - enum filelistitem_type type; -}; - struct watch_info { int wd; char *path; @@ -353,15 +348,35 @@ struct filecount_info { uint32_t length; }; +/* Directory ids must be in sync with the ids in Q_DIR* in db.c */ +enum directory_ids { + DIR_ROOT = 1, + DIR_FILE = 2, + DIR_HTTP = 3, + DIR_SPOTIFY = 4, +}; + +struct directory_info { + uint32_t id; + char *virtual_path; + uint32_t db_timestamp; + uint32_t disabled; + uint32_t parent_id; +}; + +struct directory_enum { + int parent_id; + + /* Private enum context, keep out */ + sqlite3_stmt *stmt; +}; + char * db_escape_string(const char *str); void free_pi(struct pairing_info *pi, int content_only); -void -free_fi(struct filelist_info *fi, int content_only); - void free_mfi(struct media_file_info *mfi, int content_only); @@ -371,6 +386,9 @@ unicode_fixup_mfi(struct media_file_info *mfi); void free_pli(struct playlist_info *pli, int content_only); +void +free_di(struct directory_info *di, int content_only); + /* Maintenance and DB hygiene */ void db_hook_post_scan(void); @@ -495,6 +513,9 @@ db_file_disable_bymatch(char *path, char *strip, uint32_t cookie); int db_file_enable_bycookie(uint32_t cookie, char *path); +int +db_file_update_directoryid(char *path, int dir_id); + /* Playlists */ int db_pl_get_count(void); @@ -551,12 +572,34 @@ db_groups_clear(void); int db_group_persistentid_byid(int id, int64_t *persistentid); -/* Filelist */ + +/* Directories */ int -db_mpd_start_query_filelist(struct query_params *qp, char *path); +db_directory_id_byvirtualpath(char *virtual_path); int -db_mpd_query_fetch_filelist(struct query_params *qp, struct filelist_info *fi); +db_directory_enum_start(struct directory_enum *de); + +int +db_directory_enum_fetch(struct directory_enum *de, struct directory_info *di); + +void +db_directory_enum_end(struct directory_enum *de); + +int +db_directory_addorupdate(char *virtual_path, int disabled, int parent_id); + +void +db_directory_ping_bymatch(char *path); + +void +db_directory_disable_bymatch(char *path, char *strip, uint32_t cookie); + +int +db_directory_enable_bycookie(uint32_t cookie, char *path); + +int +db_directory_enable_bypath(char *path); /* Remotes */ int diff --git a/src/db_upgrade.c b/src/db_upgrade.c new file mode 100644 index 00000000..f3b3b2fe --- /dev/null +++ b/src/db_upgrade.c @@ -0,0 +1,1563 @@ +/* + * Copyright (C) 2015 Christian Meffert + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logger.h" +#include "misc.h" + + +struct db_upgrade_query { + char *query; + char *desc; +}; + +static int +db_drop_indices(sqlite3 *hdl) +{ +#define Q_INDEX "SELECT name FROM sqlite_master WHERE type == 'index' AND name LIKE 'idx_%';" +#define Q_TMPL "DROP INDEX %q;" + sqlite3_stmt *stmt; + char *errmsg; + char *query; + char *index[256]; + int ret; + int i; + int n; + + DPRINTF(E_DBG, L_DB, "Running query '%s'\n", Q_INDEX); + + ret = sqlite3_prepare_v2(hdl, Q_INDEX, strlen(Q_INDEX) + 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) + { + index[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, index[i]); + free(index[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_INDEX +} + + +static int +db_generic_upgrade(sqlite3 *hdl, const struct db_upgrade_query *queries, int nqueries) +{ + char *errmsg; + int i; + int ret; + + for (i = 0; i < nqueries; i++, queries++) + { + DPRINTF(E_DBG, L_DB, "DB upgrade query: %s\n", queries->desc); + + ret = sqlite3_exec(hdl, queries->query, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) + { + DPRINTF(E_FATAL, L_DB, "DB upgrade error: %s\n", errmsg); + + sqlite3_free(errmsg); + return -1; + } + } + + return 0; +} + +/* Upgrade the files table to the new schema by dumping and reloading the + * table. A bit tedious. + */ +static int +db_upgrade_files_table(sqlite3 *hdl, const char *dumpquery, 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); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); + + 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_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 = (uint64_t *)malloc(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); + + 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; + } + + ret = sqlite3_step(stmt); + if (ret != SQLITE_ROW) + { + DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); + + sqlite3_finalize(stmt); + goto out_free_ids; + } + + 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';"; + + 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); + + 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_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) + */ + +#define U_V18_PL_TYPE_CHANGE_PLAIN \ + "UPDATE playlists SET type = 3 WHERE type = 0;" +#define U_V18_PL_TYPE_CHANGE_SPECIAL \ + "UPDATE playlists SET type = 0 WHERE type = 2;" +#define U_V18_DROP_VIEW_FILELIST \ + "DROP VIEW IF EXISTS filelist;" +#define U_V18_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 IN (2, 3)" \ + ";" + +#define U_V18_SCVER_MAJOR \ + "UPDATE admin SET value = '18' WHERE key = 'schema_version_major';" +#define U_V18_SCVER_MINOR \ + "UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';" + +static const struct db_upgrade_query db_upgrade_v18_queries[] = + { + { U_V18_PL_TYPE_CHANGE_PLAIN, "changing numbering of plain playlists 0 -> 3" }, + { U_V18_PL_TYPE_CHANGE_SPECIAL, "changing numbering of default playlists 2 -> 0" }, + { U_V18_DROP_VIEW_FILELIST, "dropping view filelist" }, + { U_V18_CREATE_VIEW_FILELIST, "creating view filelist" }, + + { U_V18_SCVER_MAJOR, "set schema_version_major to 18" }, + { U_V18_SCVER_MINOR, "set schema_version_minor to 00" }, + }; + +/* Upgrade from schema v18.00 to v18.01 */ +/* Change virtual_path for playlists: remove file extension + */ + +#define U_V1801_UPDATE_PLAYLISTS_M3U \ + "UPDATE playlists SET virtual_path = replace(virtual_path, '.m3u', '');" +#define U_V1801_UPDATE_PLAYLISTS_PLS \ + "UPDATE playlists SET virtual_path = replace(virtual_path, '.pls', '');" +#define U_V1801_UPDATE_PLAYLISTS_SMARTPL \ + "UPDATE playlists SET virtual_path = replace(virtual_path, '.smartpl', '');" + +#define U_V1801_SCVER_MAJOR \ + "UPDATE admin SET value = '18' WHERE key = 'schema_version_major';" +#define U_V1801_SCVER_MINOR \ + "UPDATE admin SET value = '01' WHERE key = 'schema_version_minor';" + +static const struct db_upgrade_query db_upgrade_v1801_queries[] = + { + { U_V1801_UPDATE_PLAYLISTS_M3U, "update table playlists" }, + { U_V1801_UPDATE_PLAYLISTS_PLS, "update table playlists" }, + { U_V1801_UPDATE_PLAYLISTS_SMARTPL, "update table playlists" }, + + { U_V1801_SCVER_MAJOR, "set schema_version_major to 18" }, + { U_V1801_SCVER_MINOR, "set schema_version_minor to 01" }, + }; + +/* Upgrade from schema v18.01 to v19.00 */ +/* Replace 'filelist' view with new table 'directories' + */ + +#define U_V1900_CREATE_TABLE_DIRECTORIES \ + "CREATE TABLE IF NOT EXISTS directories (" \ + " id INTEGER PRIMARY KEY NOT NULL," \ + " virtual_path VARCHAR(4096) NOT NULL," \ + " db_timestamp INTEGER DEFAULT 0," \ + " disabled INTEGER DEFAULT 0," \ + " parent_id INTEGER DEFAULT 0" \ + ");" + +#define U_V1900_DROP_VIEW_FILELIST \ + "DROP VIEW IF EXISTS filelist;" +#define U_V1900_ALTER_PL_ADD_DIRECTORYID \ + "ALTER TABLE playlists ADD COLUMN directory_id INTEGER DEFAULT 0;" +#define U_V1900_ALTER_FILES_ADD_DIRECTORYID \ + "ALTER TABLE files ADD COLUMN directory_id INTEGER DEFAULT 0;" +#define U_V1900_ALTER_FILES_ADD_DATERELEASED \ + "ALTER TABLE files ADD COLUMN date_released INTEGER DEFAULT 0;" +#define U_V1900_ALTER_SPEAKERS_ADD_NAME \ + "ALTER TABLE speakers ADD COLUMN name VARCHAR(255) DEFAULT NULL;" + +#define U_V1900_INSERT_DIR1 \ + "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (1, '/', 0, 0, 0);" +#define U_V1900_INSERT_DIR2 \ + "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (2, '/file:', 0, 0, 1);" +#define U_V1900_INSERT_DIR3 \ + "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (3, '/http:', 0, 0, 1);" +#define U_V1900_INSERT_DIR4 \ + "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \ + " VALUES (4, '/spotify:', 0, 4294967296, 1);" + +#define U_V1900_SCVER_MAJOR \ + "UPDATE admin SET value = '19' WHERE key = 'schema_version_major';" +#define U_V1900_SCVER_MINOR \ + "UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';" + +static const struct db_upgrade_query db_upgrade_v1900_queries[] = + { + { U_V1900_CREATE_TABLE_DIRECTORIES, "create table directories" }, + { U_V1900_ALTER_PL_ADD_DIRECTORYID, "alter table pl add column directory_id" }, + { U_V1900_ALTER_FILES_ADD_DIRECTORYID, "alter table files add column directory_id" }, + { U_V1900_ALTER_FILES_ADD_DATERELEASED,"alter table files add column date_released" }, + { U_V1900_ALTER_SPEAKERS_ADD_NAME, "alter table speakers add column name" }, + { U_V1900_INSERT_DIR1, "insert root directory" }, + { U_V1900_INSERT_DIR2, "insert /file: directory" }, + { U_V1900_INSERT_DIR3, "insert /htttp: directory" }, + { U_V1900_INSERT_DIR4, "insert /spotify: directory" }, + { U_V1900_DROP_VIEW_FILELIST, "drop view directories" }, + + { U_V1900_SCVER_MAJOR, "set schema_version_major to 19" }, + { U_V1900_SCVER_MINOR, "set schema_version_minor to 00" }, + }; + +int +db_upgrade_v19_directory_id(sqlite3 *hdl, char *virtual_path) +{ + sqlite3_stmt *stmt; + char *query; + int id; + int ret; + + query = sqlite3_mprintf("SELECT d.id FROM directories d WHERE d.disabled = 0 AND d.virtual_path = '%q';", virtual_path); + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + + return -1; + } + + ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_DB, "Error preparing query '%s'\n", query); + sqlite3_free(query); + + return -1; + } + + ret = sqlite3_step(stmt); + + if (ret == SQLITE_ROW) + id = sqlite3_column_int(stmt, 0); + else if (ret == SQLITE_DONE) + id = 0; // Not found + else + { + DPRINTF(E_LOG, L_DB, "Error stepping query '%s'\n", query); + sqlite3_free(query); + sqlite3_finalize(stmt); + + return -1; + } + + sqlite3_free(query); + sqlite3_finalize(stmt); + + return id; +} + +int +db_upgrade_v19_insert_directory(sqlite3 *hdl, char *virtual_path, int parent_id) +{ + char *query; + char *errmsg; + int id; + int ret; + + query = sqlite3_mprintf( + "INSERT INTO directories (virtual_path, db_timestamp, disabled, parent_id) VALUES (TRIM(%Q), %d, %d, %d);", + virtual_path, (uint64_t)time(NULL), 0, parent_id); + + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + return -1; + } + + ret = sqlite3_exec(hdl, query, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg); + + sqlite3_free(errmsg); + sqlite3_free(query); + return -1; + } + + sqlite3_free(query); + + id = (int)sqlite3_last_insert_rowid(hdl); + + DPRINTF(E_DBG, L_DB, "Added directory %s with id %d\n", virtual_path, id); + + return id; +} + +static int +db_upgrade_v19_insert_parent_directories(sqlite3 *hdl, char *virtual_path) +{ + char *ptr; + int dir_id; + int parent_id; + char buf[PATH_MAX]; + + // The root directoy ID + parent_id = 1; + + ptr = virtual_path + 1; // Skip first '/' + while (ptr && (ptr = strchr(ptr, '/'))) + { + strncpy(buf, virtual_path, (ptr - virtual_path)); + buf[(ptr - virtual_path)] = '\0'; + + dir_id = db_upgrade_v19_directory_id(hdl, buf); + + if (dir_id < 0) + { + DPRINTF(E_LOG, L_SCAN, "Select of directory failed '%s'\n", buf); + + return -1; + } + else if (dir_id == 0) + { + dir_id = db_upgrade_v19_insert_directory(hdl, buf, parent_id); + if (dir_id < 0) + { + DPRINTF(E_LOG, L_SCAN, "Insert of directory failed '%s'\n", buf); + + return -1; + } + } + + parent_id = dir_id; + ptr++; + } + + return parent_id; +} + +static int +db_upgrade_v19(sqlite3 *hdl) +{ + sqlite3_stmt *stmt; + char *query; + char *uquery; + char *errmsg; + int id; + char *virtual_path; + int dir_id; + int ret; + + query = "SELECT id, virtual_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); + virtual_path = (char *)sqlite3_column_text(stmt, 1); + + dir_id = db_upgrade_v19_insert_parent_directories(hdl, virtual_path); + if (dir_id < 0) + { + DPRINTF(E_LOG, L_DB, "Error processing parent directories for file: %s\n", virtual_path); + } + else + { + uquery = sqlite3_mprintf("UPDATE files SET directory_id = %d WHERE id = %d;", dir_id, 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, virtual_path FROM playlists WHERE type = 2 OR type = 3;"; //Only update normal and smart 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); + virtual_path = (char *)sqlite3_column_text(stmt, 1); + + dir_id = db_upgrade_v19_insert_parent_directories(hdl, virtual_path); + if (dir_id < 0) + { + DPRINTF(E_LOG, L_DB, "Error processing parent directories for file: %s\n", virtual_path); + } + else + { + uquery = sqlite3_mprintf("UPDATE files SET directory_id = %d WHERE id = %d;", dir_id, 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); + + return 0; +} + +int +db_upgrade(sqlite3 *hdl, int db_ver) +{ + int ret; + + ret = db_drop_indices(hdl); + if (ret < 0) + return -1; + + switch (db_ver) + { + case 1000: + ret = db_generic_upgrade(hdl, db_upgrade_v11_queries, sizeof(db_upgrade_v11_queries) / sizeof(db_upgrade_v11_queries[0])); + 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, sizeof(db_upgrade_v12_queries) / sizeof(db_upgrade_v12_queries[0])); + if (ret < 0) + return -1; + + /* FALLTHROUGH */ + + case 1200: + ret = db_generic_upgrade(hdl, db_upgrade_v13_queries, sizeof(db_upgrade_v13_queries) / sizeof(db_upgrade_v13_queries[0])); + 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, sizeof(db_upgrade_v14_queries) / sizeof(db_upgrade_v14_queries[0])); + 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, sizeof(db_upgrade_v15_queries) / sizeof(db_upgrade_v15_queries[0])); + if (ret < 0) + return -1; + + /* FALLTHROUGH */ + + case 1500: + ret = db_generic_upgrade(hdl, db_upgrade_v1501_queries, sizeof(db_upgrade_v1501_queries) / sizeof(db_upgrade_v1501_queries[0])); + if (ret < 0) + return -1; + + /* FALLTHROUGH */ + + case 1501: + ret = db_generic_upgrade(hdl, db_upgrade_v16_queries, sizeof(db_upgrade_v16_queries) / sizeof(db_upgrade_v16_queries[0])); + 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, sizeof(db_upgrade_v17_queries) / sizeof(db_upgrade_v17_queries[0])); + if (ret < 0) + return -1; + + /* FALLTHROUGH */ + + case 1700: + ret = db_generic_upgrade(hdl, db_upgrade_v18_queries, sizeof(db_upgrade_v18_queries) / sizeof(db_upgrade_v18_queries[0])); + if (ret < 0) + return -1; + + /* FALLTHROUGH */ + + case 1800: + ret = db_generic_upgrade(hdl, db_upgrade_v1801_queries, sizeof(db_upgrade_v1801_queries) / sizeof(db_upgrade_v1801_queries[0])); + if (ret < 0) + return -1; + + /* FALLTHROUGH */ + + case 1801: + ret = db_generic_upgrade(hdl, db_upgrade_v1900_queries, sizeof(db_upgrade_v1900_queries) / sizeof(db_upgrade_v1900_queries[0])); + if (ret < 0) + return -1; + + ret = db_upgrade_v19(hdl); + if (ret < 0) + return -1; + + break; + + default: + DPRINTF(E_FATAL, L_DB, "No upgrade path from the current DB schema\n"); + return -1; + } + + return 0; +} diff --git a/src/db_upgrade.h b/src/db_upgrade.h new file mode 100644 index 00000000..e75a4f14 --- /dev/null +++ b/src/db_upgrade.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 Christian Meffert + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SRC_DB_UPGRADE_H_ +#define SRC_DB_UPGRADE_H_ + +#include + +int +db_upgrade(sqlite3 *hdl, int db_ver); + +#endif /* SRC_DB_UPGRADE_H_ */ diff --git a/src/filescanner.c b/src/filescanner.c index 7e500ac7..fc71d2c1 100644 --- a/src/filescanner.c +++ b/src/filescanner.c @@ -23,6 +23,7 @@ # include #endif +#include #include #include #include @@ -112,14 +113,15 @@ struct deferred_pl { char *path; time_t mtime; struct deferred_pl *next; + int directory_id; }; struct stacked_dir { char *path; + int parent_id; struct stacked_dir *next; }; - static int cmd_pipe[2]; static int exit_pipe[2]; static int scan_exit; @@ -197,7 +199,7 @@ nonblock_command(struct filescanner_command *cmd) } static int -push_dir(struct stacked_dir **s, char *path) +push_dir(struct stacked_dir **s, char *path, int parent_id) { struct stacked_dir *d; @@ -215,28 +217,26 @@ push_dir(struct stacked_dir **s, char *path) return -1; } + d->parent_id = parent_id; + d->next = *s; *s = d; return 0; } -static char * +static struct stacked_dir * pop_dir(struct stacked_dir **s) { struct stacked_dir *d; - char *ret; if (!*s) return NULL; d = *s; *s = d->next; - ret = d->path; - free(d); - - return ret; + return d; } #ifdef HAVE_REGEX_H @@ -633,7 +633,7 @@ fixup_tags(struct media_file_info *mfi) void -filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi) +filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi, int dir_id) { struct media_file_info *mfi; char *filename; @@ -765,6 +765,8 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct mfi->virtual_path = strdup(virtual_path); } + mfi->directory_id = dir_id; + if (mfi->id == 0) db_file_add(mfi); else @@ -776,13 +778,13 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct } static void -process_playlist(char *file, time_t mtime) +process_playlist(char *file, time_t mtime, int dir_id) { enum file_type ft; ft = file_type_get(file); if (ft == FILE_PLAYLIST) - scan_playlist(file, mtime); + scan_playlist(file, mtime, dir_id); #ifdef ITUNES else if (ft == FILE_ITUNES) scan_itunes_itml(file); @@ -791,7 +793,7 @@ process_playlist(char *file, time_t mtime) /* Thread: scan */ static void -defer_playlist(char *path, time_t mtime) +defer_playlist(char *path, time_t mtime, int dir_id) { struct deferred_pl *pl; @@ -815,6 +817,7 @@ defer_playlist(char *path, time_t mtime) } pl->mtime = mtime; + pl->directory_id = dir_id; pl->next = playlists; playlists = pl; @@ -831,7 +834,7 @@ process_deferred_playlists(void) { playlists = pl->next; - process_playlist(pl->path, pl->mtime); + process_playlist(pl->path, pl->mtime, pl->directory_id); free(pl->path); free(pl); @@ -843,7 +846,7 @@ process_deferred_playlists(void) /* Thread: scan */ static void -process_file(char *file, time_t mtime, off_t size, int type, int flags) +process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_id) { int is_bulkscan; @@ -852,7 +855,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags) switch (file_type_get(file)) { case FILE_REGULAR: - filescanner_process_media(file, mtime, size, type, NULL); + filescanner_process_media(file, mtime, size, type, NULL, dir_id); cache_artwork_ping(file, mtime, !is_bulkscan); // TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork @@ -871,14 +874,14 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags) case FILE_PLAYLIST: case FILE_ITUNES: if (flags & F_SCAN_BULK) - defer_playlist(file, mtime); + defer_playlist(file, mtime, dir_id); else - process_playlist(file, mtime); + process_playlist(file, mtime, dir_id); break; case FILE_SMARTPL: DPRINTF(E_DBG, L_SCAN, "Smart playlist file: %s\n", file); - scan_smartpl(file, mtime); + scan_smartpl(file, mtime, dir_id); break; case FILE_ARTWORK: @@ -948,8 +951,22 @@ check_speciallib(char *path, const char *libtype) } /* Thread: scan */ +static int +create_virtual_path(char *path, char *virtual_path, int virtual_path_len) +{ + int ret; + ret = snprintf(virtual_path, virtual_path_len, "/file:%s", path); + if ((ret < 0) || (ret >= virtual_path_len)) + { + DPRINTF(E_LOG, L_SCAN, "Virtual path /file:%s, PATH_MAX exceeded\n", path); + return -1; + } + + return 0; +} + static void -process_directory(char *path, int flags) +process_directory(char *path, int flags, int parent_id) { DIR *dirp; struct dirent buf; @@ -962,6 +979,8 @@ process_directory(char *path, int flags) struct kevent kev; #endif int type; + char virtual_path[PATH_MAX]; + int dir_id; int ret; DPRINTF(E_DBG, L_SCAN, "Processing directory %s (flags = 0x%x)\n", path, flags); @@ -974,6 +993,18 @@ process_directory(char *path, int flags) return; } + /* Add/update directories table */ + + ret = create_virtual_path(path, virtual_path, sizeof(virtual_path)); + if (ret < 0) + return; + + dir_id = db_directory_addorupdate(virtual_path, 0, parent_id); + if (dir_id <= 0) + { + DPRINTF(E_LOG, L_SCAN, "Insert or update of directory failed '%s'\n", virtual_path); + } + /* Check if compilation and/or podcast directory */ type = 0; if (check_speciallib(path, "compilations")) @@ -1050,15 +1081,15 @@ process_directory(char *path, int flags) if (S_ISREG(sb.st_mode)) { if (!(flags & F_SCAN_FAST)) - process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, flags); + process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, flags, dir_id); } else if (S_ISFIFO(sb.st_mode)) { if (!(flags & F_SCAN_FAST)) - process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, flags); + process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, flags, dir_id); } else if (S_ISDIR(sb.st_mode)) - push_dir(&dirstack, entry); + push_dir(&dirstack, entry, dir_id); else DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry); } @@ -1116,21 +1147,58 @@ process_directory(char *path, int flags) } /* Thread: scan */ -static void -process_directories(char *root, int flags) -{ - char *path; - process_directory(root, flags); +static int +process_parent_directories(char *path) +{ + char *ptr; + int dir_id; + char buf[PATH_MAX]; + char virtual_path[PATH_MAX]; + int ret; + + dir_id = DIR_FILE; + + ptr = path + 1; + while (ptr && (ptr = strchr(ptr, '/'))) + { + strncpy(buf, path, (ptr - path)); + buf[(ptr - path)] = '\0'; + + ret = create_virtual_path(buf, virtual_path, sizeof(virtual_path)); + if (ret < 0) + return 0; + + dir_id = db_directory_addorupdate(virtual_path, 0, dir_id); + if (dir_id <= 0) + { + DPRINTF(E_LOG, L_SCAN, "Insert or update of directory failed '%s'\n", virtual_path); + + return 0; + } + + ptr++; + } + + return dir_id; +} + +static void +process_directories(char *root, int parent_id, int flags) +{ + struct stacked_dir *dir; + + process_directory(root, flags, parent_id); if (scan_exit) return; - while ((path = pop_dir(&dirstack))) + while ((dir = pop_dir(&dirstack))) { - process_directory(path, flags); + process_directory(dir->path, flags, dir->parent_id); - free(path); + free(dir->path); + free(dir); if (scan_exit) return; @@ -1148,6 +1216,7 @@ bulk_scan(int flags) char *deref; time_t start; time_t end; + int parent_id; int i; // Set global flag to avoid queued scan requests @@ -1165,6 +1234,8 @@ bulk_scan(int flags) { path = cfg_getnstr(lib, "directories", i); + parent_id = process_parent_directories(path); + deref = m_realpath(path); if (!deref) { @@ -1173,16 +1244,19 @@ bulk_scan(int flags) /* Assume dir is mistakenly not mounted, so just disable everything and update timestamps */ db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); + db_directory_disable_bymatch(path, "", 0); db_file_ping_bymatch(path, 1); db_pl_ping_bymatch(path, 1); + db_directory_ping_bymatch(path); continue; } counter = 0; db_transaction_begin(); - process_directories(deref, flags); + + process_directories(deref, parent_id, flags); db_transaction_end(); free(deref); @@ -1306,6 +1380,28 @@ filescanner(void *arg) pthread_exit(NULL); } +static int +get_parent_dir_id(const char *path) +{ + char *pathcopy; + char *parent_dir; + char virtual_path[PATH_MAX]; + int parent_id; + int ret; + + pathcopy = strdup(path); + parent_dir = dirname(pathcopy); + ret = create_virtual_path(parent_dir, virtual_path, sizeof(virtual_path)); + if (ret == 0) + parent_id = db_directory_id_byvirtualpath(virtual_path); + else + parent_id = 0; + + free(pathcopy); + + return parent_id; +} + #if defined(__linux__) static int @@ -1347,6 +1443,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) char *s; int flags = 0; int ret; + int parent_id; DPRINTF(E_SPAM, L_SCAN, "Directory event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); @@ -1354,6 +1451,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) { db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); + db_directory_disable_bymatch(path, "", 0); } if (ie->mask & IN_MOVE_SELF) @@ -1408,6 +1506,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) db_watch_mark_bymatch(path, path, ie->cookie); db_file_disable_bymatch(path, path, ie->cookie); db_pl_disable_bymatch(path, path, ie->cookie); + db_directory_disable_bymatch(path, path, ie->cookie); } if (ie->mask & IN_MOVED_TO) @@ -1417,6 +1516,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) db_watch_move_bycookie(ie->cookie, path); db_file_enable_bycookie(ie->cookie, path); db_pl_enable_bycookie(ie->cookie, path); + db_directory_enable_bycookie(ie->cookie, path); /* We'll rescan the directory tree to update playlists */ flags |= F_SCAN_MOVED; @@ -1450,6 +1550,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); + db_directory_disable_bymatch(path, "", 0); } else if (ret < 0) { @@ -1465,7 +1566,8 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) if (ie->mask & IN_CREATE) { - process_directories(path, flags); + parent_id = get_parent_dir_id(path); + process_directories(path, parent_id, flags); if (dirstack) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); @@ -1480,8 +1582,12 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie uint32_t path_hash; char *deref = NULL; char *file = path; + char *dir; + char dir_vpath[PATH_MAX]; int type; int i; + int dir_id; + char *ptr; int ret; DPRINTF(E_SPAM, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); @@ -1541,7 +1647,28 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie ret = db_file_enable_bycookie(ie->cookie, path); - if (ret <= 0) + if (ret > 0) + { + // If file was successfully enabled, update the directory id + dir = strdup(path); + ptr = strrchr(dir, '/'); + dir[(ptr - dir)] = '\0'; + + ret = create_virtual_path(dir, dir_vpath, sizeof(dir_vpath)); + if (ret >= 0) + { + dir_id = db_directory_id_byvirtualpath(dir_vpath); + if (dir_id > 0) + { + ret = db_file_update_directoryid(path, dir_id); + if (ret < 0) + DPRINTF(E_LOG, L_SCAN, "Error updating directory id for file: %s\n", path); + } + } + + free(dir); + } + else { /* It's not a known media file, so it's either a new file * or a playlist, known or not. @@ -1636,10 +1763,14 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie if (check_speciallib(path, "audiobooks")) type |= F_SCAN_TYPE_AUDIOBOOK; + dir_id = get_parent_dir_id(file); + if (S_ISREG(sb.st_mode)) - process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, 0); + { + process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, 0, dir_id); + } else if (S_ISFIFO(sb.st_mode)) - process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, 0); + process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, 0, dir_id); if (deref) free(deref); @@ -1777,6 +1908,7 @@ kqueue_cb(int fd, short event, void *arg) int w_len; int need_rescan; int ret; + int parent_id; ts.tv_sec = 0; ts.tv_nsec = 0; @@ -1839,6 +1971,7 @@ kqueue_cb(int fd, short event, void *arg) /* Disable files */ db_file_disable_bymatch(wi.path, "", 0); db_pl_disable_bymatch(wi.path, "", 0); + db_directory_disable_bymatch(wi.path, "", 0); if (kev.flags & EV_ERROR) { @@ -1921,17 +2054,21 @@ kqueue_cb(int fd, short event, void *arg) } if (need_rescan) - push_dir(&rescan, wi.path); + { + parent_id = get_parent_dir_id(wi.path); + push_dir(&rescan, wi.path, parent_id); + } } free(wi.path); } - while ((path = pop_dir(&rescan))) + while ((d = pop_dir(&rescan))) { - process_directories(path, 0); + process_directories(d->path, 0, d->parent_id); - free(path); + free(d->path); + free(d); if (rescan) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); diff --git a/src/filescanner.h b/src/filescanner.h index f9136a86..915f99b0 100644 --- a/src/filescanner.h +++ b/src/filescanner.h @@ -19,7 +19,7 @@ void filescanner_deinit(void); void -filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi); +filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi, int dir_id); /* Actual scanners */ int @@ -29,10 +29,10 @@ int scan_metadata_icy(char *url, struct media_file_info *mfi); void -scan_playlist(char *file, time_t mtime); +scan_playlist(char *file, time_t mtime, int dir_id); void -scan_smartpl(char *file, time_t mtime); +scan_smartpl(char *file, time_t mtime, int dir_id); #ifdef ITUNES void diff --git a/src/filescanner_playlist.c b/src/filescanner_playlist.c index 605cc3c6..d393e32b 100644 --- a/src/filescanner_playlist.c +++ b/src/filescanner_playlist.c @@ -74,7 +74,7 @@ extinf_get(char *string, struct media_file_info *mfi, int *extinf) } void -scan_playlist(char *file, time_t mtime) +scan_playlist(char *file, time_t mtime, int dir_id) { FILE *fp; struct media_file_info mfi; @@ -172,6 +172,8 @@ scan_playlist(char *file, time_t mtime) *ptr = '\0'; pli->virtual_path = strdup(virtual_path); + pli->directory_id = dir_id; + ret = db_pl_add(pli, &pl_id); if (ret < 0) { @@ -236,7 +238,7 @@ scan_playlist(char *file, time_t mtime) if (extinf) DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi.artist, mfi.title); - filescanner_process_media(filename, mtime, 0, F_SCAN_TYPE_URL, &mfi); + filescanner_process_media(filename, mtime, 0, F_SCAN_TYPE_URL, &mfi, DIR_HTTP); } /* Regular file, should already be in library */ else diff --git a/src/filescanner_smartpl.c b/src/filescanner_smartpl.c index 53bb60eb..5c55ef4e 100644 --- a/src/filescanner_smartpl.c +++ b/src/filescanner_smartpl.c @@ -171,7 +171,7 @@ smartpl_parse_file(const char *file, struct playlist_info *pli) } void -scan_smartpl(char *file, time_t mtime) +scan_smartpl(char *file, time_t mtime, int dir_id) { struct playlist_info *pli; int pl_id; @@ -203,6 +203,8 @@ scan_smartpl(char *file, time_t mtime) else pl_id = pli->id; + pli->directory_id = dir_id; + ret = smartpl_parse_file(file, pli); if (ret < 0) { diff --git a/src/mpd.c b/src/mpd.c index d22920b5..83c38591 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -1557,13 +1557,13 @@ mpd_queueitem_make(char *path, int recursive) if (recursive) { - qp.filter = sqlite3_mprintf("f.virtual_path LIKE '/%q%%'", path); + qp.filter = sqlite3_mprintf("f.disabled = 0 AND f.virtual_path LIKE '/%q%%'", path); if (!qp.filter) DPRINTF(E_DBG, L_PLAYER, "Out of memory\n"); } else { - qp.filter = sqlite3_mprintf("f.virtual_path LIKE '/%q'", path); + qp.filter = sqlite3_mprintf("f.disabled = 0 AND f.virtual_path LIKE '/%q'", path); if (!qp.filter) DPRINTF(E_DBG, L_PLAYER, "Out of memory\n"); } @@ -2685,6 +2685,214 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return 0; } +static int +mpd_add_directory(struct evbuffer *evbuf, int directory_id, int listall, int listinfo, char **errmsg) +{ + struct directory_info subdir; + struct query_params qp; + struct directory_enum dir_enum; + struct db_playlist_info dbpli; + char modified[32]; + uint32_t time_modified; + struct db_media_file_info dbmfi; + int ret; + + // Load playlists for dir-id + memset(&qp, 0, sizeof(struct query_params)); + qp.type = Q_PL; + qp.sort = S_PLAYLIST; + qp.idx_type = I_NONE; + qp.filter = sqlite3_mprintf("(f.directory_id = %d AND (f.type = %d OR f.type = %d))", directory_id, PL_PLAIN, PL_SMART); + ret = db_query_start(&qp); + if (ret < 0) + { + db_query_end(&qp); + ret = asprintf(errmsg, "Could not start query"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_UNKNOWN; + } + while (((ret = db_query_fetch_pl(&qp, &dbpli)) == 0) && (dbpli.id)) + { + if (safe_atou32(dbpli.db_timestamp, &time_modified) != 0) + { + DPRINTF(E_LOG, L_MPD, "Error converting time modified to uint32_t: %s\n", dbpli.db_timestamp); + return -1; + } + + if (listinfo) + { + mpd_time(modified, sizeof(modified), time_modified); + evbuffer_add_printf(evbuf, + "playlist: %s\n" + "Last-Modified: %s\n", + (dbpli.virtual_path + 1), + modified); + } + else + { + evbuffer_add_printf(evbuf, + "playlist: %s\n", + (dbpli.virtual_path + 1)); + } + } + db_query_end(&qp); + sqlite3_free(qp.filter); + + // Load sub directories for dir-id + memset(&dir_enum, 0, sizeof(struct directory_enum)); + dir_enum.parent_id = directory_id; + ret = db_directory_enum_start(&dir_enum); + if (ret < 0) + { + DPRINTF(E_LOG, L_MPD, "Failed to start directory enum for parent_id %d\n", directory_id); + return -1; + } + while ((ret = db_directory_enum_fetch(&dir_enum, &subdir)) == 0 && subdir.id > 0) + { + if (listinfo) + { + evbuffer_add_printf(evbuf, + "directory: %s\n" + "Last-Modified: %s\n", + (subdir.virtual_path + 1), + "2015-12-01 00:00"); + } + else + { + evbuffer_add_printf(evbuf, + "directory: %s\n", + (subdir.virtual_path + 1)); + } + + if (listall) + { + mpd_add_directory(evbuf, subdir.id, listall, listinfo, errmsg); + } + } + db_directory_enum_end(&dir_enum); + + // Load files for dir-id + memset(&qp, 0, sizeof(struct query_params)); + qp.type = Q_ITEMS; + qp.sort = S_ARTIST; + qp.idx_type = I_NONE; + qp.filter = sqlite3_mprintf("(f.directory_id = %d)", directory_id); + ret = db_query_start(&qp); + if (ret < 0) + { + db_query_end(&qp); + ret = asprintf(errmsg, "Could not start query"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_UNKNOWN; + } + while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id)) + { + if (listinfo) + { + ret = mpd_add_db_media_file_info(evbuf, &dbmfi); + if (ret < 0) + { + DPRINTF(E_LOG, L_MPD, "Error adding song to the evbuffer, song id: %s\n", dbmfi.id); + } + } + else + { + evbuffer_add_printf(evbuf, + "file: %s\n", + (dbmfi.virtual_path + 1)); + } + } + db_query_end(&qp); + + return 0; +} + +static int +mpd_command_listall(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) +{ + int dir_id; + char parent[PATH_MAX]; + int ret; + + if (argc < 2 || strlen(argv[1]) == 0 + || (strncmp(argv[1], "/", 1) == 0 && strlen(argv[1]) == 1)) + { + ret = snprintf(parent, sizeof(parent), "/"); + } + else if (strncmp(argv[1], "/", 1) == 0) + { + ret = snprintf(parent, sizeof(parent), "%s/", argv[1]); + } + else + { + ret = snprintf(parent, sizeof(parent), "/%s", argv[1]); + } + + if ((ret < 0) || (ret >= sizeof(parent))) + { + DPRINTF(E_INFO, L_MPD, "Parent path exceeds PATH_MAX\n"); + return -1; + } + + // Load dir-id from db for parent-path + dir_id = db_directory_id_byvirtualpath(parent); + if (dir_id == 0) + { + DPRINTF(E_LOG, L_MPD, "Directory info not found for virtual-path '%s'\n", parent); + return -1; + } + + ret = mpd_add_directory(evbuf, dir_id, 1, 0, errmsg); + + return ret; +} + +static int +mpd_command_listallinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) +{ + int dir_id; + char parent[PATH_MAX]; + int ret; + + if (argc < 2 || strlen(argv[1]) == 0 + || (strncmp(argv[1], "/", 1) == 0 && strlen(argv[1]) == 1)) + { + ret = snprintf(parent, sizeof(parent), "/"); + } + else if (strncmp(argv[1], "/", 1) == 0) + { + ret = snprintf(parent, sizeof(parent), "%s/", argv[1]); + } + else + { + ret = snprintf(parent, sizeof(parent), "/%s", argv[1]); + } + + if ((ret < 0) || (ret >= sizeof(parent))) + { + ret = asprintf(errmsg, "Parent path exceeds PATH_MAX\n"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return -1; + } + + // Load dir-id from db for parent-path + dir_id = db_directory_id_byvirtualpath(parent); + if (dir_id == 0) + { + ret = asprintf(errmsg, "Directory info not found for virtual-path '%s'\n", parent); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return -1; + } + + ret = mpd_add_directory(evbuf, dir_id, 1, 1, errmsg); + + return ret; +} + /* * Command handler function for 'lsinfo' * Lists the contents of the directory given in argv[1]. @@ -2692,11 +2900,8 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) static int mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct query_params qp; + int dir_id; char parent[PATH_MAX]; - struct filelist_info *fi; - struct media_file_info *mfi; - char modified[32]; int print_playlists; int ret; @@ -2711,7 +2916,7 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } else { - ret = snprintf(parent, sizeof(parent), "/%s/", argv[1]); + ret = snprintf(parent, sizeof(parent), "/%s", argv[1]); } if ((ret < 0) || (ret >= sizeof(parent))) @@ -2731,76 +2936,24 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) print_playlists = 1; } - fi = (struct filelist_info*)malloc(sizeof(struct filelist_info)); - if (!fi) + + // Load dir-id from db for parent-path + dir_id = db_directory_id_byvirtualpath(parent); + if (dir_id == 0) { - DPRINTF(E_LOG, L_MPD, "Out of memory for fi\n"); - return ACK_ERROR_UNKNOWN; + DPRINTF(E_LOG, L_MPD, "Directory info not found for virtual-path '%s'\n", parent); + return -1; } - memset(&qp, 0, sizeof(struct query_params)); + ret = mpd_add_directory(evbuf, dir_id, 0, 1, errmsg); - ret = db_mpd_start_query_filelist(&qp, parent); - if (ret < 0) + // If the root directory was passed as argument add the stored playlists to the response + if (ret == 0 && print_playlists) { - ret = asprintf(errmsg, "Could not start query for path '%s'", argv[1]); - if (ret < 0) - DPRINTF(E_LOG, L_MPD, "Out of memory\n"); - - free_fi(fi, 0); - return ACK_ERROR_UNKNOWN; - } - - while (((ret = db_mpd_query_fetch_filelist(&qp, fi)) == 0) && (fi->virtual_path)) - { - if (fi->type == F_DIR) - { - mpd_time(modified, sizeof(modified), fi->time_modified); - - evbuffer_add_printf(evbuf, - "directory: %s\n" - "Last-Modified: %s\n", - (fi->virtual_path + 1), - modified); - } - else if (fi->type == F_PLAYLIST) - { - mpd_time(modified, sizeof(modified), fi->time_modified); - - evbuffer_add_printf(evbuf, - "playlist: %s\n" - "Last-Modified: %s\n", - (fi->virtual_path + 1), - modified); - } - else if (fi->type == F_FILE) - { - mfi = db_file_fetch_byvirtualpath(fi->virtual_path); - if (mfi) - { - ret = mpd_add_mediainfo(evbuf, mfi, 0, -1); - if (ret < 0) - { - DPRINTF(E_LOG, L_MPD, "Could not add mediainfo for path '%s'\n", fi->virtual_path); - } - - free_mfi(mfi, 0); - } - } - } - - db_query_end(&qp); - - if (fi) - free_fi(fi, 0); - - if (print_playlists) - { - // If the root directory was passed as argument add the stored playlists to the response return mpd_command_listplaylists(evbuf, argc, argv, errmsg); } - return 0; + return ret; } static int @@ -3782,7 +3935,6 @@ static struct command mpd_handlers[] = .mpdcommand = "list", .handler = mpd_command_list }, - /* { .mpdcommand = "listall", .handler = mpd_command_listall @@ -3791,6 +3943,7 @@ static struct command mpd_handlers[] = .mpdcommand = "listallinfo", .handler = mpd_command_listallinfo }, + /* { .mpdcommand = "listfiles", .handler = mpd_command_listfiles @@ -4588,8 +4741,24 @@ int mpd_init(void) sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(port); saddr = (struct sockaddr *)&sin6; + + listener = evconnlistener_new_bind( + evbase_mpd, + mpd_accept_conn_cb, + NULL, + LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + -1, + saddr, + saddr_length); + + if (!listener) + { + DPRINTF(E_LOG, L_MPD, "Could not bind to port %d, falling back to IPv4\n", port); + v6enabled = 0; + } } - else + + if (!v6enabled) { saddr_length = sizeof(struct sockaddr_in); memset(&sin, 0, saddr_length); @@ -4597,23 +4766,24 @@ int mpd_init(void) sin.sin_addr.s_addr = htonl(0); sin.sin_port = htons(port); saddr = (struct sockaddr *)&sin; + + listener = evconnlistener_new_bind( + evbase_mpd, + mpd_accept_conn_cb, + NULL, + LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + -1, + saddr, + saddr_length); + + if (!listener) + { + DPRINTF(E_LOG, L_MPD, "Could not create connection listener for mpd clients on port %d\n", port); + + goto connew_fail; + } } - listener = evconnlistener_new_bind( - evbase_mpd, - mpd_accept_conn_cb, - NULL, - LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, - -1, - saddr, - saddr_length); - - if (!listener) - { - DPRINTF(E_LOG, L_MPD, "Could not create connection listener for mpd clients on port %d\n", port); - - goto connew_fail; - } evconnlistener_set_error_cb(listener, mpd_accept_error_cb); http_port = cfg_getint(cfg_getsec(cfg, "mpd"), "http_port"); diff --git a/src/spotify.c b/src/spotify.c index 5e75b6b3..63be5efd 100644 --- a/src/spotify.c +++ b/src/spotify.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -573,6 +574,9 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde sp_link *link; char url[1024]; int ret; + int dir_id; + char virtual_path[PATH_MAX]; + if (!fptr_sp_track_is_loaded(track)) { @@ -618,7 +622,36 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde return -1; } - filescanner_process_media(url, time(NULL), 0, F_SCAN_TYPE_SPOTIFY, &mfi); + ret = snprintf(virtual_path, sizeof(virtual_path), "/spotify:/%s", mfi.artist); + if ((ret < 0) || (ret >= sizeof(virtual_path))) + { + DPRINTF(E_LOG, L_SPOTIFY, "Virtual path exceeds PATH_MAX (/spotify:/%s)\n", mfi.artist); + free_mfi(&mfi, 1); + return -1; + } + dir_id = db_directory_addorupdate(virtual_path, 0, DIR_SPOTIFY); + if (dir_id <= 0) + { + DPRINTF(E_LOG, L_SPOTIFY, "Could not add or update directory '%s'\n", virtual_path); + free_mfi(&mfi, 1); + return -1; + } + ret = snprintf(virtual_path, sizeof(virtual_path), "/spotify:/%s/%s", mfi.artist, mfi.album); + if ((ret < 0) || (ret >= sizeof(virtual_path))) + { + DPRINTF(E_LOG, L_SPOTIFY, "Virtual path exceeds PATH_MAX (/spotify:/%s/%s)\n", mfi.artist, mfi.album); + free_mfi(&mfi, 1); + return -1; + } + dir_id = db_directory_addorupdate(virtual_path, 0, dir_id); + if (dir_id <= 0) + { + DPRINTF(E_LOG, L_SPOTIFY, "Could not add or update directory '%s'\n", virtual_path); + free_mfi(&mfi, 1); + return -1; + } + + filescanner_process_media(url, time(NULL), 0, F_SCAN_TYPE_SPOTIFY, &mfi, dir_id); free_mfi(&mfi, 1); @@ -669,7 +702,7 @@ spotify_playlist_save(sp_playlist *pl) int plid; int num_tracks; char virtual_path[PATH_MAX]; - int time; + int created; int ret; int i; @@ -759,6 +792,7 @@ spotify_playlist_save(sp_playlist *pl) pli->path = strdup(url); pli->virtual_path = strdup(virtual_path); pli->parent_id = g_base_plid; + pli->directory_id = DIR_SPOTIFY; ret = db_pl_add(pli, &plid); if ((ret < 0) || (plid < 1)) @@ -783,9 +817,9 @@ spotify_playlist_save(sp_playlist *pl) continue; } - time = fptr_sp_playlist_track_create_time(pl, i); + created = fptr_sp_playlist_track_create_time(pl, i); - ret = spotify_track_save(plid, track, name, time); + ret = spotify_track_save(plid, track, name, created); if (ret < 0) { DPRINTF(E_LOG, L_SPOTIFY, "Error saving track %d to playlist '%s' (id %d)\n", i, name, plid); @@ -1383,6 +1417,8 @@ logged_in(sp_session *sess, sp_error error) DPRINTF(E_LOG, L_SPOTIFY, "Login to Spotify succeeded. Reloading playlists.\n"); + db_directory_enable_bypath("/spotify:"); + pl = fptr_sp_session_starred_create(sess); fptr_sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);