Merge branch 'playlists1'

This commit is contained in:
ejurgensen 2020-02-08 11:16:02 +01:00
commit d2f0d7b53a
16 changed files with 1008 additions and 769 deletions

View File

@ -157,6 +157,10 @@ library {
# to trigger a rescan. # to trigger a rescan.
# filescan_disable = false # filescan_disable = false
# Should metadata from m3u playlists, e.g. artist and title in EXTINF,
# override the metadata we get from radio streams?
# m3u_overrides = false
# Should iTunes metadata override ours? # Should iTunes metadata override ours?
# itunes_overrides = false # itunes_overrides = false

View File

@ -95,6 +95,7 @@ static cfg_opt_t sec_library[] =
CFG_STR_LIST("filetypes_ignore", "{.db,.ini,.db-journal,.pdf,.metadata}", CFGF_NONE), CFG_STR_LIST("filetypes_ignore", "{.db,.ini,.db-journal,.pdf,.metadata}", CFGF_NONE),
CFG_STR_LIST("filepath_ignore", NULL, CFGF_NONE), CFG_STR_LIST("filepath_ignore", NULL, CFGF_NONE),
CFG_BOOL("filescan_disable", cfg_false, CFGF_NONE), CFG_BOOL("filescan_disable", cfg_false, CFGF_NONE),
CFG_BOOL("m3u_overrides", cfg_false, CFGF_NONE),
CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE), CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE),
CFG_BOOL("itunes_smartpl", cfg_false, CFGF_NONE), CFG_BOOL("itunes_smartpl", cfg_false, CFGF_NONE),
CFG_STR_LIST("no_decode", NULL, CFGF_NONE), CFG_STR_LIST("no_decode", NULL, CFGF_NONE),

View File

@ -3,6 +3,7 @@
#define __CONFFILE_H__ #define __CONFFILE_H__
#include <sys/types.h> #include <sys/types.h>
#include <stdint.h>
#include <confuse.h> #include <confuse.h>

253
src/db.c
View File

@ -62,8 +62,9 @@
#define DB_TYPE_INT64 2 #define DB_TYPE_INT64 2
#define DB_TYPE_STRING 3 #define DB_TYPE_STRING 3
// Flags that column value is set automatically by the db, e.g. by a trigger // Flags that the field will not be bound to prepared statements, which is relevant if the field has no
#define DB_FLAG_AUTO (1 << 0) // matching column, or if the the column value is set automatically by the db, e.g. by a trigger
#define DB_FLAG_NO_BIND (1 << 0)
// Flags that we will only update column value if we have non-zero value (to avoid zeroing e.g. rating) // Flags that we will only update column value if we have non-zero value (to avoid zeroing e.g. rating)
#define DB_FLAG_NO_ZERO (1 << 1) #define DB_FLAG_NO_ZERO (1 << 1)
@ -110,6 +111,9 @@ struct db_statements
sqlite3_stmt *files_insert; sqlite3_stmt *files_insert;
sqlite3_stmt *files_update; sqlite3_stmt *files_update;
sqlite3_stmt *files_ping; sqlite3_stmt *files_ping;
sqlite3_stmt *playlists_insert;
sqlite3_stmt *playlists_update;
}; };
struct col_type_map { struct col_type_map {
@ -150,7 +154,7 @@ struct browse_clause {
*/ */
static const struct col_type_map mfi_cols_map[] = static const struct col_type_map mfi_cols_map[] =
{ {
{ "id", mfi_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_AUTO }, { "id", mfi_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_BIND },
{ "path", mfi_offsetof(path), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, { "path", mfi_offsetof(path), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE },
{ "virtual_path", mfi_offsetof(virtual_path), DB_TYPE_STRING }, { "virtual_path", mfi_offsetof(virtual_path), DB_TYPE_STRING },
{ "fname", mfi_offsetof(fname), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, { "fname", mfi_offsetof(fname), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE },
@ -221,7 +225,7 @@ static const struct col_type_map mfi_cols_map[] =
*/ */
static const struct col_type_map pli_cols_map[] = static const struct col_type_map pli_cols_map[] =
{ {
{ "id", pli_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_AUTO }, { "id", pli_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_BIND },
{ "title", pli_offsetof(title), DB_TYPE_STRING, DB_FIXUP_TITLE }, { "title", pli_offsetof(title), DB_TYPE_STRING, DB_FIXUP_TITLE },
{ "type", pli_offsetof(type), DB_TYPE_INT }, { "type", pli_offsetof(type), DB_TYPE_INT },
{ "query", pli_offsetof(query), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, { "query", pli_offsetof(query), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE },
@ -235,11 +239,11 @@ static const struct col_type_map pli_cols_map[] =
{ "directory_id", pli_offsetof(directory_id), DB_TYPE_INT }, { "directory_id", pli_offsetof(directory_id), DB_TYPE_INT },
{ "query_order", pli_offsetof(query_order), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE }, { "query_order", pli_offsetof(query_order), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE },
{ "query_limit", pli_offsetof(query_limit), DB_TYPE_INT }, { "query_limit", pli_offsetof(query_limit), DB_TYPE_INT },
{ "media_kind", pli_offsetof(media_kind), DB_TYPE_INT }, { "media_kind", pli_offsetof(media_kind), DB_TYPE_INT, DB_FIXUP_MEDIA_KIND },
// Not in the database, but returned via the query's COUNT()/SUM() // Not in the database, but returned via the query's COUNT()/SUM()
{ "items", pli_offsetof(items), DB_TYPE_INT }, { "items", pli_offsetof(items), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_BIND },
{ "streams", pli_offsetof(streams), DB_TYPE_INT }, { "streams", pli_offsetof(streams), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_BIND },
}; };
/* This list must be kept in sync with /* This list must be kept in sync with
@ -248,7 +252,7 @@ static const struct col_type_map pli_cols_map[] =
*/ */
static const struct col_type_map qi_cols_map[] = static const struct col_type_map qi_cols_map[] =
{ {
{ "id", qi_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_AUTO }, { "id", qi_offsetof(id), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_BIND },
{ "file_id", qi_offsetof(id), DB_TYPE_INT }, { "file_id", qi_offsetof(id), DB_TYPE_INT },
{ "pos", qi_offsetof(pos), DB_TYPE_INT }, { "pos", qi_offsetof(pos), DB_TYPE_INT },
{ "shuffle_pos", qi_offsetof(shuffle_pos), DB_TYPE_INT }, { "shuffle_pos", qi_offsetof(shuffle_pos), DB_TYPE_INT },
@ -277,7 +281,7 @@ static const struct col_type_map qi_cols_map[] =
{ "type", qi_offsetof(type), DB_TYPE_STRING, DB_FIXUP_CODECTYPE }, { "type", qi_offsetof(type), DB_TYPE_STRING, DB_FIXUP_CODECTYPE },
{ "bitrate", qi_offsetof(bitrate), DB_TYPE_INT }, { "bitrate", qi_offsetof(bitrate), DB_TYPE_INT },
{ "samplerate", qi_offsetof(samplerate), DB_TYPE_INT }, { "samplerate", qi_offsetof(samplerate), DB_TYPE_INT },
{ "chanenls", qi_offsetof(channels), DB_TYPE_INT }, { "channels", qi_offsetof(channels), DB_TYPE_INT },
}; };
/* This list must be kept in sync with /* This list must be kept in sync with
@ -400,7 +404,7 @@ static const ssize_t dbgri_cols_map[] =
*/ */
static const struct col_type_map wi_cols_map[] = static const struct col_type_map wi_cols_map[] =
{ {
{ "wd", wi_offsetof(wd), DB_TYPE_INT, DB_FLAG_AUTO }, { "wd", wi_offsetof(wd), DB_TYPE_INT, DB_FLAG_NO_BIND },
{ "cookie", wi_offsetof(cookie), DB_TYPE_INT }, { "cookie", wi_offsetof(cookie), DB_TYPE_INT },
{ "path", wi_offsetof(path), DB_TYPE_STRING }, { "path", wi_offsetof(path), DB_TYPE_STRING },
}; };
@ -516,9 +520,6 @@ static __thread struct db_statements db_statements;
/* Forward */ /* Forward */
struct playlist_info *
db_pl_fetch_byid(int id);
static enum group_type static enum group_type
db_group_type_bypersistentid(int64_t persistentid); db_group_type_bypersistentid(int64_t persistentid);
@ -950,6 +951,8 @@ fixup_defaults(char **tag, enum fixup_type fixup, struct fixup_ctx *ctx)
ctx->mfi->media_kind = MEDIA_KIND_TVSHOW; ctx->mfi->media_kind = MEDIA_KIND_TVSHOW;
else if (ctx->mfi && !ctx->mfi->media_kind) else if (ctx->mfi && !ctx->mfi->media_kind)
ctx->mfi->media_kind = MEDIA_KIND_MUSIC; ctx->mfi->media_kind = MEDIA_KIND_MUSIC;
else if (ctx->pli && !ctx->pli->media_kind)
ctx->pli->media_kind = MEDIA_KIND_MUSIC;
else if (ctx->queue_item && !ctx->queue_item->media_kind) else if (ctx->queue_item && !ctx->queue_item->media_kind)
ctx->queue_item->media_kind = MEDIA_KIND_MUSIC; ctx->queue_item->media_kind = MEDIA_KIND_MUSIC;
@ -1095,22 +1098,22 @@ fixup_tags_queue_item(struct db_queue_item *queue_item)
} }
static int static int
bind_mfi(sqlite3_stmt *stmt, struct media_file_info *mfi) bind_generic(sqlite3_stmt *stmt, void *data, const struct col_type_map *map, size_t map_size, int id)
{ {
char **strptr; char **strptr;
char *ptr; char *ptr;
int i; int i;
int n; int n;
for (i = 0, n = 1; i < ARRAY_SIZE(mfi_cols_map); i++) for (i = 0, n = 1; i < map_size; i++)
{ {
if (mfi_cols_map[i].flag & DB_FLAG_AUTO) if (map[i].flag & DB_FLAG_NO_BIND)
continue; continue;
ptr = (char *)mfi + mfi_cols_map[i].offset; ptr = data + map[i].offset;
strptr = (char **)((char *)mfi + mfi_cols_map[i].offset); strptr = (char **)(data + map[i].offset);
switch (mfi_cols_map[i].type) switch (map[i].type)
{ {
case DB_TYPE_INT: case DB_TYPE_INT:
sqlite3_bind_int64(stmt, n, *((uint32_t *)ptr)); // Use _int64 because _int is for signed int32 sqlite3_bind_int64(stmt, n, *((uint32_t *)ptr)); // Use _int64 because _int is for signed int32
@ -1125,7 +1128,7 @@ bind_mfi(sqlite3_stmt *stmt, struct media_file_info *mfi)
break; break;
default: default:
DPRINTF(E_LOG, L_DB, "BUG: Unknown type %d in mfi column map\n", mfi_cols_map[i].type); DPRINTF(E_LOG, L_DB, "BUG: Unknown type %d in column map\n", map[i].type);
return -1; return -1;
} }
@ -1133,12 +1136,23 @@ bind_mfi(sqlite3_stmt *stmt, struct media_file_info *mfi)
} }
// This binds the final "WHERE id = ?" if it is an update // This binds the final "WHERE id = ?" if it is an update
if (mfi->id) if (id)
sqlite3_bind_int(stmt, n, mfi->id); sqlite3_bind_int(stmt, n, id);
return 0; return 0;
} }
static int
bind_mfi(sqlite3_stmt *stmt, struct media_file_info *mfi)
{
return bind_generic(stmt, mfi, mfi_cols_map, ARRAY_SIZE(mfi_cols_map), mfi->id);
}
static int
bind_pli(sqlite3_stmt *stmt, struct playlist_info *pli)
{
return bind_generic(stmt, pli, pli_cols_map, ARRAY_SIZE(pli_cols_map), pli->id);
}
/* Unlock notification support */ /* Unlock notification support */
static void static void
@ -3462,75 +3476,58 @@ db_pl_fetch_bytitlepath(const char *title, const char *path)
} }
int int
db_pl_add(struct playlist_info *pli, int *id) db_pl_add(struct playlist_info *pli)
{ {
#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, directory_id, query_order, query_limit)" \
" VALUES (TRIM(%Q), %d, '%q', %" PRIi64 ", %d, '%q', %d, %d, %d, '%q', %d, %Q, %d);"
char *query;
char *errmsg;
int ret; int ret;
// If the backend sets 1 it must be preserved, because the backend is still
// scanning and is going to update it later (see filescanner_playlist.c)
if (pli->db_timestamp != 1)
pli->db_timestamp = (uint64_t)time(NULL);
fixup_tags_pli(pli); fixup_tags_pli(pli);
/* Check duplicates */ ret = bind_pli(db_statements.playlists_insert, pli);
query = sqlite3_mprintf(QDUP_TMPL, pli->title, STR(pli->path)); if (ret < 0)
if (!query) return -1;
ret = db_statement_run(db_statements.playlists_insert);
if (ret < 0)
return -1;
ret = (int)sqlite3_last_insert_rowid(hdl);
if (ret == 0)
{ {
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); DPRINTF(E_LOG, L_DB, "Successful playlist insert but no last_insert_rowid!\n");
return -1; return -1;
} }
ret = db_get_one_int(query); DPRINTF(E_DBG, L_DB, "Added playlist %s (path %s) as id %d\n", pli->title, pli->path, ret);
sqlite3_free(query); return ret;
}
if (ret > 0) int
{ db_pl_update(struct playlist_info *pli)
DPRINTF(E_WARN, L_DB, "Duplicate playlist with title '%s' path '%s'\n", pli->title, pli->path); {
return -1; int ret;
}
/* Add */ // If the backend sets 1 it must be preserved, because the backend is still
query = sqlite3_mprintf(QADD_TMPL, // scanning and is going to update it later (see filescanner_playlist.c)
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path), if (pli->db_timestamp != 1)
pli->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->directory_id, pli->db_timestamp = (uint64_t)time(NULL);
pli->query_order, pli->query_limit);
if (!query) fixup_tags_pli(pli);
{
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 = bind_pli(db_statements.playlists_update, pli);
if (ret < 0)
return -1;
ret = db_exec(query, &errmsg); ret = db_statement_run(db_statements.playlists_update);
if (ret != SQLITE_OK) if (ret < 0)
{ return -1;
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
sqlite3_free(errmsg); return pli->id;
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 playlist %s (path %s) with id %d\n", pli->title, pli->path, *id);
return 0;
#undef QDUP_TMPL
#undef QADD_TMPL
} }
int int
@ -3557,29 +3554,6 @@ db_pl_add_item_byid(int plid, int fileid)
#undef Q_TMPL #undef Q_TMPL
} }
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', directory_id = %d," \
" query_order = %Q, query_limit = %d" \
" WHERE id = %d;"
char *query;
int ret;
fixup_tags_pli(pli);
query = sqlite3_mprintf(Q_TMPL,
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path),
pli->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->directory_id,
pli->query_order, pli->query_limit, pli->id);
ret = db_query_run(query, 1, 0);
return ret;
#undef Q_TMPL
}
void void
db_pl_clear_items(int id) db_pl_clear_items(int id)
{ {
@ -6787,24 +6761,24 @@ db_open(void)
return 0; return 0;
} }
static int static sqlite3_stmt *
db_statements_prepare(void) db_statements_prepare_insert(const struct col_type_map *map, size_t map_size, const char *table)
{ {
char *query; char *query;
char keystr[2048]; char keystr[2048];
char valstr[1024]; char valstr[1024];
sqlite3_stmt *stmt;
int ret; int ret;
int i; int i;
// Prepare "INSERT INTO files" statement
memset(keystr, 0, sizeof(keystr)); memset(keystr, 0, sizeof(keystr));
memset(valstr, 0, sizeof(valstr)); memset(valstr, 0, sizeof(valstr));
for (i = 0; i < ARRAY_SIZE(mfi_cols_map); i++) for (i = 0; i < map_size; i++)
{ {
if (mfi_cols_map[i].flag & DB_FLAG_AUTO) if (map[i].flag & DB_FLAG_NO_BIND)
continue; continue;
CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s, ", mfi_cols_map[i].name)); CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s, ", map[i].name));
CHECK_ERR(L_DB, safe_snprintf_cat(valstr, sizeof(valstr), "?, ")); CHECK_ERR(L_DB, safe_snprintf_cat(valstr, sizeof(valstr), "?, "));
} }
@ -6812,58 +6786,97 @@ db_statements_prepare(void)
*(strrchr(keystr, ',')) = '\0'; *(strrchr(keystr, ',')) = '\0';
*(strrchr(valstr, ',')) = '\0'; *(strrchr(valstr, ',')) = '\0';
CHECK_NULL(L_DB, query = db_mprintf("INSERT INTO files (%s) VALUES (%s);", keystr, valstr)); CHECK_NULL(L_DB, query = db_mprintf("INSERT INTO %s (%s) VALUES (%s);", table, keystr, valstr));
ret = db_blocking_prepare_v2(query, -1, &db_statements.files_insert, NULL); ret = db_blocking_prepare_v2(query, -1, &stmt, NULL);
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
{ {
DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl)); DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl));
free(query); free(query);
return -1; return NULL;
} }
free(query); free(query);
// Prepare "UPDATE files" statement return stmt;
}
static sqlite3_stmt *
db_statements_prepare_update(const struct col_type_map *map, size_t map_size, const char *table)
{
char *query;
char keystr[2048];
sqlite3_stmt *stmt;
int ret;
int i;
memset(keystr, 0, sizeof(keystr)); memset(keystr, 0, sizeof(keystr));
for (i = 0; i < ARRAY_SIZE(mfi_cols_map); i++) for (i = 0; i < map_size; i++)
{ {
if (mfi_cols_map[i].flag & DB_FLAG_AUTO) if (map[i].flag & DB_FLAG_NO_BIND)
continue; continue;
if (mfi_cols_map[i].flag & DB_FLAG_NO_ZERO) if (map[i].flag & DB_FLAG_NO_ZERO)
CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s = daap_no_zero(?, %s), ", mfi_cols_map[i].name, mfi_cols_map[i].name)); CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s = daap_no_zero(?, %s), ", map[i].name, map[i].name));
else else
CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s = ?, ", mfi_cols_map[i].name)); CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s = ?, ", map[i].name));
} }
// Terminate at the ending ", " // Terminate at the ending ", "
*(strrchr(keystr, ',')) = '\0'; *(strrchr(keystr, ',')) = '\0';
CHECK_NULL(L_DB, query = db_mprintf("UPDATE files SET %s WHERE %s = ?;", keystr, mfi_cols_map[0].name)); CHECK_NULL(L_DB, query = db_mprintf("UPDATE %s SET %s WHERE %s = ?;", table, keystr, map[0].name));
ret = db_blocking_prepare_v2(query, -1, &db_statements.files_update, NULL); ret = db_blocking_prepare_v2(query, -1, &stmt, NULL);
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
{ {
DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl)); DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl));
free(query); free(query);
return -1; return NULL;
} }
free(query); free(query);
// Prepare "UPDATE files SET db_timestamp" statement return stmt;
CHECK_NULL(L_DB, query = db_mprintf("UPDATE files SET db_timestamp = ?, disabled = 0 WHERE path = ? AND db_timestamp >= ?;")); }
ret = db_blocking_prepare_v2(query, -1, &db_statements.files_ping, NULL); static sqlite3_stmt *
db_statements_prepare_ping(const char *table)
{
char *query;
sqlite3_stmt *stmt;
int ret;
CHECK_NULL(L_DB, query = db_mprintf("UPDATE %s SET db_timestamp = ?, disabled = 0 WHERE path = ? AND db_timestamp >= ?;", table));
ret = db_blocking_prepare_v2(query, -1, &stmt, NULL);
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
{ {
DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl)); DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl));
free(query); free(query);
return -1; return NULL;
} }
free(query); free(query);
return stmt;
}
static int
db_statements_prepare(void)
{
db_statements.files_insert = db_statements_prepare_insert(mfi_cols_map, ARRAY_SIZE(mfi_cols_map), "files");
db_statements.files_update = db_statements_prepare_update(mfi_cols_map, ARRAY_SIZE(mfi_cols_map), "files");
db_statements.files_ping = db_statements_prepare_ping("files");
db_statements.playlists_insert = db_statements_prepare_insert(pli_cols_map, ARRAY_SIZE(pli_cols_map), "playlists");
db_statements.playlists_update = db_statements_prepare_update(pli_cols_map, ARRAY_SIZE(pli_cols_map), "playlists");
if ( !db_statements.files_insert || !db_statements.files_update || !db_statements.files_ping
|| !db_statements.playlists_insert || !db_statements.playlists_update
)
return -1;
return 0; return 0;
} }

