[db/filescanner/spotify] Replace filelist-view with separate

directories-table
to increase the performance of the mpd command 'lsinfo'
This commit is contained in:
chme 2015-12-27 07:16:50 +01:00
parent 3740486348
commit 7703a997c4
9 changed files with 1008 additions and 266 deletions

540
src/db.c
View File

@ -137,6 +137,7 @@ static const struct col_type_map mfi_cols_map[] =
{ mfi_offsetof(composer_sort), DB_TYPE_STRING }, { mfi_offsetof(composer_sort), DB_TYPE_STRING },
{ mfi_offsetof(album_artist_sort), DB_TYPE_STRING }, { mfi_offsetof(album_artist_sort), DB_TYPE_STRING },
{ mfi_offsetof(virtual_path), DB_TYPE_STRING }, { mfi_offsetof(virtual_path), DB_TYPE_STRING },
{ mfi_offsetof(directory_id), DB_TYPE_INT },
}; };
/* This list must be kept in sync with /* This list must be kept in sync with
@ -156,6 +157,7 @@ static const struct col_type_map pli_cols_map[] =
{ pli_offsetof(special_id), DB_TYPE_INT }, { pli_offsetof(special_id), DB_TYPE_INT },
{ pli_offsetof(virtual_path), DB_TYPE_STRING }, { pli_offsetof(virtual_path), DB_TYPE_STRING },
{ pli_offsetof(parent_id), DB_TYPE_INT }, { pli_offsetof(parent_id), DB_TYPE_INT },
{ pli_offsetof(directory_id), DB_TYPE_INT },
/* items is computed on the fly */ /* items is computed on the fly */
}; };
@ -224,6 +226,7 @@ static const ssize_t dbmfi_cols_map[] =
dbmfi_offsetof(composer_sort), dbmfi_offsetof(composer_sort),
dbmfi_offsetof(album_artist_sort), dbmfi_offsetof(album_artist_sort),
dbmfi_offsetof(virtual_path), dbmfi_offsetof(virtual_path),
dbmfi_offsetof(directory_id),
}; };
/* This list must be kept in sync with /* This list must be kept in sync with
@ -243,6 +246,7 @@ static const ssize_t dbpli_cols_map[] =
dbpli_offsetof(special_id), dbpli_offsetof(special_id),
dbpli_offsetof(virtual_path), dbpli_offsetof(virtual_path),
dbpli_offsetof(parent_id), dbpli_offsetof(parent_id),
dbpli_offsetof(directory_id),
/* items is computed on the fly */ /* items is computed on the fly */
}; };
@ -330,18 +334,6 @@ db_escape_string(const char *str)
return ret; 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 void
free_pi(struct pairing_info *pi, int content_only) free_pi(struct pairing_info *pi, int content_only)
{ {
@ -496,6 +488,18 @@ free_pli(struct playlist_info *pli, int content_only)
memset(pli, 0, sizeof(struct playlist_info)); 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 */ /* Unlock notification support */
static void static void
@ -708,12 +712,13 @@ db_purge_cruft(time_t ref)
char *errmsg; char *errmsg;
int i; int i;
int ret; int ret;
char *queries[3] = { NULL, NULL, NULL }; char *queries[4] = { NULL, NULL, NULL, NULL };
char *queries_tmpl[3] = 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 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 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 > 1 AND -1 <> %d AND db_timestamp < %" PRIi64 ";"
}; };
if (sizeof(queries) != sizeof(queries_tmpl)) if (sizeof(queries) != sizeof(queries_tmpl))
@ -1950,89 +1955,6 @@ db_query_fetch_string_sort(struct query_params *qp, char **string, char **sortst
return 0; 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 */ /* Files */
int int
@ -2283,6 +2205,7 @@ db_file_id_bymatch(char *path)
#undef Q_TMPL #undef Q_TMPL
} }
//TODO [cleanup] unused function(?)
int int
db_file_id_byfilebase(char *filename, char *base) db_file_id_byfilebase(char *filename, char *base)
{ {
@ -2609,8 +2532,8 @@ db_file_add(struct media_file_info *mfi)
" codectype, idx, has_video, contentrating, bits_per_sample, album_artist," \ " 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, " \ " media_kind, tv_series_name, tv_episode_num_str, tv_network_name, tv_episode_sort, tv_season_num, " \
" songartistid, songalbumid, " \ " 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)," \ " 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," \ " TRIM(%Q), TRIM(%Q), TRIM(%Q), %Q, %d, %d, %d, %" PRIi64 ", %d, %d," \
" %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d," \ " %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d," \
@ -2618,7 +2541,7 @@ db_file_add(struct media_file_info *mfi)
" %Q, %d, %d, %d, %d, TRIM(%Q)," \ " %Q, %d, %d, %d, %d, TRIM(%Q)," \
" %d, TRIM(%Q), TRIM(%Q), TRIM(%Q), %d, %d," \ " %d, TRIM(%Q), TRIM(%Q), TRIM(%Q), %d, %d," \
" daap_songalbumid(LOWER(TRIM(%Q)), ''), daap_songalbumid(LOWER(TRIM(%Q)), LOWER(TRIM(%Q))), " \ " 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 *query;
char *errmsg; char *errmsg;
@ -2653,7 +2576,7 @@ db_file_add(struct media_file_info *mfi)
mfi->media_kind, mfi->tv_series_name, mfi->tv_episode_num_str, 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->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->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) if (!query)
{ {
@ -2698,10 +2621,9 @@ db_file_update(struct media_file_info *mfi)
" tv_network_name = TRIM(%Q), tv_episode_sort = %d, tv_season_num = %d," \ " 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)))," \ " 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)," \ " 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;" " WHERE id = %d;"
// struct media_file_info *oldmfi;
char *query; char *query;
char *errmsg; char *errmsg;
int ret; int ret;
@ -2712,18 +2634,6 @@ db_file_update(struct media_file_info *mfi)
return -1; 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); mfi->db_timestamp = (uint64_t)time(NULL);
if (mfi->time_modified == 0) if (mfi->time_modified == 0)
@ -2743,7 +2653,7 @@ db_file_update(struct media_file_info *mfi)
mfi->tv_network_name, mfi->tv_episode_sort, mfi->tv_season_num, mfi->tv_network_name, mfi->tv_episode_sort, mfi->tv_season_num,
mfi->album_artist, mfi->album_artist, mfi->album, mfi->album_artist, mfi->album_artist, mfi->album,
mfi->title_sort, mfi->artist_sort, mfi->album_sort, 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); mfi->id);
if (!query) if (!query)
@ -2830,15 +2740,20 @@ db_file_delete_bypath(char *path)
void void
db_file_disable_bypath(char *path, char *strip, uint32_t cookie) 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; char *query;
int64_t disabled; int64_t disabled;
int striplen; int striplen;
int striplenvpath;
disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE;
striplen = strlen(strip) + 1; 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); db_query_run(query, 1, 1);
#undef Q_TMPL #undef Q_TMPL
@ -2847,15 +2762,20 @@ db_file_disable_bypath(char *path, char *strip, uint32_t cookie)
void void
db_file_disable_bymatch(char *path, char *strip, uint32_t cookie) 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; char *query;
int64_t disabled; int64_t disabled;
int striplen; int striplen;
int striplenvpath;
disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE;
striplen = strlen(strip) + 1; 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); db_query_run(query, 1, 1);
#undef Q_TMPL #undef Q_TMPL
@ -2864,11 +2784,11 @@ db_file_disable_bymatch(char *path, char *strip, uint32_t cookie)
int int
db_file_enable_bycookie(uint32_t cookie, char *path) 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; char *query;
int ret; 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); ret = db_query_run(query, 1, 1);
@ -2876,6 +2796,21 @@ db_file_enable_bycookie(uint32_t cookie, char *path)
#undef Q_TMPL #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 */ /* Playlists */
int int
@ -3249,8 +3184,8 @@ int
db_pl_add(struct playlist_info *pli, int *id) 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 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)" \ #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');" " VALUES (TRIM(%Q), %d, '%q', %" PRIi64 ", %d, '%q', %d, %d, %d, '%q', %d);"
char *query; char *query;
char *errmsg; char *errmsg;
int ret; int ret;
@ -3276,7 +3211,7 @@ db_pl_add(struct playlist_info *pli, int *id)
/* Add */ /* Add */
query = sqlite3_mprintf(QADD_TMPL, query = sqlite3_mprintf(QADD_TMPL,
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path), 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) if (!query)
{ {
@ -3341,14 +3276,14 @@ int
db_pl_update(struct playlist_info *pli) 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, " \ #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;" " WHERE id = %d;"
char *query; char *query;
int ret; int ret;
query = sqlite3_mprintf(Q_TMPL, query = sqlite3_mprintf(Q_TMPL,
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path), 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); ret = db_query_run(query, 1, 0);
@ -3403,15 +3338,20 @@ db_pl_delete_bypath(char *path)
void void
db_pl_disable_bypath(char *path, char *strip, uint32_t cookie) 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; char *query;
int64_t disabled; int64_t disabled;
int striplen; int striplen;
int striplenvpath;
disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE;
striplen = strlen(strip) + 1; 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); db_query_run(query, 1, 0);
#undef Q_TMPL #undef Q_TMPL
@ -3420,15 +3360,20 @@ db_pl_disable_bypath(char *path, char *strip, uint32_t cookie)
void void
db_pl_disable_bymatch(char *path, char *strip, uint32_t cookie) 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; char *query;
int64_t disabled; int64_t disabled;
int striplen; int striplen;
int striplenvpath;
disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE; disabled = (cookie != 0) ? cookie : INOTIFY_FAKE_COOKIE;
striplen = strlen(strip) + 1; 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); db_query_run(query, 1, 0);
#undef Q_TMPL #undef Q_TMPL
@ -3437,11 +3382,11 @@ db_pl_disable_bymatch(char *path, char *strip, uint32_t cookie)
int int
db_pl_enable_bycookie(uint32_t cookie, char *path) 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; char *query;
int ret; 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); ret = db_query_run(query, 1, 0);
@ -3569,6 +3514,275 @@ 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
}
/* Remotes */ /* Remotes */
static int static int
db_pairing_delete_byremote(char *remote_id) db_pairing_delete_byremote(char *remote_id)
@ -4635,7 +4849,8 @@ db_perthread_deinit(void)
" album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" album_artist_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" \
");" ");"
#define T_PL \ #define T_PL \
@ -4650,7 +4865,8 @@ db_perthread_deinit(void)
" idx INTEGER NOT NULL," \ " idx INTEGER NOT NULL," \
" special_id INTEGER DEFAULT 0," \ " special_id INTEGER DEFAULT 0," \
" virtual_path VARCHAR(4096)," \ " virtual_path VARCHAR(4096)," \
" parent_id INTEGER DEFAULT 0" \ " parent_id INTEGER DEFAULT 0," \
" directory_id INTEGER DEFAULT 0" \
");" ");"
#define T_PLITEMS \ #define T_PLITEMS \
@ -4690,16 +4906,14 @@ db_perthread_deinit(void)
" path VARCHAR(4096) NOT NULL" \ " path VARCHAR(4096) NOT NULL" \
");" ");"
#define V_FILELIST \ #define T_DIRECTORIES \
"CREATE VIEW IF NOT EXISTS filelist as" \ "CREATE TABLE IF NOT EXISTS directories (" \
" SELECT " \ " id INTEGER PRIMARY KEY NOT NULL," \
" virtual_path, time_modified, 3 as type " \ " virtual_path VARCHAR(4096) NOT NULL," \
" FROM files WHERE disabled = 0" \ " db_timestamp INTEGER DEFAULT 0," \
" UNION " \ " disabled INTEGER DEFAULT 0," \
" SELECT " \ " parent_id INTEGER DEFAULT 0" \
" virtual_path, db_timestamp, 1 as type " \ ");"
" FROM playlists where disabled = 0 AND type IN (2, 3)" \
";"
#define TRG_GROUPS_INSERT_FILES \ #define TRG_GROUPS_INSERT_FILES \
"CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \ "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \
@ -4745,16 +4959,21 @@ db_perthread_deinit(void)
" VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);" " 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);"
/* Rule of thumb: Will the current version of forked-daapd work with the new /* 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 * 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 * is a major upgrade. In other words minor version upgrades permit downgrading
* forked-daapd after the database was upgraded. */ * forked-daapd after the database was upgraded. */
#define SCHEMA_VERSION_MAJOR 18 #define SCHEMA_VERSION_MAJOR 19
#define SCHEMA_VERSION_MINOR 01 #define SCHEMA_VERSION_MINOR 00
#define Q_SCVER_MAJOR \ #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 \ #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 { struct db_init_query {
char *query; char *query;
@ -4771,8 +4990,7 @@ static const struct db_init_query db_init_table_queries[] =
{ T_PAIRINGS, "create table pairings" }, { T_PAIRINGS, "create table pairings" },
{ T_SPEAKERS, "create table speakers" }, { T_SPEAKERS, "create table speakers" },
{ T_INOTIFY, "create table inotify" }, { T_INOTIFY, "create table inotify" },
{ T_DIRECTORIES, "create table directories" },
{ V_FILELIST, "create view filelist" },
{ TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" }, { TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" },
{ TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" }, { TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" },
@ -4783,6 +5001,7 @@ static const struct db_init_query db_init_table_queries[] =
{ Q_PL4, "create default smart playlist 'TV Shows'" }, { Q_PL4, "create default smart playlist 'TV Shows'" },
{ Q_PL5, "create default smart playlist 'Podcasts'" }, { Q_PL5, "create default smart playlist 'Podcasts'" },
{ Q_PL6, "create default smart playlist 'Audiobooks'" }, { Q_PL6, "create default smart playlist 'Audiobooks'" },
{ Q_DIR1, "create default root directory '/'" },
{ Q_SCVER_MAJOR, "set schema version major" }, { Q_SCVER_MAJOR, "set schema version major" },
{ Q_SCVER_MINOR, "set schema version minor" }, { Q_SCVER_MINOR, "set schema version minor" },
@ -4832,12 +5051,18 @@ static const struct db_init_query db_init_table_queries[] =
#define I_FILELIST \ #define I_FILELIST \
"CREATE INDEX IF NOT EXISTS idx_filelist ON files(disabled, virtual_path, time_modified);" "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 \ #define I_PL_PATH \
"CREATE INDEX IF NOT EXISTS idx_pl_path ON playlists(path);" "CREATE INDEX IF NOT EXISTS idx_pl_path ON playlists(path);"
#define I_PL_DISABLED \ #define I_PL_DISABLED \
"CREATE INDEX IF NOT EXISTS idx_pl_disabled ON playlists(disabled, type, virtual_path, db_timestamp);" "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 \ #define I_FILEPATH \
"CREATE INDEX IF NOT EXISTS idx_filepath ON playlistitems(filepath ASC);" "CREATE INDEX IF NOT EXISTS idx_filepath ON playlistitems(filepath ASC);"
@ -4850,6 +5075,12 @@ static const struct db_init_query db_init_table_queries[] =
#define I_PAIRING \ #define I_PAIRING \
"CREATE INDEX IF NOT EXISTS idx_pairingguid ON pairings(guid);" "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[] = static const struct db_init_query db_init_index_queries[] =
{ {
{ I_RESCAN, "create rescan index" }, { I_RESCAN, "create rescan index" },
@ -4865,9 +5096,11 @@ static const struct db_init_query db_init_index_queries[] =
{ I_TITLE, "create title index" }, { I_TITLE, "create title index" },
{ I_ALBUM, "create album index" }, { I_ALBUM, "create album index" },
{ I_FILELIST, "create filelist index" }, { I_FILELIST, "create filelist index" },
{ I_FILE_DIR, "create file dir index" },
{ I_PL_PATH, "create playlist path index" }, { I_PL_PATH, "create playlist path index" },
{ I_PL_DISABLED, "create playlist state index" }, { I_PL_DISABLED, "create playlist state index" },
{ I_PL_DIR, "create playlist dir index" },
{ I_FILEPATH, "create file path index" }, { I_FILEPATH, "create file path index" },
{ I_PLITEMID, "create playlist id index" }, { I_PLITEMID, "create playlist id index" },
@ -4875,6 +5108,9 @@ static const struct db_init_query db_init_index_queries[] =
{ I_GRP_PERSIST, "create groups persistentid index" }, { I_GRP_PERSIST, "create groups persistentid index" },
{ I_PAIRING, "create pairing guid index" }, { I_PAIRING, "create pairing guid index" },
{ I_DIR_VPATH, "create directories disabled_virtualpath index" },
{ I_DIR_PARENT, "create directories parentid index" },
}; };
static int static int

