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.
|
||||
# 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?
|
||||
# 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("filepath_ignore", NULL, 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_smartpl", cfg_false, CFGF_NONE),
|
||||
CFG_STR_LIST("no_decode", NULL, CFGF_NONE),
|
||||
|
@ -3,6 +3,7 @@
|
||||
#define __CONFFILE_H__
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <confuse.h>
|
||||
|
||||
|
255
src/db.c
255
src/db.c
@ -62,8 +62,9 @@
|
||||
#define DB_TYPE_INT64 2
|
||||
#define DB_TYPE_STRING 3
|
||||
|
||||
// Flags that column value is set automatically by the db, e.g. by a trigger
|
||||
#define DB_FLAG_AUTO (1 << 0)
|
||||
// Flags that the field will not be bound to prepared statements, which is relevant if the field has no
|
||||
// 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)
|
||||
#define DB_FLAG_NO_ZERO (1 << 1)
|
||||
|
||||
@ -110,6 +111,9 @@ struct db_statements
|
||||
sqlite3_stmt *files_insert;
|
||||
sqlite3_stmt *files_update;
|
||||
sqlite3_stmt *files_ping;
|
||||
|
||||
sqlite3_stmt *playlists_insert;
|
||||
sqlite3_stmt *playlists_update;
|
||||
};
|
||||
|
||||
struct col_type_map {
|
||||
@ -150,7 +154,7 @@ struct browse_clause {
|
||||
*/
|
||||
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 },
|
||||
{ "virtual_path", mfi_offsetof(virtual_path), DB_TYPE_STRING },
|
||||
{ "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[] =
|
||||
{
|
||||
{ "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 },
|
||||
{ "type", pli_offsetof(type), DB_TYPE_INT },
|
||||
{ "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 },
|
||||
{ "query_order", pli_offsetof(query_order), DB_TYPE_STRING, DB_FIXUP_NO_SANITIZE },
|
||||
{ "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()
|
||||
{ "items", pli_offsetof(items), DB_TYPE_INT },
|
||||
{ "streams", pli_offsetof(streams), DB_TYPE_INT },
|
||||
{ "items", pli_offsetof(items), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_BIND },
|
||||
{ "streams", pli_offsetof(streams), DB_TYPE_INT, DB_FIXUP_STANDARD, DB_FLAG_NO_BIND },
|
||||
};
|
||||
|
||||
/* 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[] =
|
||||
{
|
||||
{ "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 },
|
||||
{ "pos", qi_offsetof(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 },
|
||||
{ "bitrate", qi_offsetof(bitrate), 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
|
||||
@ -400,7 +404,7 @@ static const ssize_t dbgri_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 },
|
||||
{ "path", wi_offsetof(path), DB_TYPE_STRING },
|
||||
};
|
||||
@ -516,9 +520,6 @@ static __thread struct db_statements db_statements;
|
||||
|
||||
|
||||
/* Forward */
|
||||
struct playlist_info *
|
||||
db_pl_fetch_byid(int id);
|
||||
|
||||
static enum group_type
|
||||
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;
|
||||
else if (ctx->mfi && !ctx->mfi->media_kind)
|
||||
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)
|
||||
ctx->queue_item->media_kind = MEDIA_KIND_MUSIC;
|
||||
|
||||
@ -1095,22 +1098,22 @@ fixup_tags_queue_item(struct db_queue_item *queue_item)
|
||||
}
|
||||
|
||||
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 *ptr;
|
||||
int i;
|
||||
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;
|
||||
|
||||
ptr = (char *)mfi + mfi_cols_map[i].offset;
|
||||
strptr = (char **)((char *)mfi + mfi_cols_map[i].offset);
|
||||
ptr = data + map[i].offset;
|
||||
strptr = (char **)(data + map[i].offset);
|
||||
|
||||
switch (mfi_cols_map[i].type)
|
||||
switch (map[i].type)
|
||||
{
|
||||
case DB_TYPE_INT:
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
if (mfi->id)
|
||||
sqlite3_bind_int(stmt, n, mfi->id);
|
||||
if (id)
|
||||
sqlite3_bind_int(stmt, n, id);
|
||||
|
||||
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 */
|
||||
static void
|
||||
@ -3462,75 +3476,58 @@ db_pl_fetch_bytitlepath(const char *title, const char *path)
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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);
|
||||
|
||||
/* Check duplicates */
|
||||
query = sqlite3_mprintf(QDUP_TMPL, pli->title, STR(pli->path));
|
||||
if (!query)
|
||||
ret = bind_pli(db_statements.playlists_insert, pli);
|
||||
if (ret < 0)
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
DPRINTF(E_WARN, L_DB, "Duplicate playlist with title '%s' path '%s'\n", pli->title, pli->path);
|
||||
int
|
||||
db_pl_update(struct playlist_info *pli)
|
||||
{
|
||||
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);
|
||||
|
||||
ret = bind_pli(db_statements.playlists_update, pli);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Add */
|
||||
query = sqlite3_mprintf(QADD_TMPL,
|
||||
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
||||
pli->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->directory_id,
|
||||
pli->query_order, pli->query_limit);
|
||||
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||
ret = db_statement_run(db_statements.playlists_update);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
||||
|
||||
ret = db_exec(query, &errmsg);
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
|
||||
|
||||
sqlite3_free(errmsg);
|
||||
sqlite3_free(query);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_free(query);
|
||||
|
||||
*id = (int)sqlite3_last_insert_rowid(hdl);
|
||||
if (*id == 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Successful insert but no last_insert_rowid!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_DB, "Added playlist %s (path %s) with id %d\n", pli->title, pli->path, *id);
|
||||
|
||||
return 0;
|
||||
|
||||
#undef QDUP_TMPL
|
||||
#undef QADD_TMPL
|
||||
return pli->id;
|
||||
}
|
||||
|
||||
int
|
||||
@ -3557,29 +3554,6 @@ db_pl_add_item_byid(int plid, int fileid)
|
||||
#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
|
||||
db_pl_clear_items(int id)
|
||||
{
|
||||
@ -6787,24 +6761,24 @@ db_open(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
db_statements_prepare(void)
|
||||
static sqlite3_stmt *
|
||||
db_statements_prepare_insert(const struct col_type_map *map, size_t map_size, const char *table)
|
||||
{
|
||||
char *query;
|
||||
char keystr[2048];
|
||||
char valstr[1024];
|
||||
sqlite3_stmt *stmt;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
// Prepare "INSERT INTO files" statement
|
||||
memset(keystr, 0, sizeof(keystr));
|
||||
memset(valstr, 0, sizeof(valstr));
|
||||
for (i = 0; i < ARRAY_SIZE(mfi_cols_map); i++)
|
||||
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;
|
||||
|
||||
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), "?, "));
|
||||
}
|
||||
|
||||
@ -6812,58 +6786,97 @@ db_statements_prepare(void)
|
||||
*(strrchr(keystr, ',')) = '\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)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl));
|
||||
free(query);
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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));
|
||||
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;
|
||||
|
||||
if (mfi_cols_map[i].flag & DB_FLAG_NO_ZERO)
|
||||
CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s = daap_no_zero(?, %s), ", mfi_cols_map[i].name, mfi_cols_map[i].name));
|
||||
if (map[i].flag & DB_FLAG_NO_ZERO)
|
||||
CHECK_ERR(L_DB, safe_snprintf_cat(keystr, sizeof(keystr), "%s = daap_no_zero(?, %s), ", map[i].name, map[i].name));
|
||||
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 ", "
|
||||
*(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)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl));
|
||||
free(query);
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(query);
|
||||
|
||||
// Prepare "UPDATE files SET db_timestamp" statement
|
||||
CHECK_NULL(L_DB, query = db_mprintf("UPDATE files SET db_timestamp = ?, disabled = 0 WHERE path = ? AND db_timestamp >= ?;"));
|
||||
return stmt;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_DB, "Could not prepare statement '%s': %s\n", query, sqlite3_errmsg(hdl));
|
||||
free(query);
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
11
src/db.h
11
src/db.h
@ -660,6 +660,9 @@ db_pl_ping_items_bymatch(const char *path, int id);
|
||||
int
|
||||
db_pl_id_bypath(const char *path);
|
||||
|
||||
struct playlist_info *
|
||||
db_pl_fetch_byid(int id);
|
||||
|
||||
struct playlist_info *
|
||||
db_pl_fetch_bypath(const char *path);
|
||||
|
||||
@ -670,7 +673,10 @@ struct playlist_info *
|
||||
db_pl_fetch_bytitlepath(const char *title, const char *path);
|
||||
|
||||
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
|
||||
db_pl_add_item_bypath(int plid, const char *path);
|
||||
@ -681,9 +687,6 @@ db_pl_add_item_byid(int plid, int fileid);
|
||||
void
|
||||
db_pl_clear_items(int id);
|
||||
|
||||
int
|
||||
db_pl_update(struct playlist_info *pli);
|
||||
|
||||
void
|
||||
db_pl_delete(int id);
|
||||
|
||||
|
@ -2182,6 +2182,7 @@ queue_tracks_add_playlist(const char *id, int pos)
|
||||
static int
|
||||
queue_tracks_add_byuris(const char *param, int pos, int *total_count)
|
||||
{
|
||||
struct player_status status;
|
||||
char *uris;
|
||||
char *uri;
|
||||
char *ptr;
|
||||
@ -2227,7 +2228,9 @@ queue_tracks_add_byuris(const char *param, int pos, int *total_count)
|
||||
}
|
||||
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)
|
||||
{
|
||||
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 "player.h"
|
||||
|
||||
struct playlist_add_param
|
||||
struct playlist_item_add_param
|
||||
{
|
||||
const char *vp_playlist;
|
||||
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 pthread_t tid_library;
|
||||
|
||||
@ -95,6 +105,58 @@ static struct event *updateev;
|
||||
static unsigned int deferred_update_notifications;
|
||||
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
|
||||
handle_deferred_update_notifications(void)
|
||||
{
|
||||
@ -114,132 +176,6 @@ handle_deferred_update_notifications(void)
|
||||
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
|
||||
purge_cruft(time_t start)
|
||||
{
|
||||
@ -378,9 +314,135 @@ fullrescan(void *arg, int *ret)
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback to notify listeners of database changes
|
||||
*/
|
||||
static enum command_state
|
||||
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
|
||||
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
|
||||
library_rescan()
|
||||
@ -437,6 +499,7 @@ library_metarescan()
|
||||
scanning = true; // TODO Guard "scanning" with a mutex
|
||||
commands_exec_async(cmdbase, metarescan, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
library_fullrescan()
|
||||
{
|
||||
@ -494,39 +557,24 @@ initscan()
|
||||
listener_notify(LISTENER_UPDATE);
|
||||
}
|
||||
|
||||
/*
|
||||
* @return true if scan is running, otherwise false
|
||||
*/
|
||||
bool
|
||||
library_is_scanning()
|
||||
{
|
||||
return scanning;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param is_scanning true if scan is running, otherwise false
|
||||
*/
|
||||
void
|
||||
library_set_scanning(bool is_scanning)
|
||||
{
|
||||
scanning = is_scanning;
|
||||
}
|
||||
|
||||
/*
|
||||
* @return true if a running scan should be aborted due to imminent shutdown, otherwise false
|
||||
*/
|
||||
bool
|
||||
library_is_exiting()
|
||||
{
|
||||
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
|
||||
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
|
||||
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())
|
||||
return -1;
|
||||
|
||||
param.vp_playlist = vp_playlist;
|
||||
param.vp_item = vp_item;
|
||||
return commands_exec_sync(cmdbase, playlist_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;
|
||||
return commands_exec_sync(cmdbase, playlist_item_add, NULL, ¶m);
|
||||
}
|
||||
|
||||
int
|
||||
@ -633,39 +617,6 @@ library_playlist_remove(char *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
|
||||
library_queue_save(char *path)
|
||||
{
|
||||
@ -675,15 +626,24 @@ library_queue_save(char *path)
|
||||
return commands_exec_sync(cmdbase, queue_save, NULL, path);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
library_queue_item_add(const char *path, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id)
|
||||
{
|
||||
struct queue_item_add_param param;
|
||||
|
||||
if (library_is_scanning())
|
||||
return -1;
|
||||
|
||||
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
|
||||
library_exec_async(command_function func, void *arg)
|
||||
{
|
||||
|
@ -72,9 +72,9 @@ struct library_source
|
||||
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
|
||||
@ -89,17 +89,24 @@ struct library_source
|
||||
/*
|
||||
* 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
|
||||
library_add_media(struct media_file_info *mfi);
|
||||
/* --------------------- Interface towards source backends ----------------- */
|
||||
|
||||
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
|
||||
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
|
||||
library_rescan();
|
||||
@ -110,20 +117,35 @@ library_metarescan();
|
||||
void
|
||||
library_fullrescan();
|
||||
|
||||
/*
|
||||
* @return true if scan is running, otherwise false
|
||||
*/
|
||||
bool
|
||||
library_is_scanning();
|
||||
|
||||
/*
|
||||
* @param is_scanning true if scan is running, otherwise false
|
||||
*/
|
||||
void
|
||||
library_set_scanning(bool is_scanning);
|
||||
|
||||
/*
|
||||
* @return true if a running scan should be aborted due to imminent shutdown, otherwise false
|
||||
*/
|
||||
bool
|
||||
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
|
||||
library_update_trigger(short update_events);
|
||||
|
||||
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
|
||||
library_playlist_remove(char *virtual_path);
|
||||
@ -131,6 +153,18 @@ library_playlist_remove(char *virtual_path);
|
||||
int
|
||||
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
|
||||
library_exec_async(command_function func, void *arg);
|
||||
|
||||
|
@ -165,57 +165,47 @@ static int
|
||||
filescanner_fullrescan();
|
||||
|
||||
|
||||
const char *
|
||||
filename_from_path(const char *path)
|
||||
/* ----------------------- Internal utility functions --------------------- */
|
||||
|
||||
static int
|
||||
virtual_path_make(char *virtual_path, int virtual_path_len, const char *path)
|
||||
{
|
||||
const char *filename;
|
||||
int ret;
|
||||
|
||||
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))
|
||||
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', virtual_path_len exceeded (%d/%d)\n", path, ret, virtual_path_len);
|
||||
return -1;
|
||||
|
||||
for (ptr--; (ptr > path) && (*ptr != '/'); ptr--)
|
||||
;
|
||||
|
||||
*current = ptr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@ -386,6 +376,108 @@ file_type_get(const char *path) {
|
||||
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
|
||||
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"));
|
||||
}
|
||||
|
||||
ret = scan_metadata_ffmpeg(file, &mfi);
|
||||
ret = scan_metadata_ffmpeg(&mfi, file);
|
||||
if (ret < 0)
|
||||
{
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* pointed to by 'sb'.
|
||||
@ -766,7 +843,7 @@ process_directory(char *path, int parent_id, int flags)
|
||||
|
||||
/* 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)
|
||||
return;
|
||||
|
||||
@ -893,7 +970,7 @@ process_parent_directories(char *path)
|
||||
strncpy(buf, path, (ptr - path));
|
||||
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)
|
||||
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
|
||||
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;
|
||||
char *file = path;
|
||||
char resolved_path[PATH_MAX];
|
||||
char *dir;
|
||||
char dir_vpath[PATH_MAX];
|
||||
int type;
|
||||
int i;
|
||||
@ -1291,14 +1345,12 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
|
||||
|
||||
if (ret > 0)
|
||||
{
|
||||
// If file was successfully enabled, update the directory id
|
||||
dir = strdup(path);
|
||||
ptr = strrchr(dir, '/');
|
||||
dir[(ptr - dir)] = '\0';
|
||||
|
||||
ret = create_virtual_path(dir, dir_vpath, sizeof(dir_vpath));
|
||||
ret = virtual_path_make(dir_vpath, sizeof(dir_vpath), path);
|
||||
if (ret >= 0)
|
||||
{
|
||||
ptr = strrchr(dir_vpath, '/');
|
||||
*ptr = '\0';
|
||||
|
||||
dir_id = db_directory_id_byvirtualpath(dir_vpath);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
free(dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1719,7 +1769,7 @@ map_media_file_to_queue_item(struct db_queue_item *queue_item, struct media_file
|
||||
}
|
||||
|
||||
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 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));
|
||||
|
||||
scan_metadata_stream(path, &mfi);
|
||||
scan_metadata_stream(&mfi, path);
|
||||
|
||||
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
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1828,7 +1878,7 @@ has_suffix(const char *file, const char *suffix)
|
||||
* Returns NULL on error and a new allocated path on success.
|
||||
*/
|
||||
static char *
|
||||
get_playlist_path(const char *vp_playlist)
|
||||
playlist_path_create(const char *vp_playlist)
|
||||
{
|
||||
const char *path;
|
||||
char *pl_path;
|
||||
@ -1863,27 +1913,6 @@ get_playlist_path(const char *vp_playlist)
|
||||
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
|
||||
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);
|
||||
|
||||
memset(&mfi, 0, sizeof(struct media_file_info));
|
||||
scan_metadata_stream(path, &mfi);
|
||||
library_add_media(&mfi);
|
||||
scan_metadata_stream(&mfi, path);
|
||||
library_media_save(&mfi);
|
||||
free_mfi(&mfi, 1);
|
||||
|
||||
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
|
||||
playlist_add(const char *vp_playlist, const char *vp_item)
|
||||
playlist_item_add(const char *vp_playlist, const char *vp_item)
|
||||
{
|
||||
char *pl_path;
|
||||
FILE *fp;
|
||||
int pl_id;
|
||||
int ret;
|
||||
|
||||
pl_path = get_playlist_path(vp_playlist);
|
||||
pl_path = playlist_path_create(vp_playlist);
|
||||
if (!pl_path)
|
||||
return LIBRARY_PATH_INVALID;
|
||||
|
||||
@ -1983,31 +2012,36 @@ playlist_add(const char *vp_playlist, const char *vp_item)
|
||||
if (!fp)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error opening file '%s' for writing: %d\n", pl_path, errno);
|
||||
free(pl_path);
|
||||
return LIBRARY_ERROR;
|
||||
goto error;
|
||||
}
|
||||
|
||||
pl_id = get_playlist_id(pl_path, vp_playlist);
|
||||
free(pl_path);
|
||||
pl_id = db_pl_id_bypath(pl_path);
|
||||
if (pl_id < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not get playlist id for %s\n", vp_playlist);
|
||||
fclose(fp);
|
||||
return LIBRARY_ERROR;
|
||||
pl_id = playlist_add(pl_path);
|
||||
if (pl_id < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = playlist_add_files(fp, pl_id, vp_item);
|
||||
fclose(fp);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
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);
|
||||
|
||||
return LIBRARY_OK;
|
||||
|
||||
error:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
free(pl_path);
|
||||
return LIBRARY_ERROR;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -2018,7 +2052,7 @@ playlist_remove(const char *vp_playlist)
|
||||
int pl_id;
|
||||
int ret;
|
||||
|
||||
pl_path = get_playlist_path(vp_playlist);
|
||||
pl_path = playlist_path_create(vp_playlist);
|
||||
if (!pl_path)
|
||||
{
|
||||
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 ret;
|
||||
|
||||
pl_path = get_playlist_path(virtual_path);
|
||||
pl_path = playlist_path_create(virtual_path);
|
||||
if (!pl_path)
|
||||
return LIBRARY_PATH_INVALID;
|
||||
|
||||
@ -2067,17 +2101,15 @@ queue_save(const char *virtual_path)
|
||||
if (!fp)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error opening file '%s' for writing: %d\n", pl_path, errno);
|
||||
free(pl_path);
|
||||
return LIBRARY_ERROR;
|
||||
goto error;
|
||||
}
|
||||
|
||||
pl_id = get_playlist_id(pl_path, virtual_path);
|
||||
free(pl_path);
|
||||
pl_id = db_pl_id_bypath(pl_path);
|
||||
if (pl_id < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not get playlist id for %s\n", virtual_path);
|
||||
fclose(fp);
|
||||
return LIBRARY_ERROR;
|
||||
pl_id = playlist_add(pl_path);
|
||||
if (pl_id < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
memset(&query_params, 0, sizeof(struct query_params));
|
||||
@ -2085,8 +2117,7 @@ queue_save(const char *virtual_path)
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Failed to start queue enum\n");
|
||||
fclose(fp);
|
||||
return LIBRARY_ERROR;
|
||||
goto error;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
memset(&mfi, 0, sizeof(struct media_file_info));
|
||||
scan_metadata_stream(queue_item.path, &mfi);
|
||||
library_add_media(&mfi);
|
||||
scan_metadata_stream(&mfi, queue_item.path);
|
||||
library_media_save(&mfi);
|
||||
free_mfi(&mfi, 1);
|
||||
}
|
||||
else
|
||||
@ -2131,7 +2162,9 @@ queue_save(const char *virtual_path)
|
||||
}
|
||||
|
||||
db_queue_enum_end(&query_params);
|
||||
|
||||
fclose(fp);
|
||||
free(pl_path);
|
||||
|
||||
db_pl_ping(pl_id);
|
||||
|
||||
@ -2139,6 +2172,12 @@ queue_save(const char *virtual_path)
|
||||
return LIBRARY_ERROR;
|
||||
|
||||
return LIBRARY_OK;
|
||||
|
||||
error:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
free(pl_path);
|
||||
return LIBRARY_ERROR;
|
||||
}
|
||||
|
||||
/* Thread: main */
|
||||
@ -2174,8 +2213,8 @@ struct library_source filescanner =
|
||||
.rescan = filescanner_rescan,
|
||||
.metarescan = filescanner_metarescan,
|
||||
.fullrescan = filescanner_fullrescan,
|
||||
.playlist_add = playlist_add,
|
||||
.playlist_item_add = playlist_item_add,
|
||||
.playlist_remove = playlist_remove,
|
||||
.queue_save = queue_save,
|
||||
.queue_add = queue_add,
|
||||
.queue_item_add = queue_item_add,
|
||||
};
|
||||
|
@ -8,10 +8,10 @@
|
||||
/* --------------------------- Actual scanners ---------------------------- */
|
||||
|
||||
int
|
||||
scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi);
|
||||
scan_metadata_ffmpeg(struct media_file_info *mfi, const char *file);
|
||||
|
||||
void
|
||||
scan_metadata_stream(const char *path, struct media_file_info *mfi);
|
||||
scan_metadata_stream(struct media_file_info *mfi, const char *path);
|
||||
|
||||
void
|
||||
scan_playlist(const char *file, time_t mtime, int dir_id);
|
||||
@ -60,4 +60,23 @@ strip_extension(const char *path);
|
||||
int
|
||||
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__ */
|
||||
|
@ -354,7 +354,7 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
|
||||
* - fname: (filename) used as fallback for artist
|
||||
*/
|
||||
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;
|
||||
AVDictionary *options;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
@ -41,6 +42,7 @@
|
||||
|
||||
#include "logger.h"
|
||||
#include "db.h"
|
||||
#include "library.h"
|
||||
#include "library/filescanner.h"
|
||||
#include "conffile.h"
|
||||
#include "misc.h"
|
||||
@ -787,13 +789,11 @@ process_pls(plist_t playlists, const char *file)
|
||||
{
|
||||
plist_t pl;
|
||||
plist_t items;
|
||||
struct playlist_info *pli;
|
||||
struct playlist_info pli;
|
||||
char *name;
|
||||
uint64_t id;
|
||||
int pl_id;
|
||||
uint32_t alen;
|
||||
uint32_t i;
|
||||
char virtual_path[PATH_MAX];
|
||||
int ret;
|
||||
|
||||
alen = plist_array_get_size(playlists);
|
||||
@ -833,47 +833,40 @@ process_pls(plist_t playlists, const char *file)
|
||||
continue;
|
||||
}
|
||||
|
||||
CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));
|
||||
playlist_fill(&pli, file);
|
||||
|
||||
pli->type = PL_PLAIN;
|
||||
pli->title = strdup(name);
|
||||
pli->path = strdup(file);
|
||||
snprintf(virtual_path, sizeof(virtual_path), "/file:%s/%s", file, name);
|
||||
pli->virtual_path = strdup(virtual_path);
|
||||
free(pli.title);
|
||||
pli.title = strdup(name);
|
||||
free(pli.virtual_path);
|
||||
pli.virtual_path = safe_asprintf("/file:%s/%s", file, name);
|
||||
|
||||
ret = db_pl_add(pli, &pl_id);
|
||||
free_pli(pli, 0);
|
||||
ret = library_playlist_save(&pli);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error adding iTunes playlist '%s' (%s)\n", name, file);
|
||||
|
||||
free_pli(&pli, 1);
|
||||
free(name);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
scan_itunes_itml(const char *file, time_t mtime, int dir_id)
|
||||
static bool
|
||||
itml_is_modified(const char *path, time_t mtime)
|
||||
{
|
||||
struct playlist_info *pli;
|
||||
struct stat sb;
|
||||
char buf[PATH_MAX];
|
||||
char *itml_xml;
|
||||
plist_t itml;
|
||||
plist_t node;
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
// This is special playlist that is disabled and only used for saving a timestamp
|
||||
pli = db_pl_fetch_bytitlepath(file, file);
|
||||
// This is a special playlist that is disabled and only used for saving a timestamp
|
||||
pli = db_pl_fetch_bytitlepath(path, path);
|
||||
if (pli)
|
||||
{
|
||||
// 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)
|
||||
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
|
||||
db_pl_ping_bymatch(file, 0);
|
||||
db_pl_ping_bymatch(path, 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
|
||||
db_pl_delete_bypath(file);
|
||||
free_pli(pli, 0);
|
||||
db_pl_delete_bypath(path);
|
||||
}
|
||||
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)));
|
||||
|
||||
pli->type = PL_PLAIN;
|
||||
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)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error adding iTunes XML meta playlist '%s'\n", file);
|
||||
|
||||
free_pli(pli, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable, only used for saving timestamp
|
||||
db_pl_disable_bypath(file, STRIP_NONE, 0);
|
||||
// Prepare the special meta playlist used for saving timestamp
|
||||
playlist_fill(pli, path);
|
||||
free(pli->title);
|
||||
pli->title = strdup(path);
|
||||
|
||||
ret = library_playlist_save(pli);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error adding iTunes XML meta playlist '%s'\n", path);
|
||||
free_pli(pli, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
if (fd < 0)
|
||||
{
|
||||
|
@ -32,48 +32,109 @@
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "conffile.h"
|
||||
#include "logger.h"
|
||||
#include "db.h"
|
||||
#include "library/filescanner.h"
|
||||
#include "misc.h"
|
||||
#include "library.h"
|
||||
|
||||
/* Formats we can read so far */
|
||||
#define PLAYLIST_PLS 1
|
||||
#define PLAYLIST_M3U 2
|
||||
// Formats we can read so far
|
||||
enum playlist_type
|
||||
{
|
||||
PLAYLIST_UNKNOWN = 0,
|
||||
PLAYLIST_PLS,
|
||||
PLAYLIST_M3U,
|
||||
};
|
||||
|
||||
/* Get metadata from the EXTINF tag */
|
||||
static int
|
||||
extinf_get(char *string, struct media_file_info *mfi, int *extinf)
|
||||
static enum playlist_type
|
||||
playlist_type(const char *path)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
if (strncmp(string, "#EXTINF:", strlen("#EXTINF:")) != 0)
|
||||
return 0;
|
||||
ptr = strrchr(path, '.');
|
||||
if (!ptr)
|
||||
return PLAYLIST_UNKNOWN;
|
||||
|
||||
ptr = strchr(string, ',');
|
||||
if (!ptr || strlen(ptr) < 2)
|
||||
return 0;
|
||||
|
||||
/* 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);
|
||||
if (strcasecmp(ptr, ".m3u") == 0)
|
||||
return PLAYLIST_M3U;
|
||||
else if (strcasecmp(ptr, ".pls") == 0)
|
||||
return PLAYLIST_PLS;
|
||||
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)
|
||||
*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
|
||||
scan_metadata_stream(const char *path, struct media_file_info *mfi)
|
||||
scan_metadata_stream(struct media_file_info *mfi, const char *path)
|
||||
{
|
||||
char *pos;
|
||||
int ret;
|
||||
@ -91,7 +152,7 @@ scan_metadata_stream(const char *path, struct media_file_info *mfi)
|
||||
mfi->time_modified = time(NULL);
|
||||
mfi->directory_id = DIR_HTTP;
|
||||
|
||||
ret = scan_metadata_ffmpeg(path, mfi);
|
||||
ret = scan_metadata_ffmpeg(mfi, path);
|
||||
if (ret < 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -198,42 +342,28 @@ process_regular_file(int pl_id, char *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
scan_playlist(const char *file, time_t mtime, int dir_id)
|
||||
static int
|
||||
playlist_prepare(const char *path, time_t mtime)
|
||||
{
|
||||
FILE *fp;
|
||||
struct media_file_info mfi;
|
||||
struct playlist_info *pli;
|
||||
struct stat sb;
|
||||
char buf[PATH_MAX];
|
||||
char *path;
|
||||
const char *filename;
|
||||
char *ptr;
|
||||
size_t len;
|
||||
int extinf;
|
||||
int pl_id;
|
||||
int pl_format;
|
||||
int ntracks;
|
||||
int nadded;
|
||||
int ret;
|
||||
|
||||
ptr = strrchr(file, '.');
|
||||
if (!ptr)
|
||||
return;
|
||||
|
||||
if (strcasecmp(ptr, ".m3u") == 0)
|
||||
pl_format = PLAYLIST_M3U;
|
||||
else if (strcasecmp(ptr, ".pls") == 0)
|
||||
pl_format = PLAYLIST_PLS;
|
||||
else
|
||||
return;
|
||||
|
||||
filename = filename_from_path(file);
|
||||
|
||||
/* Fetch or create playlist */
|
||||
pli = db_pl_fetch_bypath(file);
|
||||
if (pli)
|
||||
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
|
||||
@ -242,50 +372,48 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
|
||||
// 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);
|
||||
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;
|
||||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF(E_LOG, L_SCAN, "Modified playlist found, processing '%s'\n", file);
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "New playlist found, processing '%s'\n", file);
|
||||
|
||||
CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));
|
||||
return pl_id;
|
||||
}
|
||||
|
||||
pli->type = PL_PLAIN;
|
||||
void
|
||||
scan_playlist(const char *file, time_t mtime, int dir_id)
|
||||
{
|
||||
FILE *fp;
|
||||
struct media_file_info mfi;
|
||||
struct stat sb;
|
||||
char buf[PATH_MAX];
|
||||
char *path;
|
||||
size_t len;
|
||||
int pl_id;
|
||||
int pl_format;
|
||||
int ntracks;
|
||||
int nadded;
|
||||
int ret;
|
||||
|
||||
/* 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);
|
||||
pl_format = playlist_type(file);
|
||||
if (pl_format == PLAYLIST_UNKNOWN)
|
||||
return;
|
||||
}
|
||||
|
||||
DPRINTF(E_INFO, L_SCAN, "Added new playlist as id %d\n", pl_id);
|
||||
}
|
||||
|
||||
free_pli(pli, 0);
|
||||
// Will create or update the playlist entry in the database
|
||||
pl_id = playlist_prepare(file, mtime);
|
||||
if (pl_id < 0)
|
||||
return; // Not necessarily an error, could also be that the playlist hasn't changed
|
||||
|
||||
ret = stat(file, &sb);
|
||||
if (ret < 0)
|
||||
@ -303,7 +431,6 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
|
||||
|
||||
db_transaction_begin();
|
||||
|
||||
extinf = 0;
|
||||
memset(&mfi, 0, sizeof(struct media_file_info));
|
||||
ntracks = 0;
|
||||
nadded = 0;
|
||||
@ -312,7 +439,7 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
|
||||
{
|
||||
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]))
|
||||
{
|
||||
len--;
|
||||
@ -321,11 +448,11 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
|
||||
if (len < 1)
|
||||
continue;
|
||||
|
||||
/* Saves metadata in mfi if EXTINF metadata line */
|
||||
if ((pl_format == PLAYLIST_M3U) && extinf_get(buf, &mfi, &extinf))
|
||||
// Saves metadata in mfi if EXT metadata line
|
||||
if ((pl_format == PLAYLIST_M3U) && (exttag_read(&mfi, buf) == 0))
|
||||
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;
|
||||
if ((pl_format == PLAYLIST_PLS) && (strncasecmp(buf, "file", strlen("file")) == 0) && (path = strchr(buf, '=')))
|
||||
path++;
|
||||
@ -335,12 +462,14 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
|
||||
if (!path)
|
||||
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] != '.'))
|
||||
continue;
|
||||
|
||||
/* Check if line is an URL, will be added to library, otherwise it should already be there */
|
||||
if (strncasecmp(path, "http://", 7) == 0 || strncasecmp(path, "https://", 8) == 0)
|
||||
// URLs and playlists will be added to library, tracks should already be there
|
||||
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);
|
||||
else
|
||||
ret = process_regular_file(pl_id, path);
|
||||
@ -356,15 +485,14 @@ scan_playlist(const char *file, time_t mtime, int dir_id)
|
||||
if (ret == 0)
|
||||
nadded++;
|
||||
|
||||
/* Clean up in preparation for next item */
|
||||
extinf = 0;
|
||||
// Clean up in preparation for next item
|
||||
free_mfi(&mfi, 1);
|
||||
}
|
||||
|
||||
db_transaction_end();
|
||||
|
||||
/* We had some extinf that we never got to use, free it now */
|
||||
if (extinf)
|
||||
// In case we had some m3u ext metadata that we never got to use, free it now
|
||||
// (no risk of double free when the free_mfi()'s are content_only)
|
||||
free_mfi(&mfi, 1);
|
||||
|
||||
if (!feof(fp))
|
||||
|
@ -35,6 +35,8 @@
|
||||
#include "db.h"
|
||||
#include "misc.h"
|
||||
#include "smartpl_query.h"
|
||||
#include "library/filescanner.h"
|
||||
#include "library.h"
|
||||
|
||||
|
||||
void
|
||||
@ -42,28 +44,18 @@ scan_smartpl(const char *file, time_t mtime, int dir_id)
|
||||
{
|
||||
struct smartpl smartpl;
|
||||
struct playlist_info *pli;
|
||||
int pl_id;
|
||||
char virtual_path[PATH_MAX];
|
||||
char *ptr;
|
||||
int ret;
|
||||
|
||||
/* Fetch or create playlist */
|
||||
pli = db_pl_fetch_bypath(file);
|
||||
if (!pli)
|
||||
{
|
||||
pli = calloc(1, sizeof(struct playlist_info));
|
||||
if (!pli)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Out of memory\n");
|
||||
return;
|
||||
}
|
||||
CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));
|
||||
|
||||
ret = playlist_fill(pli, file);
|
||||
if (ret < 0)
|
||||
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;
|
||||
}
|
||||
|
||||
@ -74,45 +66,27 @@ scan_smartpl(const char *file, time_t mtime, int dir_id)
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error parsing smart playlist '%s'\n", file);
|
||||
|
||||
free_pli(pli, 0);
|
||||
free_smartpl(&smartpl, 1);
|
||||
return;
|
||||
goto free_pli;
|
||||
}
|
||||
|
||||
free(pli->title);
|
||||
pli->title = strdup(smartpl.title);
|
||||
|
||||
free(pli->query);
|
||||
pli->query = strdup(smartpl.query_where);
|
||||
|
||||
free(pli->query_order);
|
||||
pli->query_order = safe_strdup(smartpl.order);
|
||||
|
||||
swap_pointers(&pli->title, &smartpl.title);
|
||||
swap_pointers(&pli->query, &smartpl.query_where);
|
||||
swap_pointers(&pli->query_order, &smartpl.order);
|
||||
pli->query_limit = smartpl.limit;
|
||||
|
||||
free_smartpl(&smartpl, 1);
|
||||
|
||||
if (pli->id)
|
||||
{
|
||||
pl_id = pli->id;
|
||||
ret = db_pl_update(pli);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = db_pl_add(pli, &pl_id);
|
||||
}
|
||||
ret = library_playlist_save(pli);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error adding smart playlist '%s'\n", file);
|
||||
|
||||
free_pli(pli, 0);
|
||||
return;
|
||||
DPRINTF(E_LOG, L_SCAN, "Error saving smart playlist '%s'\n", file);
|
||||
goto free_pli;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
DPRINTF(E_INFO, L_SCAN, "Done processing smart playlist\n");
|
||||
return;
|
||||
}
|
||||
|
12
src/mpd.c
12
src/mpd.c
@ -1695,6 +1695,7 @@ mpd_queue_add(char *path, bool exact_match, int position)
|
||||
static int
|
||||
mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
|
||||
{
|
||||
struct player_status status;
|
||||
int ret;
|
||||
|
||||
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)
|
||||
{
|
||||
player_get_status(&status);
|
||||
|
||||
// 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)
|
||||
{
|
||||
*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
|
||||
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 ret;
|
||||
|
||||
@ -1745,8 +1749,10 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg,
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
player_get_status(&status);
|
||||
|
||||
// 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)
|
||||
{
|
||||
*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]);
|
||||
|
||||
ret = library_playlist_add(vp_playlist, vp_item);
|
||||
ret = library_playlist_item_add(vp_playlist, vp_item);
|
||||
free(vp_playlist);
|
||||
free(vp_item);
|
||||
if (ret < 0)
|
||||
|
@ -1239,7 +1239,7 @@ queue_add_playlist(const char *uri, int position, char reshuffle, uint32_t item_
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -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);
|
||||
|
||||
library_add_media(&mfi);
|
||||
library_media_save(&mfi);
|
||||
|
||||
free_mfi(&mfi, 1);
|
||||
}
|
||||
@ -1438,6 +1438,22 @@ track_add(struct spotify_track *track, struct spotify_album *album, const char *
|
||||
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
|
||||
*/
|
||||
@ -1571,6 +1587,24 @@ scan_playlist_tracks(const char *playlist_tracks_endpoint_uri, int plid)
|
||||
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
|
||||
*/
|
||||
@ -1578,8 +1612,8 @@ static int
|
||||
saved_playlist_add(json_object *item, int index, int total, void *arg)
|
||||
{
|
||||
struct spotify_playlist playlist;
|
||||
char virtual_path[PATH_MAX];
|
||||
int plid;
|
||||
struct playlist_info pli;
|
||||
int pl_id;
|
||||
|
||||
// Map playlist information
|
||||
parse_metadata_playlist(item, &playlist);
|
||||
@ -1592,21 +1626,14 @@ saved_playlist_add(json_object *item, int index, int total, void *arg)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (playlist.owner)
|
||||
{
|
||||
snprintf(virtual_path, PATH_MAX, "/spotify:/%s (%s)", playlist.name, playlist.owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", playlist.name);
|
||||
}
|
||||
map_playlist_to_pli(&pli, &playlist);
|
||||
|
||||
db_transaction_begin();
|
||||
plid = library_add_playlist_info(playlist.uri, playlist.name, virtual_path, PL_PLAIN, spotify_base_plid, DIR_SPOTIFY);
|
||||
db_transaction_end();
|
||||
pl_id = playlist_add_or_update(&pli);
|
||||
|
||||
if (plid > 0)
|
||||
scan_playlist_tracks(playlist.tracks_href, plid);
|
||||
free_pli(&pli, 1);
|
||||
|
||||
if (pl_id > 0)
|
||||
scan_playlist_tracks(playlist.tracks_href, pl_id);
|
||||
else
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist: '%s' (%s) \n", playlist.name, playlist.uri);
|
||||
|
||||
@ -1633,13 +1660,24 @@ scan_playlists()
|
||||
static void
|
||||
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");
|
||||
spotify_saved_plid = 0;
|
||||
}
|
||||
|
||||
free_pli(&pli, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1649,18 +1687,29 @@ static void
|
||||
create_base_playlist()
|
||||
{
|
||||
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_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);
|
||||
if (ret < 0)
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Error adding base playlist\n");
|
||||
else
|
||||
spotify_base_plid = ret;
|
||||
free_pli(&pli, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
@ -1968,6 +2017,6 @@ struct library_source spotifyscanner =
|
||||
.metarescan = rescan,
|
||||
.initscan = initscan,
|
||||
.fullrescan = fullrescan,
|
||||
.queue_add = queue_add,
|
||||
.queue_item_add = queue_item_add,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user