View File

@ -660,6 +660,9 @@ db_pl_ping_items_bymatch(const char *path, int id);
int int
db_pl_id_bypath(const char *path); db_pl_id_bypath(const char *path);
struct playlist_info *
db_pl_fetch_byid(int id);
struct playlist_info * struct playlist_info *
db_pl_fetch_bypath(const char *path); db_pl_fetch_bypath(const char *path);
@ -670,7 +673,10 @@ struct playlist_info *
db_pl_fetch_bytitlepath(const char *title, const char *path); db_pl_fetch_bytitlepath(const char *title, const char *path);
int int
db_pl_add(struct playlist_info *pli, int *id); db_pl_add(struct playlist_info *pli);
int
db_pl_update(struct playlist_info *pli);
int int
db_pl_add_item_bypath(int plid, const char *path); db_pl_add_item_bypath(int plid, const char *path);
@ -681,9 +687,6 @@ db_pl_add_item_byid(int plid, int fileid);
void void
db_pl_clear_items(int id); db_pl_clear_items(int id);
int
db_pl_update(struct playlist_info *pli);
void void
db_pl_delete(int id); db_pl_delete(int id);

View File

@ -2182,6 +2182,7 @@ queue_tracks_add_playlist(const char *id, int pos)
static int static int
queue_tracks_add_byuris(const char *param, int pos, int *total_count) queue_tracks_add_byuris(const char *param, int pos, int *total_count)
{ {
struct player_status status;
char *uris; char *uris;
char *uri; char *uri;
char *ptr; char *ptr;
@ -2227,7 +2228,9 @@ queue_tracks_add_byuris(const char *param, int pos, int *total_count)
} }
else else
{ {
ret = library_queue_add(uri, pos, &count, NULL); player_get_status(&status);
ret = library_queue_item_add(uri, pos, status.shuffle, status.item_id, &count, NULL);
if (ret != LIBRARY_OK) if (ret != LIBRARY_OK)
{ {
DPRINTF(E_LOG, L_WEB, "Invalid uri '%s'\n", uri); DPRINTF(E_LOG, L_WEB, "Invalid uri '%s'\n", uri);

View File

@ -48,12 +48,22 @@
#include "listener.h" #include "listener.h"
#include "player.h" #include "player.h"
struct playlist_add_param struct playlist_item_add_param
{ {
const char *vp_playlist; const char *vp_playlist;
const char *vp_item; const char *vp_item;
}; };
struct queue_item_add_param
{
const char *path;
int position;
char reshuffle;
uint32_t item_id;
int *count;
int *new_item_id;
};
static struct commands_base *cmdbase; static struct commands_base *cmdbase;
static pthread_t tid_library; static pthread_t tid_library;
@ -95,6 +105,58 @@ static struct event *updateev;
static unsigned int deferred_update_notifications; static unsigned int deferred_update_notifications;
static short deferred_update_events; static short deferred_update_events;
/* ------------------- CALLED BY LIBRARY SOURCE MODULES -------------------- */
int
library_media_save(struct media_file_info *mfi)
{
if (!mfi->path || !mfi->fname)
{
DPRINTF(E_LOG, L_LIB, "Ignoring media file with missing values (path='%s', fname='%s', data_kind='%d')\n",
mfi->path, mfi->fname, mfi->data_kind);
return -1;
}
if (!mfi->directory_id || !mfi->virtual_path)
{
// Missing informations for virtual_path and directory_id (may) lead to misplaced appearance in mpd clients
DPRINTF(E_WARN, L_LIB, "Media file with missing values (path='%s', directory='%d', virtual_path='%s')\n",
mfi->path, mfi->directory_id, mfi->virtual_path);
}
if (mfi->id == 0)
return db_file_add(mfi);
else
return db_file_update(mfi);
}
int
library_playlist_save(struct playlist_info *pli)
{
if (!pli->path)
{
DPRINTF(E_LOG, L_LIB, "Ignoring playlist file with missing path\n");
return -1;
}
if (!pli->directory_id || !pli->virtual_path)
{
// Missing informations for virtual_path and directory_id (may) lead to misplaced appearance in mpd clients
DPRINTF(E_WARN, L_LIB, "Playlist with missing values (path='%s', directory='%d', virtual_path='%s')\n",
pli->path, pli->directory_id, pli->virtual_path);
}
if (pli->id == 0)
return db_pl_add(pli);
else
return db_pl_update(pli);
}
/* ---------------------- LIBRARY ABSTRACTION --------------------- */
/* thread: library */
static bool static bool
handle_deferred_update_notifications(void) handle_deferred_update_notifications(void)
{ {
@ -114,132 +176,6 @@ handle_deferred_update_notifications(void)
return ret; return ret;
} }
void
library_add_media(struct media_file_info *mfi)
{
if (!mfi->path || !mfi->fname)
{
DPRINTF(E_LOG, L_LIB, "Ignoring media file with missing values (path='%s', fname='%s', data_kind='%d')\n",
mfi->path, mfi->fname, mfi->data_kind);
return;
}
if (!mfi->directory_id || !mfi->virtual_path)
{
// Missing informations for virtual_path and directory_id (may) lead to misplaced appearance in mpd clients
DPRINTF(E_WARN, L_LIB, "Media file with missing values (path='%s', directory='%d', virtual_path='%s')\n",
mfi->path, mfi->directory_id, mfi->virtual_path);
}
if (mfi->id == 0)
db_file_add(mfi);
else
db_file_update(mfi);
}
int
library_queue_add(const char *path, int position, int *count, int *new_item_id)
{
struct player_status status;
int i;
int ret;
DPRINTF(E_DBG, L_LIB, "Add items for path '%s' to the queue\n", path);
player_get_status(&status);
ret = LIBRARY_PATH_INVALID;
for (i = 0; sources[i] && ret == LIBRARY_PATH_INVALID; i++)
{
if (sources[i]->disabled || !sources[i]->queue_add)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support queue_add\n", sources[i]->name);
continue;
}
ret = sources[i]->queue_add(path, position, status.shuffle, status.item_id, count, new_item_id);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Items for path '%s' from library source '%s' added to the queue\n", path, sources[i]->name);
break;
}
}
if (ret != LIBRARY_OK)
DPRINTF(E_LOG, L_LIB, "Failed to add items for path '%s' to the queue (%d)\n", path, ret);
return ret;
}
int
library_add_playlist_info(const char *path, const char *title, const char *virtual_path, enum pl_type type, int parent_pl_id, int dir_id)
{
struct playlist_info *pli;
int plid;
int ret;
pli = db_pl_fetch_bypath(path);
if (pli)
{
DPRINTF(E_DBG, L_LIB, "Playlist found ('%s', link %s), updating\n", title, path);
plid = pli->id;
pli->type = type;
free(pli->title);
pli->title = strdup(title);
if (pli->virtual_path)
free(pli->virtual_path);
pli->virtual_path = safe_strdup(virtual_path);
pli->directory_id = dir_id;
ret = db_pl_update(pli);
if (ret < 0)
{
DPRINTF(E_LOG, L_LIB, "Error updating playlist ('%s', link %s)\n", title, path);
free_pli(pli, 0);
return -1;
}
db_pl_clear_items(plid);
}
else
{
DPRINTF(E_DBG, L_LIB, "Adding playlist ('%s', link %s)\n", title, path);
pli = (struct playlist_info *)malloc(sizeof(struct playlist_info));
if (!pli)
{
DPRINTF(E_LOG, L_LIB, "Out of memory\n");
return -1;
}
memset(pli, 0, sizeof(struct playlist_info));
pli->type = type;
pli->title = strdup(title);
pli->path = strdup(path);
pli->virtual_path = safe_strdup(virtual_path);
pli->parent_id = parent_pl_id;
pli->directory_id = dir_id;
ret = db_pl_add(pli, &plid);
if ((ret < 0) || (plid < 1))
{
DPRINTF(E_LOG, L_LIB, "Error adding playlist ('%s', link %s, ret %d, plid %d)\n", title, path, ret, plid);
free_pli(pli, 0);
return -1;
}
}
free_pli(pli, 0);
return plid;
}
static void static void
purge_cruft(time_t start) purge_cruft(time_t start)
{ {
@ -378,9 +314,135 @@ fullrescan(void *arg, int *ret)
return COMMAND_END; return COMMAND_END;
} }
/* static enum command_state
* Callback to notify listeners of database changes playlist_item_add(void *arg, int *retval)
*/ {
struct playlist_item_add_param *param = arg;
int i;
int ret = LIBRARY_ERROR;
DPRINTF(E_DBG, L_LIB, "Adding item '%s' to playlist '%s'\n", param->vp_item, param->vp_playlist);
for (i = 0; sources[i]; i++)
{
if (sources[i]->disabled || !sources[i]->playlist_item_add)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support playlist_item_add\n", sources[i]->name);
continue;
}
ret = sources[i]->playlist_item_add(param->vp_playlist, param->vp_item);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Adding item '%s' to playlist '%s' with library source '%s'\n", param->vp_item, param->vp_playlist, sources[i]->name);
listener_notify(LISTENER_STORED_PLAYLIST);
break;
}
}
*retval = ret;
return COMMAND_END;
}
static enum command_state
playlist_remove(void *arg, int *retval)
{
const char *virtual_path = arg;
int i;
int ret = LIBRARY_ERROR;
DPRINTF(E_DBG, L_LIB, "Removing playlist at path '%s'\n", virtual_path);
for (i = 0; sources[i]; i++)
{
if (sources[i]->disabled || !sources[i]->playlist_remove)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support playlist_remove\n", sources[i]->name);
continue;
}
ret = sources[i]->playlist_remove(virtual_path);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Removing playlist '%s' with library source '%s'\n", virtual_path, sources[i]->name);
listener_notify(LISTENER_STORED_PLAYLIST);
break;
}
}
*retval = ret;
return COMMAND_END;
}
static enum command_state
queue_item_add(void *arg, int *retval)
{
struct queue_item_add_param *param = arg;
int i;
int ret;
DPRINTF(E_DBG, L_LIB, "Add items for path '%s' to the queue\n", param->path);
ret = LIBRARY_PATH_INVALID;
for (i = 0; sources[i] && ret == LIBRARY_PATH_INVALID; i++)
{
if (sources[i]->disabled || !sources[i]->queue_item_add)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support queue_add\n", sources[i]->name);
continue;
}
ret = sources[i]->queue_item_add(param->path, param->position, param->reshuffle, param->item_id, param->count, param->new_item_id);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Items for path '%s' from library source '%s' added to the queue\n", param->path, sources[i]->name);
break;
}
}
if (ret != LIBRARY_OK)
DPRINTF(E_LOG, L_LIB, "Failed to add items for path '%s' to the queue (%d)\n", param->path, ret);
*retval = ret;
return COMMAND_END;
}
static enum command_state
queue_save(void *arg, int *retval)
{
const char *virtual_path = arg;
int i;
int ret = LIBRARY_ERROR;
DPRINTF(E_DBG, L_LIB, "Saving queue to path '%s'\n", virtual_path);
for (i = 0; sources[i]; i++)
{
if (sources[i]->disabled || !sources[i]->queue_save)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support queue_save\n", sources[i]->name);
continue;
}
ret = sources[i]->queue_save(virtual_path);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Saving queue to path '%s' with library source '%s'\n", virtual_path, sources[i]->name);
listener_notify(LISTENER_STORED_PLAYLIST);
break;
}
}
*retval = ret;
return COMMAND_END;
}
// Callback to notify listeners of database changes
static void static void
update_trigger_cb(int fd, short what, void *arg) update_trigger_cb(int fd, short what, void *arg)
{ {
@ -410,7 +472,7 @@ update_trigger(void *arg, int *retval)
} }
/* --------------------------- LIBRARY INTERFACE -------------------------- */ /* ----------------------- LIBRARY EXTERNAL INTERFACE ---------------------- */
void void
library_rescan() library_rescan()
@ -437,6 +499,7 @@ library_metarescan()
scanning = true; // TODO Guard "scanning" with a mutex scanning = true; // TODO Guard "scanning" with a mutex
commands_exec_async(cmdbase, metarescan, NULL); commands_exec_async(cmdbase, metarescan, NULL);
} }
void void
library_fullrescan() library_fullrescan()
{ {
@ -494,39 +557,24 @@ initscan()
listener_notify(LISTENER_UPDATE); listener_notify(LISTENER_UPDATE);
} }
/*
* @return true if scan is running, otherwise false
*/
bool bool
library_is_scanning() library_is_scanning()
{ {
return scanning; return scanning;
} }
/*
* @param is_scanning true if scan is running, otherwise false
*/
void void
library_set_scanning(bool is_scanning) library_set_scanning(bool is_scanning)
{ {
scanning = is_scanning; scanning = is_scanning;
} }
/*
* @return true if a running scan should be aborted due to imminent shutdown, otherwise false
*/
bool bool
library_is_exiting() library_is_exiting()
{ {
return scan_exit; return scan_exit;
} }
/*
* Trigger for sending the DATABASE event
*
* Needs to be called, if an update to the database (library tables) occurred. The DATABASE event
* is emitted with the delay 'library_update_wait'. It is safe to call this function from any thread.
*/
void void
library_update_trigger(short update_events) library_update_trigger(short update_events)
{ {
@ -547,81 +595,17 @@ library_update_trigger(short update_events)
} }
} }
static enum command_state
playlist_add(void *arg, int *retval)
{
struct playlist_add_param *param = arg;
int i;
int ret = LIBRARY_ERROR;
DPRINTF(E_DBG, L_LIB, "Adding item '%s' to playlist '%s'\n", param->vp_item, param->vp_playlist);
for (i = 0; sources[i]; i++)
{
if (sources[i]->disabled || !sources[i]->playlist_add)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support playlist_add\n", sources[i]->name);
continue;
}
ret = sources[i]->playlist_add(param->vp_playlist, param->vp_item);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Adding item '%s' to playlist '%s' with library source '%s'\n", param->vp_item, param->vp_playlist, sources[i]->name);
listener_notify(LISTENER_STORED_PLAYLIST);
break;
}
}
*retval = ret;
return COMMAND_END;
}
int int
library_playlist_add(const char *vp_playlist, const char *vp_item) library_playlist_item_add(const char *vp_playlist, const char *vp_item)
{ {
struct playlist_add_param param; struct playlist_item_add_param param;
if (library_is_scanning()) if (library_is_scanning())
return -1; return -1;
param.vp_playlist = vp_playlist; param.vp_playlist = vp_playlist;
param.vp_item = vp_item; param.vp_item = vp_item;
return commands_exec_sync(cmdbase, playlist_add, NULL, &param); return commands_exec_sync(cmdbase, playlist_item_add, NULL, &param);
}
static enum command_state
playlist_remove(void *arg, int *retval)
{
const char *virtual_path;
int i;
int ret = LIBRARY_ERROR;
virtual_path = arg;
DPRINTF(E_DBG, L_LIB, "Removing playlist at path '%s'\n", virtual_path);
for (i = 0; sources[i]; i++)
{
if (sources[i]->disabled || !sources[i]->playlist_remove)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support playlist_remove\n", sources[i]->name);
continue;
}
ret = sources[i]->playlist_remove(virtual_path);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Removing playlist '%s' with library source '%s'\n", virtual_path, sources[i]->name);
listener_notify(LISTENER_STORED_PLAYLIST);
break;
}
}
*retval = ret;
return COMMAND_END;
} }
int int
@ -633,39 +617,6 @@ library_playlist_remove(char *virtual_path)
return commands_exec_sync(cmdbase, playlist_remove, NULL, virtual_path); return commands_exec_sync(cmdbase, playlist_remove, NULL, virtual_path);
} }
static enum command_state
queue_save(void *arg, int *retval)
{
const char *virtual_path;
int i;
int ret = LIBRARY_ERROR;
virtual_path = arg;
DPRINTF(E_DBG, L_LIB, "Saving queue to path '%s'\n", virtual_path);
for (i = 0; sources[i]; i++)
{
if (sources[i]->disabled || !sources[i]->queue_save)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support queue_save\n", sources[i]->name);
continue;
}
ret = sources[i]->queue_save(virtual_path);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Saving queue to path '%s' with library source '%s'\n", virtual_path, sources[i]->name);
listener_notify(LISTENER_STORED_PLAYLIST);
break;
}
}
*retval = ret;
return COMMAND_END;
}
int int
library_queue_save(char *path) library_queue_save(char *path)
{ {
@ -675,15 +626,24 @@ library_queue_save(char *path)
return commands_exec_sync(cmdbase, queue_save, NULL, path); return commands_exec_sync(cmdbase, queue_save, NULL, path);
} }
/* int
* Execute the function 'func' with the given argument 'arg' in the library thread. library_queue_item_add(const char *path, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id)
* {
* The pointer passed as argument is freed in the library thread after func returned. struct queue_item_add_param param;
*
* @param func The function to be executed if (library_is_scanning())
* @param arg Argument passed to func return -1;
* @return 0 if triggering the function execution succeeded, -1 on failure.
*/ param.path = path;
param.position = position;
param.reshuffle = reshuffle;
param.item_id = item_id;
param.count = count;
param.new_item_id = new_item_id;
return commands_exec_sync(cmdbase, queue_item_add, NULL, &param);
}
int int
library_exec_async(command_function func, void *arg) library_exec_async(command_function func, void *arg)
{ {

View File

@ -72,9 +72,9 @@ struct library_source
int (*fullrescan)(void); int (*fullrescan)(void);
/* /*
* Save queue as a new playlist under the given virtual path * Add item to playlist
*/ */
int (*playlist_add)(const char *vp_playlist, const char *vp_item); int (*playlist_item_add)(const char *vp_playlist, const char *vp_item);
/* /*
* Removes the playlist under the given virtual path * Removes the playlist under the given virtual path
@ -89,17 +89,24 @@ struct library_source
/* /*
* Add item for the given path to the current queue * Add item for the given path to the current queue
*/ */
int (*queue_add)(const char *path, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id); int (*queue_item_add)(const char *path, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id);
}; };
void /* --------------------- Interface towards source backends ----------------- */
library_add_media(struct media_file_info *mfi);
int int
library_add_playlist_info(const char *path, const char *title, const char *virtual_path, enum pl_type type, int parent_pl_id, int dir_id); library_media_save(struct media_file_info *mfi);
/*
* Adds a playlist if pli->id == 0, otherwise updates.
*
* @param pli Playlist to save
* @return playlist id if operation succeeded, -1 on failure.
*/
int int
library_queue_add(const char *path, int position, int *count, int *new_item_id); library_playlist_save(struct playlist_info *pli);
/* ------------------------ Library external interface --------------------- */
void void
library_rescan(); library_rescan();
@ -110,20 +117,35 @@ library_metarescan();
void void
library_fullrescan(); library_fullrescan();
/*
* @return true if scan is running, otherwise false
*/
bool bool
library_is_scanning(); library_is_scanning();
/*
* @param is_scanning true if scan is running, otherwise false
*/
void void
library_set_scanning(bool is_scanning); library_set_scanning(bool is_scanning);
/*
* @return true if a running scan should be aborted due to imminent shutdown, otherwise false
*/
bool bool
library_is_exiting(); library_is_exiting();
/*
* Trigger for sending the DATABASE event
*
* Needs to be called, if an update to the database (library tables) occurred. The DATABASE event
* is emitted with the delay 'library_update_wait'. It is safe to call this function from any thread.
*/
void void
library_update_trigger(short update_events); library_update_trigger(short update_events);
int int
library_playlist_add(const char *vp_playlist, const char *vp_item); library_playlist_item_add(const char *vp_playlist, const char *vp_item);
int int
library_playlist_remove(char *virtual_path); library_playlist_remove(char *virtual_path);
@ -131,6 +153,18 @@ library_playlist_remove(char *virtual_path);
int int
library_queue_save(char *path); library_queue_save(char *path);
int
library_queue_item_add(const char *path, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id);
/*
* Execute the function 'func' with the given argument 'arg' in the library thread.
*
* The pointer passed as argument is freed in the library thread after func returned.
*
* @param func The function to be executed
* @param arg Argument passed to func
* @return 0 if triggering the function execution succeeded, -1 on failure.
*/
int int
library_exec_async(command_function func, void *arg); library_exec_async(command_function func, void *arg);