View File

@ -61,12 +61,6 @@ enum query_type {
#define ARTWORK_SPOTIFY 6 #define ARTWORK_SPOTIFY 6
#define ARTWORK_HTTP 7 #define ARTWORK_HTTP 7
enum filelistitem_type {
F_PLAYLIST = 1,
F_DIR = 2,
F_FILE = 3,
};
struct query_params { struct query_params {
/* Query parameters, filled in by caller */ /* Query parameters, filled in by caller */
enum query_type type; enum query_type type;
@ -188,6 +182,8 @@ struct media_file_info {
char *album_artist_sort; char *album_artist_sort;
char *virtual_path; char *virtual_path;
uint32_t directory_id; /* Id of directory */
}; };
#define mfi_offsetof(field) offsetof(struct media_file_info, field) #define mfi_offsetof(field) offsetof(struct media_file_info, field)
@ -215,6 +211,7 @@ struct playlist_info {
uint32_t special_id; /* iTunes identifies certain 'special' playlists with special meaning */ uint32_t special_id; /* iTunes identifies certain 'special' playlists with special meaning */
char *virtual_path; /* virtual path of underlying playlist */ char *virtual_path; /* virtual path of underlying playlist */
uint32_t parent_id; /* Id of parent playlist if the playlist is nested */ 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) #define pli_offsetof(field) offsetof(struct playlist_info, field)
@ -233,6 +230,7 @@ struct db_playlist_info {
char *special_id; char *special_id;
char *virtual_path; char *virtual_path;
char *parent_id; char *parent_id;
char *directory_id;
}; };
#define dbpli_offsetof(field) offsetof(struct db_playlist_info, field) #define dbpli_offsetof(field) offsetof(struct db_playlist_info, field)
@ -322,16 +320,11 @@ struct db_media_file_info {
char *composer_sort; char *composer_sort;
char *album_artist_sort; char *album_artist_sort;
char *virtual_path; char *virtual_path;
char *directory_id;
}; };
#define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field) #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 { struct watch_info {
int wd; int wd;
char *path; char *path;
@ -353,15 +346,27 @@ struct filecount_info {
uint32_t length; uint32_t length;
}; };
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 * char *
db_escape_string(const char *str); db_escape_string(const char *str);
void void
free_pi(struct pairing_info *pi, int content_only); free_pi(struct pairing_info *pi, int content_only);
void
free_fi(struct filelist_info *fi, int content_only);
void void
free_mfi(struct media_file_info *mfi, int content_only); free_mfi(struct media_file_info *mfi, int content_only);
@ -371,6 +376,9 @@ unicode_fixup_mfi(struct media_file_info *mfi);
void void
free_pli(struct playlist_info *pli, int content_only); free_pli(struct playlist_info *pli, int content_only);
void
free_di(struct directory_info *di, int content_only);
/* Maintenance and DB hygiene */ /* Maintenance and DB hygiene */
void void
db_hook_post_scan(void); db_hook_post_scan(void);
@ -495,6 +503,9 @@ db_file_disable_bymatch(char *path, char *strip, uint32_t cookie);
int int
db_file_enable_bycookie(uint32_t cookie, char *path); db_file_enable_bycookie(uint32_t cookie, char *path);
int
db_file_update_directoryid(char *path, int dir_id);
/* Playlists */ /* Playlists */
int int
db_pl_get_count(void); db_pl_get_count(void);
@ -551,12 +562,31 @@ db_groups_clear(void);
int int
db_group_persistentid_byid(int id, int64_t *persistentid); db_group_persistentid_byid(int id, int64_t *persistentid);
/* Filelist */
/* Directories */
int int
db_mpd_start_query_filelist(struct query_params *qp, char *path); db_directory_id_byvirtualpath(char *virtual_path);
int 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);
/* Remotes */ /* Remotes */
int int

