From ddbf05c8c1f1e6fd25184bf7f22ee5b67eaaa582 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sun, 20 Jul 2008 15:56:51 +0100 Subject: [PATCH] Implement most of the playlist commands in the server. playlist-set is not yet implemented. None of this is tested yet. Organization: Straylight/Edgeware From: Richard Kettlewell --- lib/configuration.c | 2 + lib/configuration.h | 3 + lib/trackdb-playlists.c | 16 +++- server/server.c | 172 +++++++++++++++++++++++++++++++++++----- 4 files changed, 171 insertions(+), 22 deletions(-) diff --git a/lib/configuration.c b/lib/configuration.c index 2e75ecb..934e52b 100644 --- a/lib/configuration.c +++ b/lib/configuration.c @@ -952,6 +952,7 @@ static const struct conf conf[] = { { C(noticed_history), &type_integer, validate_positive }, { C(password), &type_string, validate_any }, { C(player), &type_stringlist_accum, validate_player }, + { C(playlist_lock_timeout), &type_integer, validate_positive }, { C(playlist_max) , &type_integer, validate_positive }, { C(plugins), &type_string_accum, validate_isdir }, { C(prefsync), &type_integer, validate_positive }, @@ -1200,6 +1201,7 @@ static struct config *config_default(void) { c->new_bias_age = 7 * 86400; /* 1 week */ c->new_bias = 9000000; /* 100 times the base weight */ c->playlist_max = INT_MAX; /* effectively no limit */ + c->playlist_lock_timeout = 10; /* 10s */ /* Default stopwords */ if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords)) exit(1); diff --git a/lib/configuration.h b/lib/configuration.h index d269beb..b94b684 100644 --- a/lib/configuration.h +++ b/lib/configuration.h @@ -188,6 +188,9 @@ struct config { /** @brief Maximum size of a playlist */ long playlist_max; + /** @brief Maximum lifetime of a playlist lock */ + long playlist_lock_timeout; + /* These values had better be non-negative */ #define BACKEND_ALSA 0 /**< Use ALSA (Linux only) */ #define BACKEND_COMMAND 1 /**< Execute a command */ diff --git a/lib/trackdb-playlists.c b/lib/trackdb-playlists.c index 1492f8c..77f7c13 100644 --- a/lib/trackdb-playlists.c +++ b/lib/trackdb-playlists.c @@ -147,7 +147,7 @@ static int playlist_may_write(const char *name, * * Possible return values: * - @c 0 on success - * - @c DB_NOTFOUND if the playlist doesn't exist + * - @c ENOENT if the playlist doesn't exist * - @c EINVAL if the playlist name is invalid * - @c EACCES if the playlist cannot be read by @p who */ @@ -165,6 +165,9 @@ int trackdb_playlist_get(const char *name, WITH_TRANSACTION(trackdb_playlist_get_tid(name, who, tracksp, ntracksp, sharep, tid)); + /* Don't expose libdb error codes too much */ + if(e == DB_NOTFOUND) + e = ENOENT; return e; } @@ -237,6 +240,10 @@ static int trackdb_playlist_get_tid(const char *name, * none, and the default sharing is private (if it is an owned one) or shared * (otherwise). * + * If neither @c tracks nor @c share are set then we only do an access check. + * The database is never modified (even to create the playlist) in this + * situation. + * * Possible return values: * - @c 0 on success * - @c EINVAL if the playlist name is invalid @@ -312,6 +319,9 @@ static int trackdb_playlist_set_tid(const char *name, } if(!playlist_may_write(name, who, s)) return EACCES; + /* If no change was requested then don't even create */ + if(!share && !tracks) + return 0; /* Set the new values */ if(share) kvp_set(&k, "share", share); @@ -418,7 +428,7 @@ static int trackdb_playlist_list_tid(const char *who, * - @c 0 on success * - @c EINVAL if the playlist name is invalid * - @c EACCES if the playlist cannot be modified by @p who - * - @c DB_NOTFOUND if the playlist doesn't exist + * - @c ENOENT if the playlist doesn't exist */ int trackdb_playlist_delete(const char *name, const char *who) { @@ -431,6 +441,8 @@ int trackdb_playlist_delete(const char *name, } /* We've checked as much as we can for now, now go and attempt the change */ WITH_TRANSACTION(trackdb_playlist_delete_tid(name, who, tid)); + if(e == DB_NOTFOUND) + e = ENOENT; return e; } diff --git a/server/server.c b/server/server.c index c4c889a..f95176c 100644 --- a/server/server.c +++ b/server/server.c @@ -74,6 +74,10 @@ struct conn { struct conn *next; /** @brief True if pending rescan had 'wait' set */ int rescan_wait; + /** @brief Playlist that this connection locks */ + const char *locked_playlist; + /** @brief When that playlist was locked */ + time_t locked_when; }; /** @brief Linked list of connections */ @@ -1026,21 +1030,25 @@ static int c_resolve(struct conn *c, return 1; } -static int c_tags(struct conn *c, - char attribute((unused)) **vec, - int attribute((unused)) nvec) { - char **tags = trackdb_alltags(); - - sink_printf(ev_writer_sink(c->w), "253 Tag list follows\n"); - while(*tags) { +static int list_response(struct conn *c, + const char *reply, + char **list) { + sink_printf(ev_writer_sink(c->w), "253 %s\n", reply); + while(*list) { sink_printf(ev_writer_sink(c->w), "%s%s\n", - **tags == '.' ? "." : "", *tags); - ++tags; + **list == '.' ? "." : "", *list); + ++list; } sink_writes(ev_writer_sink(c->w), ".\n"); return 1; /* completed */ } +static int c_tags(struct conn *c, + char attribute((unused)) **vec, + int attribute((unused)) nvec) { + return list_response(c, "Tag list follows", trackdb_alltags()); +} + static int c_set_global(struct conn *c, char **vec, int attribute((unused)) nvec) { @@ -1307,17 +1315,7 @@ static int c_userinfo(struct conn *c, static int c_users(struct conn *c, char attribute((unused)) **vec, int attribute((unused)) nvec) { - /* TODO de-dupe with c_tags */ - char **users = trackdb_listusers(); - - sink_writes(ev_writer_sink(c->w), "253 User list follows\n"); - while(*users) { - sink_printf(ev_writer_sink(c->w), "%s%s\n", - **users == '.' ? "." : "", *users); - ++users; - } - sink_writes(ev_writer_sink(c->w), ".\n"); - return 1; /* completed */ + return list_response(c, "User list follows", trackdb_listusers()); } /** @brief Base64 mapping table for confirmation strings @@ -1575,6 +1573,132 @@ static int c_schedule_add(struct conn *c, return 1; } +static int playlist_response(struct conn *c, + int err) { + switch(err) { + case 0: + assert(!"cannot cope with success"); + case EACCES: + sink_writes(ev_writer_sink(c->w), "550 Access denied\n"); + break; + case EINVAL: + sink_writes(ev_writer_sink(c->w), "550 Invalid playlist name\n"); + break; + case ENOENT: + sink_writes(ev_writer_sink(c->w), "555 No such playlist\n"); + break; + default: + sink_writes(ev_writer_sink(c->w), "550 Error accessing playlist\n"); + break; + } + return 1; +} + +static int c_playlist_get(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + char **tracks; + int err; + + if(!(err = trackdb_playlist_get(vec[0], c->who, &tracks, 0, 0))) + return list_response(c, "Playlist contents follows", tracks); + else + return playlist_response(c, err); +} + +static int c_playlist_set(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + /* TODO */ +} + +static int c_playlist_get_share(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + char *share; + int err; + + if(!(err = trackdb_playlist_get(vec[0], c->who, 0, 0, &share))) { + sink_printf(ev_writer_sink(c->w), "252 %s", quoteutf8(share)); + return 1; + } else + return playlist_response(c, err); +} + +static int c_playlist_set_share(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + int err; + + if(!(err = trackdb_playlist_set(vec[0], c->who, 0, 0, vec[1]))) { + sink_printf(ev_writer_sink(c->w), "250 OK"); + return 1; + } else + return playlist_response(c, err); +} + +static int c_playlists(struct conn *c, + char attribute((unused)) **vec, + int attribute((unused)) nvec) { + char **p; + + trackdb_playlist_list(c->who, &p, 0); + return list_response(c, "List of playlists follows", p); +} + +static int c_playlist_delete(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + int err; + + if(!(err = trackdb_playlist_delete(vec[0], c->who))) { + sink_writes(ev_writer_sink(c->w), "250 OK"); + return 1; + } else + return playlist_response(c, err); +} + +static int c_playlist_lock(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + int err; + struct conn *cc; + + /* Check we're allowed to modify this playlist */ + if((err = trackdb_playlist_set(vec[0], c->who, 0, 0, 0))) + return playlist_response(c, err); + /* If we hold a lock don't allow a new one */ + if(c->locked_playlist) { + sink_writes(ev_writer_sink(c->w), "550 Already holding a lock\n"); + return 1; + } + /* See if some other connection locks the same playlist */ + for(cc = connections; cc; cc = cc->next) + if(cc->locked_playlist && !strcmp(cc->locked_playlist, vec[0])) + break; + if(cc) { + /* TODO: implement config->playlist_lock_timeout */ + sink_writes(ev_writer_sink(c->w), "550 Already locked\n"); + return 1; + } + c->locked_playlist = xstrdup(vec[0]); + time(&c->locked_when); + sink_writes(ev_writer_sink(c->w), "250 Acquired lock\n"); + return 1; +} + +static int c_playlist_unlock(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + if(!c->locked_playlist) { + sink_writes(ev_writer_sink(c->w), "550 Not holding a lock\n"); + return 1; + } + c->locked_playlist = 0; + sink_writes(ev_writer_sink(c->w), "250 Released lock\n"); + return 1; +} + static const struct command { /** @brief Command name */ const char *name; @@ -1620,6 +1744,14 @@ static const struct command { { "pause", 0, 0, c_pause, RIGHT_PAUSE }, { "play", 1, 1, c_play, RIGHT_PLAY }, { "playing", 0, 0, c_playing, RIGHT_READ }, + { "playlist-delete", 1, 1, c_playlist_delete, RIGHT_PLAY }, + { "playlist-get", 1, 1, c_playlist_get, RIGHT_PLAY }, + { "playlist-get-share", 1, 1, c_playlist_get_share, RIGHT_PLAY }, + { "playlist-lock", 1, 1, c_playlist_lock, RIGHT_PLAY }, + { "playlist-set", 1, 1, c_playlist_set, RIGHT_PLAY }, + { "playlist-set-share", 2, 2, c_playlist_set_share, RIGHT_PLAY }, + { "playlist-unlock", 0, 0, c_playlist_unlock, RIGHT_PLAY }, + { "playlists", 0, 0, c_playlists, RIGHT_PLAY }, { "prefs", 1, 1, c_prefs, RIGHT_READ }, { "queue", 0, 0, c_queue, RIGHT_READ }, { "random-disable", 0, 0, c_random_disable, RIGHT_GLOBAL_PREFS }, -- [mdw]