mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 23:25:56 -05:00
Merge branch 'playlists1'
This commit is contained in:
commit
d2f0d7b53a
@ -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
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
@ -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
253
src/db.c
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
src/db.h
11
src/db.h
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
470
src/library.c
470
src/library.c
@ -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, ¶m);
|
return commands_exec_sync(cmdbase, playlist_item_add, NULL, ¶m);
|
||||||
}
|
|
||||||
|
|
||||||
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, ¶m);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
library_exec_async(command_function func, void *arg)
|
library_exec_async(command_function func, void *arg)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -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__ */
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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));
|
||||||
|
@ -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");
|
|
||||||
}
|
}
|
||||||
|
12
src/mpd.c
12
src/mpd.c
@ -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)
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user