View File

@ -1167,6 +1167,263 @@ static const struct db_upgrade_query db_upgrade_v1801_queries[] =
{ U_V1801_SCVER_MINOR, "set schema_version_minor to 01" }, { 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_INSERT_DIR1 \
"INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
" VALUES (1, '/', 0, 0, 0);"
#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_INSERT_DIR1, "insert root 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 int
db_upgrade(sqlite3 *hdl, int db_ver) db_upgrade(sqlite3 *hdl, int db_ver)
{ {
@ -1266,6 +1523,17 @@ db_upgrade(sqlite3 *hdl, int db_ver)
if (ret < 0) if (ret < 0)
return -1; 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; break;
default: default:

View File

@ -112,13 +112,20 @@ struct deferred_pl {
char *path; char *path;
time_t mtime; time_t mtime;
struct deferred_pl *next; struct deferred_pl *next;
int directory_id;
}; };
struct stacked_dir { struct stacked_dir {
char *path; char *path;
int parent_id;
struct stacked_dir *next; struct stacked_dir *next;
}; };
enum root_directories {
DIR_FILE = 0,
DIR_HTTP = 1,
DIR_SPOTIFY = 2,
};
static int cmd_pipe[2]; static int cmd_pipe[2];
static int exit_pipe[2]; static int exit_pipe[2];
@ -197,7 +204,7 @@ nonblock_command(struct filescanner_command *cmd)
} }
static int 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; struct stacked_dir *d;
@ -215,28 +222,26 @@ push_dir(struct stacked_dir **s, char *path)
return -1; return -1;
} }
d->parent_id = parent_id;
d->next = *s; d->next = *s;
*s = d; *s = d;
return 0; return 0;
} }
static char * static struct stacked_dir *
pop_dir(struct stacked_dir **s) pop_dir(struct stacked_dir **s)
{ {
struct stacked_dir *d; struct stacked_dir *d;
char *ret;
if (!*s) if (!*s)
return NULL; return NULL;
d = *s; d = *s;
*s = d->next; *s = d->next;
ret = d->path;
free(d); return d;
return ret;
} }
#ifdef HAVE_REGEX_H #ifdef HAVE_REGEX_H
@ -633,7 +638,7 @@ fixup_tags(struct media_file_info *mfi)
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)
{ {
struct media_file_info *mfi; struct media_file_info *mfi;
char *filename; char *filename;
@ -765,6 +770,8 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
mfi->virtual_path = strdup(virtual_path); mfi->virtual_path = strdup(virtual_path);
} }
mfi->directory_id = dir_id;
if (mfi->id == 0) if (mfi->id == 0)
db_file_add(mfi); db_file_add(mfi);
else else
@ -776,13 +783,13 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
} }
static void static void
process_playlist(char *file, time_t mtime) process_playlist(char *file, time_t mtime, int dir_id)
{ {
enum file_type ft; enum file_type ft;
ft = file_type_get(file); ft = file_type_get(file);
if (ft == FILE_PLAYLIST) if (ft == FILE_PLAYLIST)
scan_playlist(file, mtime); scan_playlist(file, mtime, dir_id);
#ifdef ITUNES #ifdef ITUNES
else if (ft == FILE_ITUNES) else if (ft == FILE_ITUNES)
scan_itunes_itml(file); scan_itunes_itml(file);
@ -791,7 +798,7 @@ process_playlist(char *file, time_t mtime)
/* Thread: scan */ /* Thread: scan */
static void static void
defer_playlist(char *path, time_t mtime) defer_playlist(char *path, time_t mtime, int dir_id)
{ {
struct deferred_pl *pl; struct deferred_pl *pl;
@ -815,6 +822,7 @@ defer_playlist(char *path, time_t mtime)
} }
pl->mtime = mtime; pl->mtime = mtime;
pl->directory_id = dir_id;
pl->next = playlists; pl->next = playlists;
playlists = pl; playlists = pl;
@ -831,7 +839,7 @@ process_deferred_playlists(void)
{ {
playlists = pl->next; playlists = pl->next;
process_playlist(pl->path, pl->mtime); process_playlist(pl->path, pl->mtime, pl->directory_id);
free(pl->path); free(pl->path);
free(pl); free(pl);
@ -843,7 +851,7 @@ process_deferred_playlists(void)
/* Thread: scan */ /* Thread: scan */
static void 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; int is_bulkscan;
@ -852,7 +860,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
switch (file_type_get(file)) switch (file_type_get(file))
{ {
case FILE_REGULAR: 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); 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 // TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork
@ -871,14 +879,14 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
case FILE_PLAYLIST: case FILE_PLAYLIST:
case FILE_ITUNES: case FILE_ITUNES:
if (flags & F_SCAN_BULK) if (flags & F_SCAN_BULK)
defer_playlist(file, mtime); defer_playlist(file, mtime, dir_id);
else else
process_playlist(file, mtime); process_playlist(file, mtime, dir_id);
break; break;
case FILE_SMARTPL: case FILE_SMARTPL:
DPRINTF(E_DBG, L_SCAN, "Smart playlist file: %s\n", file); DPRINTF(E_DBG, L_SCAN, "Smart playlist file: %s\n", file);
scan_smartpl(file, mtime); scan_smartpl(file, mtime, dir_id);
break; break;
case FILE_ARTWORK: case FILE_ARTWORK:
@ -948,8 +956,22 @@ check_speciallib(char *path, const char *libtype)
} }
/* Thread: scan */ /* 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 static void
process_directory(char *path, int flags) process_directory(char *path, int flags, int parent_id)
{ {
DIR *dirp; DIR *dirp;
struct dirent buf; struct dirent buf;
@ -962,6 +984,9 @@ process_directory(char *path, int flags)
struct kevent kev; struct kevent kev;
#endif #endif
int type; int type;
struct directory_info di;
char virtual_path[PATH_MAX];
int dir_id;
int ret; int ret;
DPRINTF(E_DBG, L_SCAN, "Processing directory %s (flags = 0x%x)\n", path, flags); DPRINTF(E_DBG, L_SCAN, "Processing directory %s (flags = 0x%x)\n", path, flags);
@ -974,6 +999,24 @@ process_directory(char *path, int flags)
return; return;
} }
/* Add/update directories table */
memset(&di, 0, sizeof(struct directory_info));
ret = create_virtual_path(path, virtual_path, sizeof(virtual_path));
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Virtual path /file:%s, PATH_MAX exceeded\n", path);
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 '/http:'\n");
}
/* Check if compilation and/or podcast directory */ /* Check if compilation and/or podcast directory */
type = 0; type = 0;
if (check_speciallib(path, "compilations")) if (check_speciallib(path, "compilations"))
@ -1050,15 +1093,15 @@ process_directory(char *path, int flags)
if (S_ISREG(sb.st_mode)) if (S_ISREG(sb.st_mode))
{ {
if (!(flags & F_SCAN_FAST)) 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)) else if (S_ISFIFO(sb.st_mode))
{ {
if (!(flags & F_SCAN_FAST)) 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)) else if (S_ISDIR(sb.st_mode))
push_dir(&dirstack, entry); push_dir(&dirstack, entry, dir_id);
else else
DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry); DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry);
} }
@ -1116,21 +1159,63 @@ process_directory(char *path, int flags)
} }
/* Thread: scan */ /* 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;
// The root directoy ID
dir_id = 1;
ptr = path;
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)
{
DPRINTF(E_LOG, L_SCAN, "Virtual path /file:%s, PATH_MAX exceeded\n", path);
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) if (scan_exit)
return; 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) if (scan_exit)
return; return;
@ -1148,6 +1233,7 @@ bulk_scan(int flags)
char *deref; char *deref;
time_t start; time_t start;
time_t end; time_t end;
int parent_id;
int i; int i;
// Set global flag to avoid queued scan requests // Set global flag to avoid queued scan requests
@ -1165,6 +1251,8 @@ bulk_scan(int flags)
{ {
path = cfg_getnstr(lib, "directories", i); path = cfg_getnstr(lib, "directories", i);
parent_id = process_parent_directories(path);
deref = m_realpath(path); deref = m_realpath(path);
if (!deref) if (!deref)
{ {
@ -1173,16 +1261,19 @@ bulk_scan(int flags)
/* Assume dir is mistakenly not mounted, so just disable everything and update timestamps */ /* Assume dir is mistakenly not mounted, so just disable everything and update timestamps */
db_file_disable_bymatch(path, "", 0); db_file_disable_bymatch(path, "", 0);
db_pl_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0);
db_directory_disable_bymatch(path, "", 0);
db_file_ping_bymatch(path, 1); db_file_ping_bymatch(path, 1);
db_pl_ping_bymatch(path, 1); db_pl_ping_bymatch(path, 1);
db_directory_ping_bymatch(path);
continue; continue;
} }
counter = 0; counter = 0;
db_transaction_begin(); db_transaction_begin();
process_directories(deref, flags);
process_directories(deref, parent_id, flags);
db_transaction_end(); db_transaction_end();
free(deref); free(deref);
@ -1347,6 +1438,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie)
char *s; char *s;
int flags = 0; int flags = 0;
int ret; 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); DPRINTF(E_SPAM, L_SCAN, "Directory event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd);
@ -1354,6 +1446,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie)
{ {
db_file_disable_bymatch(path, "", 0); db_file_disable_bymatch(path, "", 0);
db_pl_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0);
db_directory_disable_bymatch(path, "", 0);
} }
if (ie->mask & IN_MOVE_SELF) if (ie->mask & IN_MOVE_SELF)
@ -1408,6 +1501,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie)
db_watch_mark_bymatch(path, path, ie->cookie); db_watch_mark_bymatch(path, path, ie->cookie);
db_file_disable_bymatch(path, path, ie->cookie); db_file_disable_bymatch(path, path, ie->cookie);
db_pl_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) if (ie->mask & IN_MOVED_TO)
@ -1417,6 +1511,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie)
db_watch_move_bycookie(ie->cookie, path); db_watch_move_bycookie(ie->cookie, path);
db_file_enable_bycookie(ie->cookie, path); db_file_enable_bycookie(ie->cookie, path);
db_pl_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 */ /* We'll rescan the directory tree to update playlists */
flags |= F_SCAN_MOVED; flags |= F_SCAN_MOVED;
@ -1450,6 +1545,7 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie)
db_file_disable_bymatch(path, "", 0); db_file_disable_bymatch(path, "", 0);
db_pl_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0);
db_directory_disable_bymatch(path, "", 0);
} }
else if (ret < 0) else if (ret < 0)
{ {
@ -1465,7 +1561,9 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie)
if (ie->mask & IN_CREATE) if (ie->mask & IN_CREATE)
{ {
process_directories(path, flags); parent_id = process_parent_directories(path);
process_directories(path, parent_id, flags);
if (dirstack) if (dirstack)
DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n");
@ -1480,8 +1578,12 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
uint32_t path_hash; uint32_t path_hash;
char *deref = NULL; char *deref = NULL;
char *file = path; char *file = path;
char *dir;
char dir_vpath[PATH_MAX];
int type; int type;
int i; int i;
int dir_id;
char *ptr;
int ret; int ret;
DPRINTF(E_SPAM, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); DPRINTF(E_SPAM, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd);
@ -1541,7 +1643,30 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
ret = db_file_enable_bycookie(ie->cookie, path); 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)
DPRINTF(E_LOG, L_SCAN, "Error creating virtual path for: %s\n", dir);
else
{
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 /* It's not a known media file, so it's either a new file
* or a playlist, known or not. * or a playlist, known or not.
@ -1636,10 +1761,14 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
if (check_speciallib(path, "audiobooks")) if (check_speciallib(path, "audiobooks"))
type |= F_SCAN_TYPE_AUDIOBOOK; type |= F_SCAN_TYPE_AUDIOBOOK;
dir_id = process_parent_directories(file);
if (S_ISREG(sb.st_mode)) 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)) 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) if (deref)
free(deref); free(deref);

View File

@ -19,7 +19,7 @@ void
filescanner_deinit(void); filescanner_deinit(void);
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 */ /* Actual scanners */
int int
@ -29,10 +29,10 @@ int
scan_metadata_icy(char *url, struct media_file_info *mfi); scan_metadata_icy(char *url, struct media_file_info *mfi);
void void
scan_playlist(char *file, time_t mtime); scan_playlist(char *file, time_t mtime, int dir_id);
void void
scan_smartpl(char *file, time_t mtime); scan_smartpl(char *file, time_t mtime, int dir_id);
#ifdef ITUNES #ifdef ITUNES
void void

View File

@ -74,7 +74,7 @@ extinf_get(char *string, struct media_file_info *mfi, int *extinf)
} }
void void
scan_playlist(char *file, time_t mtime) scan_playlist(char *file, time_t mtime, int dir_id)
{ {
FILE *fp; FILE *fp;
struct media_file_info mfi; struct media_file_info mfi;
@ -93,6 +93,7 @@ scan_playlist(char *file, time_t mtime)
int ret; int ret;
char virtual_path[PATH_MAX]; char virtual_path[PATH_MAX];
int i; int i;
int di_id;
DPRINTF(E_LOG, L_SCAN, "Processing static playlist: %s\n", file); DPRINTF(E_LOG, L_SCAN, "Processing static playlist: %s\n", file);
@ -172,6 +173,8 @@ scan_playlist(char *file, time_t mtime)
*ptr = '\0'; *ptr = '\0';
pli->virtual_path = strdup(virtual_path); pli->virtual_path = strdup(virtual_path);
pli->directory_id = dir_id;
ret = db_pl_add(pli, &pl_id); ret = db_pl_add(pli, &pl_id);
if (ret < 0) if (ret < 0)
{ {
@ -236,7 +239,14 @@ scan_playlist(char *file, time_t mtime)
if (extinf) if (extinf)
DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi.artist, mfi.title); 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); di_id = db_directory_addorupdate("/http:", 0, 1);
if (di_id <= 0)
{
DPRINTF(E_LOG, L_SCAN, "Insert or update of directory failed '/http:'\n");
}
filescanner_process_media(filename, mtime, 0, F_SCAN_TYPE_URL, &mfi, di_id);
} }
/* Regular file, should already be in library */ /* Regular file, should already be in library */
else else

View File

@ -171,7 +171,7 @@ smartpl_parse_file(const char *file, struct playlist_info *pli)
} }
void void
scan_smartpl(char *file, time_t mtime) scan_smartpl(char *file, time_t mtime, int dir_id)
{ {
struct playlist_info *pli; struct playlist_info *pli;
int pl_id; int pl_id;
@ -203,6 +203,8 @@ scan_smartpl(char *file, time_t mtime)
else else
pl_id = pli->id; pl_id = pli->id;
pli->directory_id = dir_id;
ret = smartpl_parse_file(file, pli); ret = smartpl_parse_file(file, pli);
if (ret < 0) if (ret < 0)
{ {

110
src/mpd.c
View File

@ -2692,12 +2692,16 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
static int static int
mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
{ {
int dir_id;
struct directory_info subdir;
struct query_params qp; struct query_params qp;
struct directory_enum dir_enum;
char parent[PATH_MAX]; char parent[PATH_MAX];
struct filelist_info *fi;
struct media_file_info *mfi;
char modified[32];
int print_playlists; int print_playlists;
struct db_playlist_info dbpli;
char modified[32];
uint32_t time_modified;
struct db_media_file_info dbmfi;
int ret; int ret;
if (argc < 2 || strlen(argv[1]) == 0 if (argc < 2 || strlen(argv[1]) == 0
@ -2711,7 +2715,7 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
} }
else else
{ {
ret = snprintf(parent, sizeof(parent), "/%s/", argv[1]); ret = snprintf(parent, sizeof(parent), "/%s", argv[1]);
} }
if ((ret < 0) || (ret >= sizeof(parent))) if ((ret < 0) || (ret >= sizeof(parent)))
@ -2731,72 +2735,94 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
print_playlists = 1; 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"); DPRINTF(E_LOG, L_MPD, "Directory info not found for virtual-path '%s'\n", parent);
return ACK_ERROR_UNKNOWN; return -1;
} }
// Load playlists for dir-id
memset(&qp, 0, sizeof(struct query_params)); memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_PL;
ret = db_mpd_start_query_filelist(&qp, parent); qp.sort = S_PLAYLIST;
qp.idx_type = I_NONE;
qp.filter = sqlite3_mprintf("(directory_id = %d AND (f.type = %d OR f.type = %d))", dir_id, PL_PLAIN, PL_SMART);
ret = db_query_start(&qp);
if (ret < 0) if (ret < 0)
{ {
ret = asprintf(errmsg, "Could not start query for path '%s'", argv[1]); db_query_end(&qp);
ret = asprintf(errmsg, "Could not start query");
if (ret < 0) if (ret < 0)
DPRINTF(E_LOG, L_MPD, "Out of memory\n"); DPRINTF(E_LOG, L_MPD, "Out of memory\n");
free_fi(fi, 0);
return ACK_ERROR_UNKNOWN; return ACK_ERROR_UNKNOWN;
} }
while (((ret = db_query_fetch_pl(&qp, &dbpli)) == 0) && (dbpli.id))
while (((ret = db_mpd_query_fetch_filelist(&qp, fi)) == 0) && (fi->virtual_path))
{ {
if (fi->type == F_DIR) if (safe_atou32(dbpli.db_timestamp, &time_modified) != 0)
{ {
mpd_time(modified, sizeof(modified), fi->time_modified); DPRINTF(E_LOG, L_MPD, "Error converting time modified to uint32_t: %s\n", dbpli.db_timestamp);
return -1;
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), time_modified);
{
mpd_time(modified, sizeof(modified), fi->time_modified);
evbuffer_add_printf(evbuf, evbuffer_add_printf(evbuf,
"playlist: %s\n" "playlist: %s\n"
"Last-Modified: %s\n", "Last-Modified: %s\n",
(fi->virtual_path + 1), (dbpli.virtual_path + 1),
modified); modified);
} }
else if (fi->type == F_FILE) db_query_end(&qp);
{ sqlite3_free(qp.filter);
mfi = db_file_fetch_byvirtualpath(fi->virtual_path);
if (mfi) // Load sub directories for dir-id
{ memset(&dir_enum, 0, sizeof(struct directory_enum));
ret = mpd_add_mediainfo(evbuf, mfi, 0, -1); dir_enum.parent_id = dir_id;
ret = db_directory_enum_start(&dir_enum);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_MPD, "Could not add mediainfo for path '%s'\n", fi->virtual_path); DPRINTF(E_LOG, L_MPD, "Failed to start directory enum for parent_id %d\n", dir_id);
return -1;
} }
while ((ret = db_directory_enum_fetch(&dir_enum, &subdir)) == 0 && subdir.id > 0)
{
evbuffer_add_printf(evbuf,
"directory: %s\n"
"Last-Modified: %s\n",
(subdir.virtual_path + 1),
"2015-12-01 00:00");
}
db_directory_enum_end(&dir_enum);
free_mfi(mfi, 0); // 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("(directory_id = %d)", dir_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))
{
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);
} }
} }
}
db_query_end(&qp); db_query_end(&qp);
if (fi) // If the root directory was passed as argument add the stored playlists to the response
free_fi(fi, 0);
if (print_playlists) 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 mpd_command_listplaylists(evbuf, argc, argv, errmsg);
} }

