Merge branch 'playlists1'

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

View File

@ -157,6 +157,10 @@ library {
# to trigger a rescan.
# 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

View File

@ -95,6 +95,7 @@ static cfg_opt_t sec_library[] =
CFG_STR_LIST("filetypes_ignore", "{.db,.ini,.db-journal,.pdf,.metadata}", CFGF_NONE),
CFG_STR_LIST("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),

View File

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

253
src/db.c
View File

@ -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);
return -1;
}
int
db_pl_update(struct playlist_info *pli)
{
int ret;
/* 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 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);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return -1;
}
fixup_tags_pli(pli);
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);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
ret = db_statement_run(db_statements.playlists_update);
if (ret < 0)
return -1;
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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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, &param);
}
static enum command_state
playlist_remove(void *arg, int *retval)
{
const char *virtual_path;
int i;
int ret = LIBRARY_ERROR;
virtual_path = arg;
DPRINTF(E_DBG, L_LIB, "Removing playlist at path '%s'\n", virtual_path);
for (i = 0; sources[i]; i++)
{
if (sources[i]->disabled || !sources[i]->playlist_remove)
{
DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support playlist_remove\n", sources[i]->name);
continue;
}
ret = sources[i]->playlist_remove(virtual_path);
if (ret == LIBRARY_OK)
{
DPRINTF(E_DBG, L_LIB, "Removing playlist '%s' with library source '%s'\n", virtual_path, sources[i]->name);
listener_notify(LISTENER_STORED_PLAYLIST);
break;
}
}
*retval = ret;
return COMMAND_END;
return commands_exec_sync(cmdbase, playlist_item_add, NULL, &param);
}
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, &param);
}
int
library_exec_async(command_function func, void *arg)
{

View File

@ -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);

View File

@ -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,
};

View File

@ -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__ */

View File

@ -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;

View File

@ -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)));
}
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;
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);
ret = library_playlist_save(pli);
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);
return;
return false;
}
// Disable, only used for saving timestamp
db_pl_disable_bypath(file, STRIP_NONE, 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);
if (fd < 0)
{

View File

@ -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,94 +342,78 @@ process_regular_file(int pl_id, char *path)
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
scan_playlist(const char *file, time_t mtime, int dir_id)
{
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)
pl_format = playlist_type(file);
if (pl_format == PLAYLIST_UNKNOWN)
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)
{
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);
// 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,16 +485,15 @@ 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)
free_mfi(&mfi, 1);
// 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))
DPRINTF(E_LOG, L_SCAN, "Error reading playlist '%s' (only added %d tracks): %s\n", file, nadded, strerror(errno));

View File

@ -35,6 +35,8 @@
#include "db.h"
#include "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;
}

View File

@ -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)

View File

@ -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,
};