View File

@ -165,57 +165,47 @@ static int
filescanner_fullrescan(); filescanner_fullrescan();
const char * /* ----------------------- Internal utility functions --------------------- */
filename_from_path(const char *path)
static int
virtual_path_make(char *virtual_path, int virtual_path_len, const char *path)
{ {
const char *filename; int ret;
filename = strrchr(path, '/'); ret = snprintf(virtual_path, virtual_path_len, "/file:%s", path);
if ((!filename) || (strlen(filename) == 1)) if ((ret < 0) || (ret >= virtual_path_len))
filename = path; {
else DPRINTF(E_LOG, L_SCAN, "Virtual path '/file:%s', virtual_path_len exceeded (%d/%d)\n", path, ret, virtual_path_len);
filename++;
return filename;
}
char *
strip_extension(const char *path)
{
char *ptr;
char *result;
result = strdup(path);
ptr = strrchr(result, '.');
if (ptr)
*ptr = '\0';
return result;
}
int
parent_dir(const char **current, const char *path)
{
const char *ptr;
if (*current)
ptr = *current;
else
ptr = strrchr(path, '/');
if (!ptr || (ptr == path))
return -1; return -1;
}
for (ptr--; (ptr > path) && (*ptr != '/'); ptr--)
;
*current = ptr;
return 0; return 0;
} }
static int static int
push_dir(struct stacked_dir **s, char *path, int parent_id) 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 = virtual_path_make(virtual_path, sizeof(virtual_path), parent_dir);
if (ret == 0)
parent_id = db_directory_id_byvirtualpath(virtual_path);
else
parent_id = 0;
free(pathcopy);
return parent_id;
}
static int
push_dir(struct stacked_dir **s, const char *path, int parent_id)
{ {
struct stacked_dir *d; struct stacked_dir *d;
@ -386,6 +376,108 @@ file_type_get(const char *path) {
return FILE_REGULAR; return FILE_REGULAR;
} }
/* ----------------- Utility functions used by the scanners --------------- */
const char *
filename_from_path(const char *path)
{
const char *filename;
filename = strrchr(path, '/');
if ((!filename) || (strlen(filename) == 1))
filename = path;
else
filename++;
return filename;
}
char *
strip_extension(const char *path)
{
char *ptr;
char *result;
result = strdup(path);
ptr = strrchr(result, '.');
if (ptr)
*ptr = '\0';
return result;
}
int
parent_dir(const char **current, const char *path)
{
const char *ptr;
if (*current)
ptr = *current;
else
ptr = strrchr(path, '/');
if (!ptr || (ptr == path))
return -1;
for (ptr--; (ptr > path) && (*ptr != '/'); ptr--)
;
*current = ptr;
return 0;
}
int
playlist_fill(struct playlist_info *pli, const char *path)
{
const char *filename;
char virtual_path[PATH_MAX];
int ret;
filename = filename_from_path(path);
ret = virtual_path_make(virtual_path, sizeof(virtual_path), path);
if (ret < 0)
return -1;
memset(pli, 0, sizeof(struct playlist_info));
pli->type = PL_PLAIN;
pli->path = strdup(path);
pli->title = strip_extension(filename); // Will alloc
pli->virtual_path = strip_extension(virtual_path); // Will alloc
pli->directory_id = get_parent_dir_id(path);
return 0;
}
int
playlist_add(const char *path)
{
struct playlist_info pli;
int ret;
ret = playlist_fill(&pli, path);
if (ret < 0)
return -1;
ret = library_playlist_save(&pli);
if (ret < 0)
{
free_pli(&pli, 1);
return -1;
}
free_pli(&pli, 1);
return ret;
}
/* --------------------------- Processing procedures ---------------------- */
static void static void
process_playlist(char *file, time_t mtime, int dir_id) process_playlist(char *file, time_t mtime, int dir_id)
{ {
@ -530,7 +622,7 @@ process_regular_file(const char *file, struct stat *sb, int type, int flags, int
mfi.album_artist = safe_strdup(cfg_getstr(cfg_getsec(cfg, "library"), "compilation_artist")); mfi.album_artist = safe_strdup(cfg_getstr(cfg_getsec(cfg, "library"), "compilation_artist"));
} }
ret = scan_metadata_ffmpeg(file, &mfi); ret = scan_metadata_ffmpeg(&mfi, file);
if (ret < 0) if (ret < 0)
{ {
free_mfi(&mfi, 1); free_mfi(&mfi, 1);
@ -538,7 +630,7 @@ process_regular_file(const char *file, struct stat *sb, int type, int flags, int
} }
} }
library_add_media(&mfi); library_media_save(&mfi);
cache_artwork_ping(file, sb->st_mtime, !is_bulkscan); cache_artwork_ping(file, sb->st_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
@ -674,21 +766,6 @@ check_speciallib(char *path, const char *libtype)
return 0; return 0;
} }
/* 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;
}
/* /*
* Returns informations about the attributes of the file at the given 'path' in the structure * Returns informations about the attributes of the file at the given 'path' in the structure
* pointed to by 'sb'. * pointed to by 'sb'.
@ -766,7 +843,7 @@ process_directory(char *path, int parent_id, int flags)
/* Add/update directories table */ /* Add/update directories table */
ret = create_virtual_path(path, virtual_path, sizeof(virtual_path)); ret = virtual_path_make(virtual_path, sizeof(virtual_path), path);
if (ret < 0) if (ret < 0)
return; return;
@ -893,7 +970,7 @@ process_parent_directories(char *path)
strncpy(buf, path, (ptr - path)); strncpy(buf, path, (ptr - path));
buf[(ptr - path)] = '\0'; buf[(ptr - path)] = '\0';
ret = create_virtual_path(buf, virtual_path, sizeof(virtual_path)); ret = virtual_path_make(virtual_path, sizeof(virtual_path), buf);
if (ret < 0) if (ret < 0)
return 0; return 0;
@ -1017,28 +1094,6 @@ bulk_scan(int flags)
} }
} }
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;
}
static int static int
watches_clear(uint32_t wd, char *path) watches_clear(uint32_t wd, char *path)
{ {
@ -1222,7 +1277,6 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
uint32_t path_hash; uint32_t path_hash;
char *file = path; char *file = path;
char resolved_path[PATH_MAX]; char resolved_path[PATH_MAX];
char *dir;
char dir_vpath[PATH_MAX]; char dir_vpath[PATH_MAX];
int type; int type;
int i; int i;
@ -1291,14 +1345,12 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
if (ret > 0) if (ret > 0)
{ {
// If file was successfully enabled, update the directory id ret = virtual_path_make(dir_vpath, sizeof(dir_vpath), path);
dir = strdup(path);
ptr = strrchr(dir, '/');
dir[(ptr - dir)] = '\0';
ret = create_virtual_path(dir, dir_vpath, sizeof(dir_vpath));
if (ret >= 0) if (ret >= 0)
{ {
ptr = strrchr(dir_vpath, '/');
*ptr = '\0';
dir_id = db_directory_id_byvirtualpath(dir_vpath); dir_id = db_directory_id_byvirtualpath(dir_vpath);
if (dir_id > 0) if (dir_id > 0)
{ {
@ -1307,8 +1359,6 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
DPRINTF(E_LOG, L_SCAN, "Error updating directory id for file: %s\n", path); DPRINTF(E_LOG, L_SCAN, "Error updating directory id for file: %s\n", path);
} }
} }
free(dir);
} }
else else
{ {
@ -1719,7 +1769,7 @@ map_media_file_to_queue_item(struct db_queue_item *queue_item, struct media_file
} }
static int static int
queue_add_stream(const char *path, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id) queue_item_stream_add(const char *path, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id)
{ {
struct media_file_info mfi; struct media_file_info mfi;
struct db_queue_item item; struct db_queue_item item;
@ -1728,7 +1778,7 @@ queue_add_stream(const char *path, int position, char reshuffle, uint32_t item_i
memset(&mfi, 0, sizeof(struct media_file_info)); memset(&mfi, 0, sizeof(struct media_file_info));
scan_metadata_stream(path, &mfi); scan_metadata_stream(&mfi, path);
map_media_file_to_queue_item(&item, &mfi); map_media_file_to_queue_item(&item, &mfi);
@ -1753,11 +1803,11 @@ queue_add_stream(const char *path, int position, char reshuffle, uint32_t item_i
} }
static int static int
queue_add(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id) queue_item_add(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id)
{ {
if (strncasecmp(uri, "http://", strlen("http://")) == 0 || strncasecmp(uri, "https://", strlen("https://")) == 0) if (strncasecmp(uri, "http://", strlen("http://")) == 0 || strncasecmp(uri, "https://", strlen("https://")) == 0)
{ {
queue_add_stream(uri, position, reshuffle, item_id, count, new_item_id); queue_item_stream_add(uri, position, reshuffle, item_id, count, new_item_id);
return LIBRARY_OK; return LIBRARY_OK;
} }
@ -1828,7 +1878,7 @@ has_suffix(const char *file, const char *suffix)
* Returns NULL on error and a new allocated path on success. * Returns NULL on error and a new allocated path on success.
*/ */
static char * static char *
get_playlist_path(const char *vp_playlist) playlist_path_create(const char *vp_playlist)
{ {
const char *path; const char *path;
char *pl_path; char *pl_path;
@ -1863,27 +1913,6 @@ get_playlist_path(const char *vp_playlist)
return pl_path; return pl_path;
} }
static int
get_playlist_id(const char *pl_path, const char *vp_playlist)
{
const char *filename;
char *title;
int dir_id;
int pl_id;
pl_id = db_pl_id_bypath(pl_path);
if (pl_id < 0)
{
dir_id = get_parent_dir_id(pl_path);
filename = filename_from_path(pl_path);
title = strip_extension(filename);
pl_id = library_add_playlist_info(pl_path, title, vp_playlist, PL_PLAIN, 0, dir_id);
free(title);
}
return pl_id;
}
static int static int
playlist_add_path(FILE *fp, int pl_id, const char *path) playlist_add_path(FILE *fp, int pl_id, const char *path)
{ {
@ -1949,8 +1978,8 @@ playlist_add_files(FILE *fp, int pl_id, const char *virtual_path)
DPRINTF(E_DBG, L_SCAN, "Scan stream '%s' and add to playlist (id = %d)\n", path, pl_id); DPRINTF(E_DBG, L_SCAN, "Scan stream '%s' and add to playlist (id = %d)\n", path, pl_id);
memset(&mfi, 0, sizeof(struct media_file_info)); memset(&mfi, 0, sizeof(struct media_file_info));
scan_metadata_stream(path, &mfi); scan_metadata_stream(&mfi, path);
library_add_media(&mfi); library_media_save(&mfi);
free_mfi(&mfi, 1); free_mfi(&mfi, 1);
ret = playlist_add_path(fp, pl_id, path); ret = playlist_add_path(fp, pl_id, path);
@ -1968,14 +1997,14 @@ playlist_add_files(FILE *fp, int pl_id, const char *virtual_path)
} }
static int static int
playlist_add(const char *vp_playlist, const char *vp_item) playlist_item_add(const char *vp_playlist, const char *vp_item)
{ {
char *pl_path; char *pl_path;
FILE *fp; FILE *fp;
int pl_id; int pl_id;
int ret; int ret;
pl_path = get_playlist_path(vp_playlist); pl_path = playlist_path_create(vp_playlist);
if (!pl_path) if (!pl_path)
return LIBRARY_PATH_INVALID; return LIBRARY_PATH_INVALID;
@ -1983,31 +2012,36 @@ playlist_add(const char *vp_playlist, const char *vp_item)
if (!fp) if (!fp)
{ {
DPRINTF(E_LOG, L_SCAN, "Error opening file '%s' for writing: %d\n", pl_path, errno); DPRINTF(E_LOG, L_SCAN, "Error opening file '%s' for writing: %d\n", pl_path, errno);
free(pl_path); goto error;
return LIBRARY_ERROR;
} }
pl_id = get_playlist_id(pl_path, vp_playlist); pl_id = db_pl_id_bypath(pl_path);
free(pl_path);
if (pl_id < 0) if (pl_id < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Could not get playlist id for %s\n", vp_playlist); pl_id = playlist_add(pl_path);
fclose(fp); if (pl_id < 0)
return LIBRARY_ERROR; goto error;
} }
ret = playlist_add_files(fp, pl_id, vp_item); ret = playlist_add_files(fp, pl_id, vp_item);
fclose(fp);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Could not add %s to playlist\n", vp_item); DPRINTF(E_LOG, L_SCAN, "Could not add %s to playlist\n", vp_item);
return LIBRARY_ERROR; goto error;
} }
fclose(fp);
free(pl_path);
db_pl_ping(pl_id); db_pl_ping(pl_id);
return LIBRARY_OK; return LIBRARY_OK;
error:
if (fp)
fclose(fp);
free(pl_path);
return LIBRARY_ERROR;
} }
static int static int
@ -2018,7 +2052,7 @@ playlist_remove(const char *vp_playlist)
int pl_id; int pl_id;
int ret; int ret;
pl_path = get_playlist_path(vp_playlist); pl_path = playlist_path_create(vp_playlist);
if (!pl_path) if (!pl_path)
{ {
DPRINTF(E_LOG, L_SCAN, "Unsupported virtual path '%s'\n", vp_playlist); DPRINTF(E_LOG, L_SCAN, "Unsupported virtual path '%s'\n", vp_playlist);
@ -2059,7 +2093,7 @@ queue_save(const char *virtual_path)
int pl_id; int pl_id;
int ret; int ret;
pl_path = get_playlist_path(virtual_path); pl_path = playlist_path_create(virtual_path);
if (!pl_path) if (!pl_path)
return LIBRARY_PATH_INVALID; return LIBRARY_PATH_INVALID;
@ -2067,17 +2101,15 @@ queue_save(const char *virtual_path)
if (!fp) if (!fp)
{ {
DPRINTF(E_LOG, L_SCAN, "Error opening file '%s' for writing: %d\n", pl_path, errno); DPRINTF(E_LOG, L_SCAN, "Error opening file '%s' for writing: %d\n", pl_path, errno);
free(pl_path); goto error;
return LIBRARY_ERROR;
} }
pl_id = get_playlist_id(pl_path, virtual_path); pl_id = db_pl_id_bypath(pl_path);
free(pl_path);
if (pl_id < 0) if (pl_id < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Could not get playlist id for %s\n", virtual_path); pl_id = playlist_add(pl_path);
fclose(fp); if (pl_id < 0)
return LIBRARY_ERROR; goto error;
} }
memset(&query_params, 0, sizeof(struct query_params)); memset(&query_params, 0, sizeof(struct query_params));
@ -2085,8 +2117,7 @@ queue_save(const char *virtual_path)
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Failed to start queue enum\n"); DPRINTF(E_LOG, L_SCAN, "Failed to start queue enum\n");
fclose(fp); goto error;
return LIBRARY_ERROR;
} }
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0) while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
@ -2105,8 +2136,8 @@ queue_save(const char *virtual_path)
DPRINTF(E_DBG, L_SCAN, "Scan stream '%s' and add to playlist (id = %d)\n", queue_item.path, pl_id); DPRINTF(E_DBG, L_SCAN, "Scan stream '%s' and add to playlist (id = %d)\n", queue_item.path, pl_id);
memset(&mfi, 0, sizeof(struct media_file_info)); memset(&mfi, 0, sizeof(struct media_file_info));
scan_metadata_stream(queue_item.path, &mfi); scan_metadata_stream(&mfi, queue_item.path);
library_add_media(&mfi); library_media_save(&mfi);
free_mfi(&mfi, 1); free_mfi(&mfi, 1);
} }
else else
@ -2131,7 +2162,9 @@ queue_save(const char *virtual_path)
} }
db_queue_enum_end(&query_params); db_queue_enum_end(&query_params);
fclose(fp); fclose(fp);
free(pl_path);
db_pl_ping(pl_id); db_pl_ping(pl_id);
@ -2139,6 +2172,12 @@ queue_save(const char *virtual_path)
return LIBRARY_ERROR; return LIBRARY_ERROR;
return LIBRARY_OK; return LIBRARY_OK;
error:
if (fp)
fclose(fp);
free(pl_path);
return LIBRARY_ERROR;
} }
/* Thread: main */ /* Thread: main */
@ -2174,8 +2213,8 @@ struct library_source filescanner =
.rescan = filescanner_rescan, .rescan = filescanner_rescan,
.metarescan = filescanner_metarescan, .metarescan = filescanner_metarescan,
.fullrescan = filescanner_fullrescan, .fullrescan = filescanner_fullrescan,
.playlist_add = playlist_add, .playlist_item_add = playlist_item_add,
.playlist_remove = playlist_remove, .playlist_remove = playlist_remove,
.queue_save = queue_save, .queue_save = queue_save,
.queue_add = queue_add, .queue_item_add = queue_item_add,
}; };