View File

@ -35,6 +35,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/queue.h> #include <sys/queue.h>
#include <time.h>
#include <pthread.h> #include <pthread.h>
#include <dlfcn.h> #include <dlfcn.h>
@ -573,6 +574,9 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
sp_link *link; sp_link *link;
char url[1024]; char url[1024];
int ret; int ret;
int dir_id;
char virtual_path[PATH_MAX];
if (!fptr_sp_track_is_loaded(track)) if (!fptr_sp_track_is_loaded(track))
{ {
@ -618,7 +622,32 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
return -1; return -1;
} }
filescanner_process_media(url, time(NULL), 0, F_SCAN_TYPE_SPOTIFY, &mfi); snprintf(virtual_path, PATH_MAX, "/spotify:");
dir_id = db_directory_addorupdate(virtual_path, 0, 1);
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;
}
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", mfi.artist);
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;
}
snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s", mfi.artist, mfi.album);
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); free_mfi(&mfi, 1);
@ -669,9 +698,10 @@ spotify_playlist_save(sp_playlist *pl)
int plid; int plid;
int num_tracks; int num_tracks;
char virtual_path[PATH_MAX]; char virtual_path[PATH_MAX];
int time; int created;
int ret; int ret;
int i; int i;
int dir_id;
if (!fptr_sp_playlist_is_loaded(pl)) if (!fptr_sp_playlist_is_loaded(pl))
{ {
@ -752,6 +782,16 @@ spotify_playlist_save(sp_playlist *pl)
return -1; return -1;
} }
dir_id = db_directory_addorupdate("/spotify:", 0, 1);
if (dir_id <= 0)
{
DPRINTF(E_LOG, L_SCAN, "Insert or update of directory failed '/spotify:'\n");
free_pli(pli, 0);
return -1;
}
memset(pli, 0, sizeof(struct playlist_info)); memset(pli, 0, sizeof(struct playlist_info));
pli->type = PL_PLAIN; pli->type = PL_PLAIN;
@ -759,6 +799,7 @@ spotify_playlist_save(sp_playlist *pl)
pli->path = strdup(url); pli->path = strdup(url);
pli->virtual_path = strdup(virtual_path); pli->virtual_path = strdup(virtual_path);
pli->parent_id = g_base_plid; pli->parent_id = g_base_plid;
pli->directory_id = dir_id;
ret = db_pl_add(pli, &plid); ret = db_pl_add(pli, &plid);
if ((ret < 0) || (plid < 1)) if ((ret < 0) || (plid < 1))
@ -783,9 +824,9 @@ spotify_playlist_save(sp_playlist *pl)
continue; 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) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Error saving track %d to playlist '%s' (id %d)\n", i, name, plid); DPRINTF(E_LOG, L_SPOTIFY, "Error saving track %d to playlist '%s' (id %d)\n", i, name, plid);