View File

@ -8,10 +8,10 @@
/* --------------------------- Actual scanners ---------------------------- */ /* --------------------------- Actual scanners ---------------------------- */
int int
scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi); scan_metadata_ffmpeg(struct media_file_info *mfi, const char *file);
void void
scan_metadata_stream(const char *path, struct media_file_info *mfi); scan_metadata_stream(struct media_file_info *mfi, const char *path);
void void
scan_playlist(const char *file, time_t mtime, int dir_id); scan_playlist(const char *file, time_t mtime, int dir_id);
@ -60,4 +60,23 @@ strip_extension(const char *path);
int int
parent_dir(const char **current, const char *path); parent_dir(const char **current, const char *path);
/* Fills a playlist struct with default values based on path. The title will
* for instance be set to the base filename without file extension. Since
* the fields in the struct are alloc'ed, caller must free with free_pli().
*
* @out pli the playlist struct to be filled
* @in path the path to the playlist
* @return 0 if ok, negative on error
*/
int
playlist_fill(struct playlist_info *pli, const char *path);
/* Adds a playlist to the database with the fields set by playlist_fill()
*
* @in path the path to the playlist
* @return the id of the playlist (pli->id), negative on error
*/
int
playlist_add(const char *path);
#endif /* !__FILESCANNER_H__ */ #endif /* !__FILESCANNER_H__ */

View File

@ -354,7 +354,7 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
* - fname: (filename) used as fallback for artist * - fname: (filename) used as fallback for artist
*/ */
int int
scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) scan_metadata_ffmpeg(struct media_file_info *mfi, const char *file)
{ {
AVFormatContext *ctx; AVFormatContext *ctx;
AVDictionary *options; AVDictionary *options;

View File

@ -21,6 +21,7 @@
#endif #endif
#include <stdio.h> #include <stdio.h>
#include <stdbool.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -41,6 +42,7 @@
#include "logger.h" #include "logger.h"
#include "db.h" #include "db.h"
#include "library.h"
#include "library/filescanner.h" #include "library/filescanner.h"
#include "conffile.h" #include "conffile.h"
#include "misc.h" #include "misc.h"
@ -787,13 +789,11 @@ process_pls(plist_t playlists, const char *file)
{ {
plist_t pl; plist_t pl;
plist_t items; plist_t items;
struct playlist_info *pli; struct playlist_info pli;
char *name; char *name;
uint64_t id; uint64_t id;
int pl_id;
uint32_t alen; uint32_t alen;
uint32_t i; uint32_t i;
char virtual_path[PATH_MAX];
int ret; int ret;
alen = plist_array_get_size(playlists); alen = plist_array_get_size(playlists);
@ -833,47 +833,40 @@ process_pls(plist_t playlists, const char *file)
continue; continue;
} }
CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info))); playlist_fill(&pli, file);
pli->type = PL_PLAIN; free(pli.title);
pli->title = strdup(name); pli.title = strdup(name);
pli->path = strdup(file); free(pli.virtual_path);
snprintf(virtual_path, sizeof(virtual_path), "/file:%s/%s", file, name); pli.virtual_path = safe_asprintf("/file:%s/%s", file, name);
pli->virtual_path = strdup(virtual_path);
ret = db_pl_add(pli, &pl_id); ret = library_playlist_save(&pli);
free_pli(pli, 0);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Error adding iTunes playlist '%s' (%s)\n", name, file); DPRINTF(E_LOG, L_SCAN, "Error adding iTunes playlist '%s' (%s)\n", name, file);
free_pli(&pli, 1);
free(name); free(name);
continue; continue;
} }
DPRINTF(E_INFO, L_SCAN, "Added playlist as id %d\n", pl_id); DPRINTF(E_INFO, L_SCAN, "Added playlist as id %d\n", ret);
process_pl_items(items, pl_id, name); process_pl_items(items, ret, name);
free_pli(&pli, 1);
free(name); free(name);
} }
} }
static bool
void itml_is_modified(const char *path, time_t mtime)
scan_itunes_itml(const char *file, time_t mtime, int dir_id)
{ {
struct playlist_info *pli; struct playlist_info *pli;
struct stat sb;
char buf[PATH_MAX];
char *itml_xml;
plist_t itml;
plist_t node;
int fd;
int ret; int ret;
// This is special playlist that is disabled and only used for saving a timestamp // This is a special playlist that is disabled and only used for saving a timestamp
pli = db_pl_fetch_bytitlepath(file, file); pli = db_pl_fetch_bytitlepath(path, path);
if (pli) if (pli)
{ {
// mtime == db_timestamp is also treated as a modification because some editors do // mtime == db_timestamp is also treated as a modification because some editors do
@ -882,48 +875,60 @@ scan_itunes_itml(const char *file, time_t mtime, int dir_id)
// is equal to the newly updated db_timestamp) // is equal to the newly updated db_timestamp)
if (mtime && (pli->db_timestamp > mtime)) if (mtime && (pli->db_timestamp > mtime))
{ {
DPRINTF(E_LOG, L_SCAN, "Unchanged iTunes XML found, not processing '%s'\n", file); DPRINTF(E_LOG, L_SCAN, "Unchanged iTunes XML found, not processing '%s'\n", path);
// TODO Protect the radio stations from purge after scan // TODO Protect the radio stations from purge after scan
db_pl_ping_bymatch(file, 0); db_pl_ping_bymatch(path, 0);
free_pli(pli, 0); free_pli(pli, 0);
return; return false;
} }
DPRINTF(E_LOG, L_SCAN, "Modified iTunes XML found, processing '%s'\n", file); DPRINTF(E_LOG, L_SCAN, "Modified iTunes XML found, processing '%s'\n", path);
// Clear out everything, we will recreate // Clear out everything, we will recreate
db_pl_delete_bypath(file); db_pl_delete_bypath(path);
free_pli(pli, 0);
} }
else else
{ {
DPRINTF(E_LOG, L_SCAN, "New iTunes XML found, processing: '%s'\n", file); DPRINTF(E_LOG, L_SCAN, "New iTunes XML found, processing: '%s'\n", path);
CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));
} }
CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info))); // Prepare the special meta playlist used for saving timestamp
playlist_fill(pli, path);
free(pli->title);
pli->title = strdup(path);
pli->type = PL_PLAIN; ret = library_playlist_save(pli);
pli->title = strdup(file);
pli->path = strdup(file);
snprintf(buf, sizeof(buf), "/file:%s", file);
pli->virtual_path = strip_extension(buf);
pli->directory_id = dir_id;
ret = db_pl_add(pli, (int *)&pli->id);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Error adding iTunes XML meta playlist '%s'\n", file); DPRINTF(E_LOG, L_SCAN, "Error adding iTunes XML meta playlist '%s'\n", path);
free_pli(pli, 0); free_pli(pli, 0);
return; return false;
} }
// Disable, only used for saving timestamp
db_pl_disable_bypath(file, STRIP_NONE, 0);
free_pli(pli, 0); free_pli(pli, 0);
// Disable, only used for saving timestamp
db_pl_disable_bypath(path, STRIP_NONE, 0);
return true;
}
void
scan_itunes_itml(const char *file, time_t mtime, int dir_id)
{
struct stat sb;
char *itml_xml;
plist_t itml;
plist_t node;
int fd;
int ret;
if (!itml_is_modified(file, mtime))
return;
fd = open(file, O_RDONLY); fd = open(file, O_RDONLY);
if (fd < 0) if (fd < 0)
{ {

View File

@ -32,48 +32,109 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <errno.h> #include <errno.h>
#include "conffile.h"
#include "logger.h" #include "logger.h"
#include "db.h" #include "db.h"
#include "library/filescanner.h" #include "library/filescanner.h"
#include "misc.h" #include "misc.h"
#include "library.h" #include "library.h"
/* Formats we can read so far */ // Formats we can read so far
#define PLAYLIST_PLS 1 enum playlist_type
#define PLAYLIST_M3U 2 {
PLAYLIST_UNKNOWN = 0,
PLAYLIST_PLS,
PLAYLIST_M3U,
};
/* Get metadata from the EXTINF tag */ static enum playlist_type
static int playlist_type(const char *path)
extinf_get(char *string, struct media_file_info *mfi, int *extinf)
{ {
char *ptr; char *ptr;
if (strncmp(string, "#EXTINF:", strlen("#EXTINF:")) != 0) ptr = strrchr(path, '.');
return 0; if (!ptr)
return PLAYLIST_UNKNOWN;
ptr = strchr(string, ','); if (strcasecmp(ptr, ".m3u") == 0)
if (!ptr || strlen(ptr) < 2) return PLAYLIST_M3U;
return 0; else if (strcasecmp(ptr, ".pls") == 0)
return PLAYLIST_PLS;
/* New extinf found, so clear old data */
free_mfi(mfi, 1);
*extinf = 1;
mfi->artist = strdup(ptr + 1);
ptr = strstr(mfi->artist, " -");
if (ptr && strlen(ptr) > 3)
mfi->title = strdup(ptr + 3);
else else
mfi->title = strdup(""); return PLAYLIST_UNKNOWN;
}
static int
extinf_read(char **artist, char **title, const char *tag)
{
char *ptr;
ptr = strchr(tag, ',');
if (!ptr || strlen(ptr) < 2)
return -1;
*artist = strdup(ptr + 1);
ptr = strstr(*artist, " -");
if (ptr && strlen(ptr) > 3)
*title = strdup(ptr + 3);
else
*title = strdup("");
if (ptr) if (ptr)
*ptr = '\0'; *ptr = '\0';
return 1; return 0;
}
static int
extval_read(char **val, const char *tag)
{
char *ptr;
ptr = strchr(tag, ':');
if (!ptr || strlen(ptr) < 2)
return -1;
*val = strdup(ptr + 1);
return 0;
}
// Get metadata from a EXTINF or EXTALB tag
static int
exttag_read(struct media_file_info *mfi, const char *tag)
{
char *artist;
char *title;
char *val;
if (strncmp(tag, "#EXTINF:", strlen("#EXTINF:")) == 0 && extinf_read(&artist, &title, tag) == 0)
{
free(mfi->artist);
free(mfi->title);
mfi->artist = artist;
mfi->title = title;
if (!mfi->album_artist)
mfi->album_artist = strdup(artist);
return 0;
}
if (strncmp(tag, "#EXTALB:", strlen("#EXTALB:")) == 0 && extval_read(&val, tag) == 0)
{
free(mfi->album);
mfi->album = val;
return 0;
}
if (strncmp(tag, "#EXTART:", strlen("#EXTART:")) == 0 && extval_read(&val, tag) == 0)
{
free(mfi->album_artist);
mfi->album_artist = val;
return 0;
}
return -1;
} }
void void
scan_metadata_stream(const char *path, struct media_file_info *mfi) scan_metadata_stream(struct media_file_info *mfi, const char *path)
{ {
char *pos; char *pos;
int ret; int ret;
@ -91,7 +152,7 @@ scan_metadata_stream(const char *path, struct media_file_info *mfi)
mfi->time_modified = time(NULL); mfi->time_modified = time(NULL);
mfi->directory_id = DIR_HTTP; mfi->directory_id = DIR_HTTP;
ret = scan_metadata_ffmpeg(path, mfi); ret = scan_metadata_ffmpeg(mfi, path);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Playlist URL '%s' is unavailable for probe/metadata, assuming MP3 encoding\n", path); DPRINTF(E_LOG, L_SCAN, "Playlist URL '%s' is unavailable for probe/metadata, assuming MP3 encoding\n", path);
@ -104,12 +165,95 @@ scan_metadata_stream(const char *path, struct media_file_info *mfi)
mfi->title = strdup(mfi->fname); mfi->title = strdup(mfi->fname);
} }
static int
process_nested_playlist(int parent_id, const char *path)
{
struct playlist_info *pli;
int ret;
// First set the type of the parent playlist to folder
pli = db_pl_fetch_byid(parent_id);
if (!pli)
goto error;
pli->type = PL_FOLDER;
ret = library_playlist_save(pli);
if (ret < 0)
goto error;
free_pli(pli, 0);
// Do we already have the playlist in the database?
pli = db_pl_fetch_bypath(path);
if (!pli)
{
pli = calloc(1, sizeof(struct playlist_info));
ret = playlist_fill(pli, path);
if (ret < 0)
goto error;
// This is a "trick" to make sure the nested playlist will be scanned.
// Otherwise what could happen is that we save the playlist with current
// db_timestamp, and when the scanner finds the actual playlist it will
// conclude from the timestamp that the playlist is unchanged, and thus
// it would never be scanned.
pli->db_timestamp = 1;
}
pli->parent_id = parent_id;
ret = library_playlist_save(pli);
if (ret < 0)
goto error;
free_pli(pli, 0);
return 0;
error:
DPRINTF(E_LOG, L_SCAN, "Error processing nested playlist '%s' in playlist %d\n", path, parent_id);
free_pli(pli, 0);
return -1;
}
static int static int
process_url(int pl_id, const char *path, struct media_file_info *mfi) process_url(int pl_id, const char *path, struct media_file_info *mfi)
{ {
struct media_file_info m3u;
int ret;
mfi->id = db_file_id_bypath(path); mfi->id = db_file_id_bypath(path);
scan_metadata_stream(path, mfi);
library_add_media(mfi); if (cfg_getbool(cfg_getsec(cfg, "library"), "m3u_overrides"))
{
memset(&m3u, 0, sizeof(struct media_file_info));
m3u.artist = safe_strdup(mfi->artist);
m3u.album_artist = safe_strdup(mfi->album_artist);
m3u.album = safe_strdup(mfi->album);
m3u.title = safe_strdup(mfi->title);
scan_metadata_stream(mfi, path);
if (m3u.artist)
swap_pointers(&mfi->artist, &m3u.artist);
if (m3u.album_artist)
swap_pointers(&mfi->album_artist, &m3u.album_artist);
if (m3u.album)
swap_pointers(&mfi->album, &m3u.album);
if (m3u.title)
swap_pointers(&mfi->title, &m3u.title);
free_mfi(&m3u, 1);
}
else
scan_metadata_stream(mfi, path);
ret = library_media_save(mfi);
if (ret < 0)
return -1;
return db_pl_add_item_bypath(pl_id, path); return db_pl_add_item_bypath(pl_id, path);
} }
@ -198,94 +342,78 @@ process_regular_file(int pl_id, char *path)
return 0; return 0;
} }
static int
playlist_prepare(const char *path, time_t mtime)
{
struct playlist_info *pli;
int pl_id;
pli = db_pl_fetch_bypath(path);
if (!pli)
{
DPRINTF(E_LOG, L_SCAN, "New playlist found, processing '%s'\n", path);
pl_id = playlist_add(path);
if (pl_id < 0)
{
DPRINTF(E_LOG, L_SCAN, "Error adding playlist '%s'\n", path);
return -1;
}
DPRINTF(E_INFO, L_SCAN, "Added new playlist as id %d\n", pl_id);
return pl_id;
}
db_pl_ping(pli->id);
// mtime == db_timestamp is also treated as a modification because some editors do
// stuff like 1) close the file with no changes (leading us to update db_timestamp),
// 2) copy over a modified version from a tmp file (which may result in a mtime that
// is equal to the newly updated db_timestamp)
if (mtime && (pli->db_timestamp > mtime))
{
DPRINTF(E_LOG, L_SCAN, "Unchanged playlist found, not processing '%s'\n", path);
// Protect this playlist's radio stations from purge after scan
db_pl_ping_items_bymatch("http://", pli->id);
db_pl_ping_items_bymatch("https://", pli->id);
free_pli(pli, 0);
return -1;
}
DPRINTF(E_LOG, L_SCAN, "Modified playlist found, processing '%s'\n", path);
pl_id = pli->id;
free_pli(pli, 0);
db_pl_clear_items(pl_id);
return pl_id;
}
void void
scan_playlist(const char *file, time_t mtime, int dir_id) scan_playlist(const char *file, time_t mtime, int dir_id)
{ {
FILE *fp; FILE *fp;
struct media_file_info mfi; struct media_file_info mfi;
struct playlist_info *pli;
struct stat sb; struct stat sb;
char buf[PATH_MAX]; char buf[PATH_MAX];
char *path; char *path;
const char *filename;
char *ptr;
size_t len; size_t len;
int extinf;
int pl_id; int pl_id;
int pl_format; int pl_format;
int ntracks; int ntracks;
int nadded; int nadded;
int ret; int ret;
ptr = strrchr(file, '.'); pl_format = playlist_type(file);
if (!ptr) if (pl_format == PLAYLIST_UNKNOWN)
return; return;
if (strcasecmp(ptr, ".m3u") == 0) // Will create or update the playlist entry in the database
pl_format = PLAYLIST_M3U; pl_id = playlist_prepare(file, mtime);
else if (strcasecmp(ptr, ".pls") == 0) if (pl_id < 0)
pl_format = PLAYLIST_PLS; return; // Not necessarily an error, could also be that the playlist hasn't changed
else
return;
filename = filename_from_path(file);
/* Fetch or create playlist */
pli = db_pl_fetch_bypath(file);
if (pli)
{
db_pl_ping(pli->id);
// mtime == db_timestamp is also treated as a modification because some editors do
// stuff like 1) close the file with no changes (leading us to update db_timestamp),
// 2) copy over a modified version from a tmp file (which may result in a mtime that
// is equal to the newly updated db_timestamp)
if (mtime && (pli->db_timestamp > mtime))
{
DPRINTF(E_LOG, L_SCAN, "Unchanged playlist found, not processing '%s'\n", file);
// Protect this playlist's radio stations from purge after scan
db_pl_ping_items_bymatch("http://", pli->id);
db_pl_ping_items_bymatch("https://", pli->id);
free_pli(pli, 0);
return;
}
DPRINTF(E_LOG, L_SCAN, "Modified playlist found, processing '%s'\n", file);
pl_id = pli->id;
db_pl_clear_items(pl_id);
}
else
{
DPRINTF(E_LOG, L_SCAN, "New playlist found, processing '%s'\n", file);
CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));
pli->type = PL_PLAIN;
/* Get only the basename, to be used as the playlist title */
pli->title = strip_extension(filename);
pli->path = strdup(file);
snprintf(buf, sizeof(buf), "/file:%s", file);
pli->virtual_path = strip_extension(buf);
pli->directory_id = dir_id;
ret = db_pl_add(pli, &pl_id);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Error adding playlist '%s'\n", file);
free_pli(pli, 0);
return;
}
DPRINTF(E_INFO, L_SCAN, "Added new playlist as id %d\n", pl_id);
}
free_pli(pli, 0);
ret = stat(file, &sb); ret = stat(file, &sb);
if (ret < 0) if (ret < 0)
@ -303,7 +431,6 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
db_transaction_begin(); db_transaction_begin();
extinf = 0;
memset(&mfi, 0, sizeof(struct media_file_info)); memset(&mfi, 0, sizeof(struct media_file_info));
ntracks = 0; ntracks = 0;
nadded = 0; nadded = 0;
@ -312,7 +439,7 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
{ {
len = strlen(buf); len = strlen(buf);
/* rtrim and check that length is sane (ignore blank lines) */ // rtrim and check that length is sane (ignore blank lines)
while ((len > 0) && isspace(buf[len - 1])) while ((len > 0) && isspace(buf[len - 1]))
{ {
len--; len--;
@ -321,11 +448,11 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
if (len < 1) if (len < 1)
continue; continue;
/* Saves metadata in mfi if EXTINF metadata line */ // Saves metadata in mfi if EXT metadata line
if ((pl_format == PLAYLIST_M3U) && extinf_get(buf, &mfi, &extinf)) if ((pl_format == PLAYLIST_M3U) && (exttag_read(&mfi, buf) == 0))
continue; continue;
/* For pls files we are only interested in the part after the FileX= entry */ // For pls files we are only interested in the part after the FileX= entry
path = NULL; path = NULL;
if ((pl_format == PLAYLIST_PLS) && (strncasecmp(buf, "file", strlen("file")) == 0) && (path = strchr(buf, '='))) if ((pl_format == PLAYLIST_PLS) && (strncasecmp(buf, "file", strlen("file")) == 0) && (path = strchr(buf, '=')))
path++; path++;
@ -335,12 +462,14 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
if (!path) if (!path)
continue; continue;
/* Check that first char is sane for a path */ // Check that first char is sane for a path
if ((!isalnum(path[0])) && (path[0] != '/') && (path[0] != '.')) if ((!isalnum(path[0])) && (path[0] != '/') && (path[0] != '.'))
continue; continue;
/* Check if line is an URL, will be added to library, otherwise it should already be there */ // URLs and playlists will be added to library, tracks should already be there
if (strncasecmp(path, "http://", 7) == 0 || strncasecmp(path, "https://", 8) == 0) if (playlist_type(path) != PLAYLIST_UNKNOWN)
ret = process_nested_playlist(pl_id, path);
else if (strncasecmp(path, "http://", 7) == 0 || strncasecmp(path, "https://", 8) == 0)
ret = process_url(pl_id, path, &mfi); ret = process_url(pl_id, path, &mfi);
else else
ret = process_regular_file(pl_id, path); ret = process_regular_file(pl_id, path);
@ -356,16 +485,15 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
if (ret == 0) if (ret == 0)
nadded++; nadded++;
/* Clean up in preparation for next item */ // Clean up in preparation for next item
extinf = 0;
free_mfi(&mfi, 1); free_mfi(&mfi, 1);
} }
db_transaction_end(); db_transaction_end();
/* We had some extinf that we never got to use, free it now */ // In case we had some m3u ext metadata that we never got to use, free it now
if (extinf) // (no risk of double free when the free_mfi()'s are content_only)
free_mfi(&mfi, 1); free_mfi(&mfi, 1);
if (!feof(fp)) if (!feof(fp))
DPRINTF(E_LOG, L_SCAN, "Error reading playlist '%s' (only added %d tracks): %s\n", file, nadded, strerror(errno)); DPRINTF(E_LOG, L_SCAN, "Error reading playlist '%s' (only added %d tracks): %s\n", file, nadded, strerror(errno));

View File

@ -35,6 +35,8 @@
#include "db.h" #include "db.h"
#include "misc.h" #include "misc.h"
#include "smartpl_query.h" #include "smartpl_query.h"
#include "library/filescanner.h"
#include "library.h"
void void
@ -42,28 +44,18 @@ scan_smartpl(const char *file, time_t mtime, int dir_id)
{ {
struct smartpl smartpl; struct smartpl smartpl;
struct playlist_info *pli; struct playlist_info *pli;
int pl_id;
char virtual_path[PATH_MAX];
char *ptr;
int ret; int ret;
/* Fetch or create playlist */ /* Fetch or create playlist */
pli = db_pl_fetch_bypath(file); pli = db_pl_fetch_bypath(file);
if (!pli) if (!pli)
{ {
pli = calloc(1, sizeof(struct playlist_info)); CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));
if (!pli)
{ ret = playlist_fill(pli, file);
DPRINTF(E_LOG, L_SCAN, "Out of memory\n"); if (ret < 0)
return; goto free_pli;
}
pli->path = strdup(file);
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
ptr = strrchr(virtual_path, '.');
if (ptr)
*ptr = '\0';
pli->virtual_path = strdup(virtual_path);
pli->type = PL_SMART; pli->type = PL_SMART;
} }
@ -74,45 +66,27 @@ scan_smartpl(const char *file, time_t mtime, int dir_id)
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Error parsing smart playlist '%s'\n", file); DPRINTF(E_LOG, L_SCAN, "Error parsing smart playlist '%s'\n", file);
free_pli(pli, 0);
free_smartpl(&smartpl, 1); free_smartpl(&smartpl, 1);
return; goto free_pli;
} }
free(pli->title); swap_pointers(&pli->title, &smartpl.title);
pli->title = strdup(smartpl.title); swap_pointers(&pli->query, &smartpl.query_where);
swap_pointers(&pli->query_order, &smartpl.order);
free(pli->query);
pli->query = strdup(smartpl.query_where);
free(pli->query_order);
pli->query_order = safe_strdup(smartpl.order);
pli->query_limit = smartpl.limit; pli->query_limit = smartpl.limit;
free_smartpl(&smartpl, 1); free_smartpl(&smartpl, 1);
if (pli->id) ret = library_playlist_save(pli);
{
pl_id = pli->id;
ret = db_pl_update(pli);
}
else
{
ret = db_pl_add(pli, &pl_id);
}
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SCAN, "Error adding smart playlist '%s'\n", file); DPRINTF(E_LOG, L_SCAN, "Error saving smart playlist '%s'\n", file);
goto free_pli;
free_pli(pli, 0);
return;
} }
DPRINTF(E_INFO, L_SCAN, "Added or updated smart playlist as id %d\n", pl_id); DPRINTF(E_INFO, L_SCAN, "Added or updated smart playlist '%s' with id %d\n", file, ret);
free_pli:
free_pli(pli, 0); free_pli(pli, 0);
return;
DPRINTF(E_INFO, L_SCAN, "Done processing smart playlist\n");
} }

View File

@ -1695,6 +1695,7 @@ mpd_queue_add(char *path, bool exact_match, int position)
static int static int
mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{ {
struct player_status status;
int ret; int ret;
ret = mpd_queue_add(argv[1], false, -1); ret = mpd_queue_add(argv[1], false, -1);
@ -1707,8 +1708,10 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, st
if (ret == 0) if (ret == 0)
{ {
player_get_status(&status);
// Given path is not in the library, check if it is possible to add as a non-library queue item // Given path is not in the library, check if it is possible to add as a non-library queue item
ret = library_queue_add(argv[1], -1, NULL, NULL); ret = library_queue_item_add(argv[1], -1, status.shuffle, status.item_id, NULL, NULL);
if (ret != LIBRARY_OK) if (ret != LIBRARY_OK)
{ {
*errmsg = safe_asprintf("Failed to add song '%s' to playlist (unkown path)", argv[1]); *errmsg = safe_asprintf("Failed to add song '%s' to playlist (unkown path)", argv[1]);
@ -1728,6 +1731,7 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, st
static int static int
mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{ {
struct player_status status;
int to_pos = -1; int to_pos = -1;
int ret; int ret;
@ -1745,8 +1749,10 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg,
if (ret == 0) if (ret == 0)
{ {
player_get_status(&status);
// Given path is not in the library, directly add it as a new queue item // Given path is not in the library, directly add it as a new queue item
ret = library_queue_add(argv[1], to_pos, NULL, NULL); ret = library_queue_item_add(argv[1], to_pos, status.shuffle, status.item_id, NULL, NULL);
if (ret != LIBRARY_OK) if (ret != LIBRARY_OK)
{ {
*errmsg = safe_asprintf("Failed to add song '%s' to playlist (unkown path)", argv[1]); *errmsg = safe_asprintf("Failed to add song '%s' to playlist (unkown path)", argv[1]);
@ -2510,7 +2516,7 @@ mpd_command_playlistadd(struct evbuffer *evbuf, int argc, char **argv, char **er
vp_item = prepend_slash(argv[2]); vp_item = prepend_slash(argv[2]);
ret = library_playlist_add(vp_playlist, vp_item); ret = library_playlist_item_add(vp_playlist, vp_item);
free(vp_playlist); free(vp_playlist);
free(vp_item); free(vp_item);
if (ret < 0) if (ret < 0)

View File

@ -1239,7 +1239,7 @@ queue_add_playlist(const char *uri, int position, char reshuffle, uint32_t item_
} }
static int static int
queue_add(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id) queue_item_add(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id)
{ {
if (strncasecmp(uri, "spotify:track:", strlen("spotify:track:")) == 0) if (strncasecmp(uri, "spotify:track:", strlen("spotify:track:")) == 0)
{ {
@ -1423,7 +1423,7 @@ track_add(struct spotify_track *track, struct spotify_album *album, const char *
map_track_to_mfi(&mfi, track, album, pl_name); map_track_to_mfi(&mfi, track, album, pl_name);
library_add_media(&mfi); library_media_save(&mfi);
free_mfi(&mfi, 1); free_mfi(&mfi, 1);
} }
@ -1438,6 +1438,22 @@ track_add(struct spotify_track *track, struct spotify_album *album, const char *
return 0; return 0;
} }
static int
playlist_add_or_update(struct playlist_info *pli)
{
int pl_id;
pl_id = db_pl_id_bypath(pli->path);
if (pl_id < 0)
return library_playlist_save(pli);
pli->id = pl_id;
db_pl_clear_items(pli->id);
return library_playlist_save(pli);
}
/* /*
* Add a saved album to the library * Add a saved album to the library
*/ */
@ -1571,6 +1587,24 @@ scan_playlist_tracks(const char *playlist_tracks_endpoint_uri, int plid)
return ret; return ret;
} }
static void
map_playlist_to_pli(struct playlist_info *pli, struct spotify_playlist *playlist)
{
memset(pli, 0, sizeof(struct playlist_info));
pli->type = PL_PLAIN;
pli->path = strdup(playlist->uri);
pli->title = safe_strdup(playlist->name);
pli->parent_id = spotify_base_plid;
pli->directory_id = DIR_SPOTIFY;
if (playlist->owner)
pli->virtual_path = safe_asprintf("/spotify:/%s (%s)", playlist->name, playlist->owner);
else
pli->virtual_path = safe_asprintf("/spotify:/%s", playlist->name);
}
/* /*
* Add a saved playlist to the library * Add a saved playlist to the library
*/ */
@ -1578,8 +1612,8 @@ static int
saved_playlist_add(json_object *item, int index, int total, void *arg) saved_playlist_add(json_object *item, int index, int total, void *arg)
{ {
struct spotify_playlist playlist; struct spotify_playlist playlist;
char virtual_path[PATH_MAX]; struct playlist_info pli;
int plid; int pl_id;
// Map playlist information // Map playlist information
parse_metadata_playlist(item, &playlist); parse_metadata_playlist(item, &playlist);
@ -1592,21 +1626,14 @@ saved_playlist_add(json_object *item, int index, int total, void *arg)
return -1; return -1;
} }
if (playlist.owner) map_playlist_to_pli(&pli, &playlist);
{
snprintf(virtual_path, PATH_MAX, "/spotify:/%s (%s)", playlist.name, playlist.owner);
}
else
{
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", playlist.name);
}
db_transaction_begin(); pl_id = playlist_add_or_update(&pli);
plid = library_add_playlist_info(playlist.uri, playlist.name, virtual_path, PL_PLAIN, spotify_base_plid, DIR_SPOTIFY);
db_transaction_end();
if (plid > 0) free_pli(&pli, 1);
scan_playlist_tracks(playlist.tracks_href, plid);
if (pl_id > 0)
scan_playlist_tracks(playlist.tracks_href, pl_id);
else else
DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist: '%s' (%s) \n", playlist.name, playlist.uri); DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist: '%s' (%s) \n", playlist.name, playlist.uri);
@ -1633,13 +1660,24 @@ scan_playlists()
static void static void
create_saved_tracks_playlist() create_saved_tracks_playlist()
{ {
spotify_saved_plid = library_add_playlist_info("spotify:savedtracks", "Spotify Saved", "/spotify:/Spotify Saved", PL_PLAIN, spotify_base_plid, DIR_SPOTIFY); struct playlist_info pli =
{
.path = strdup("spotify:savedtracks"),
.title = strdup("Spotify Saved"),
.virtual_path = strdup("/spotify:/Spotify Saved"),
.type = PL_PLAIN,
.parent_id = spotify_base_plid,
.directory_id = DIR_SPOTIFY,
};
if (spotify_saved_plid <= 0) spotify_saved_plid = playlist_add_or_update(&pli);
if (spotify_saved_plid < 0)
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist for saved tracks\n"); DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist for saved tracks\n");
spotify_saved_plid = 0; spotify_saved_plid = 0;
} }
free_pli(&pli, 1);
} }
/* /*
@ -1649,18 +1687,29 @@ static void
create_base_playlist() create_base_playlist()
{ {
cfg_t *spotify_cfg; cfg_t *spotify_cfg;
int ret; struct playlist_info pli =
{
.path = strdup("spotify:playlistfolder"),
.title = strdup("Spotify"),
.type = PL_FOLDER,
};
spotify_base_plid = 0; spotify_base_plid = 0;
spotify_cfg = cfg_getsec(cfg, "spotify"); spotify_cfg = cfg_getsec(cfg, "spotify");
if (!cfg_getbool(spotify_cfg, "base_playlist_disable")) if (cfg_getbool(spotify_cfg, "base_playlist_disable"))
{ {
ret = library_add_playlist_info("spotify:playlistfolder", "Spotify", NULL, PL_FOLDER, 0, 0); free_pli(&pli, 1);
if (ret < 0) return;
DPRINTF(E_LOG, L_SPOTIFY, "Error adding base playlist\n");
else
spotify_base_plid = ret;
} }
spotify_base_plid = playlist_add_or_update(&pli);
if (spotify_base_plid < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Error adding base playlist\n");
spotify_base_plid = 0;
}
free_pli(&pli, 1);
} }
static void static void
@ -1968,6 +2017,6 @@ struct library_source spotifyscanner =
.metarescan = rescan, .metarescan = rescan,
.initscan = initscan, .initscan = initscan,
.fullrescan = fullrescan, .fullrescan = fullrescan,
.queue_add = queue_add, .queue_item_add = queue_item_add,
}; };