exit(EXIT_FAILURE);
}
+static void cf_adopt(char **argv) {
+ if(disorder_adopt(getclient(), argv[0]))
+ exit(EXIT_FAILURE);
+}
+
+ static void cf_playlists(char attribute((unused)) **argv) {
+ char **vec;
+
+ if(disorder_playlists(getclient(), &vec, 0))
+ exit(EXIT_FAILURE);
+ while(*vec)
+ xprintf("%s\n", nullcheck(utf82mb(*vec++)));
+ }
+
+ static void cf_playlist_del(char **argv) {
+ if(disorder_playlist_delete(getclient(), argv[0]))
+ exit(EXIT_FAILURE);
+ }
+
+ static void cf_playlist_get(char **argv) {
+ char **vec;
+
+ if(disorder_playlist_get(getclient(), argv[0], &vec, 0))
+ exit(EXIT_FAILURE);
+ while(*vec)
+ xprintf("%s\n", nullcheck(utf82mb(*vec++)));
+ }
+
+ static void cf_playlist_set(char **argv) {
+ struct vector v[1];
+ FILE *input;
+ const char *tag;
+ char *l;
+
+ if(argv[1]) {
+ // Read track list from file
+ if(!(input = fopen(argv[1], "r")))
+ fatal(errno, "opening %s", argv[1]);
+ tag = argv[1];
+ } else {
+ // Read track list from standard input
+ input = stdin;
+ tag = "stdin";
+ }
+ vector_init(v);
+ while(!inputline(tag, input, &l, '\n')) {
+ if(!strcmp(l, "."))
+ break;
+ vector_append(v, l);
+ }
+ if(ferror(input))
+ fatal(errno, "reading %s", tag);
+ if(input != stdin)
+ fclose(input);
+ if(disorder_playlist_lock(getclient(), argv[0])
+ || disorder_playlist_set(getclient(), argv[0], v->vec, v->nvec)
+ || disorder_playlist_unlock(getclient()))
+ exit(EXIT_FAILURE);
+ }
+
static const struct command {
const char *name;
int min, max;
recent.c added.c queue-generic.c queue-generic.h queue-menu.c \
choose.c choose-menu.c choose-search.c popup.c misc.c \
control.c properties.c menu.c log.c progress.c login.c rtp.c \
- help.c ../lib/memgc.c settings.c users.c lookup.c playlists.c
+ help.c ../lib/memgc.c settings.c users.c lookup.c choose.h \
- popup.h
++ popup.h playlists.c
disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT) \
$(LIBASOUND) $(COREAUDIO) $(LIBDB)
disobedience_LDFLAGS=$(GTK_LIBS)
static void log_volume(void *v, int l, int r);
static void log_rescanned(void *v);
static void log_rights_changed(void *v, rights_type r);
+static void log_adopted(void *v, const char *id, const char *user);
+ static void log_playlist_created(void *v,
+ const char *playlist, const char *sharing);
+ static void log_playlist_modified(void *v,
+ const char *playlist, const char *sharing);
+ static void log_playlist_deleted(void *v,
+ const char *playlist);
/** @brief Callbacks for server state monitoring */
const disorder_eclient_log_callbacks log_callbacks = {
.volume = log_volume,
.rescanned = log_rescanned,
.rights_changed = log_rights_changed,
- .adopted = log_adopted
++ .adopted = log_adopted,
+ .playlist_created = log_playlist_created,
+ .playlist_modified = log_playlist_modified,
+ .playlist_deleted = log_playlist_deleted,
};
/** @brief Update everything */
--suppress_actions;
}
+/** @brief Called when a track is adopted */
+static void log_adopted(void attribute((unused)) *v,
+ const char attribute((unused)) *id,
+ const char attribute((unused)) *who) {
+ event_raise("queue-changed", 0);
+}
+
+ static void log_playlist_created(void attribute((unused)) *v,
+ const char *playlist,
+ const char attribute((unused)) *sharing) {
+ event_raise("playlist-created", (void *)playlist);
+ }
+
+ static void log_playlist_modified(void attribute((unused)) *v,
+ const char *playlist,
+ const char attribute((unused)) *sharing) {
+ event_raise("playlist-modified", (void *)playlist);
+ }
+
+ static void log_playlist_deleted(void attribute((unused)) *v,
+ const char *playlist) {
+ event_raise("playlist-deleted", (void *)playlist);
+ }
+
/*
Local Variables:
c-basic-offset:2
return rc;
}
+/** @brief Adopt a track
+ * @param c Client
+ * @param id Track ID to adopt
+ * @return 0 on success, non-0 on error
+ */
+int disorder_adopt(disorder_client *c, const char *id) {
+ return disorder_simple(c, 0, "adopt", id, (char *)0);
+}
+
+ /** @brief Delete a playlist
+ * @param c Client
+ * @param playlist Playlist to delete
+ * @return 0 on success, non-0 on error
+ */
+ int disorder_playlist_delete(disorder_client *c,
+ const char *playlist) {
+ return disorder_simple(c, 0, "playlist-delete", playlist, (char *)0);
+ }
+
+ /** @brief Get the contents of a playlist
+ * @param c Client
+ * @param playlist Playlist to get
+ * @param tracksp Where to put list of tracks
+ * @param ntracksp Where to put count of tracks
+ * @return 0 on success, non-0 on error
+ */
+ int disorder_playlist_get(disorder_client *c, const char *playlist,
+ char ***tracksp, int *ntracksp) {
+ return disorder_simple_list(c, tracksp, ntracksp,
+ "playlist-get", playlist, (char *)0);
+ }
+
+ /** @brief List all readable playlists
+ * @param c Client
+ * @param playlistsp Where to put list of playlists
+ * @param nplaylistsp Where to put count of playlists
+ * @return 0 on success, non-0 on error
+ */
+ int disorder_playlists(disorder_client *c,
+ char ***playlistsp, int *nplaylistsp) {
+ return disorder_simple_list(c, playlistsp, nplaylistsp,
+ "playlists", (char *)0);
+ }
+
+ /** @brief Get the sharing status of a playlist
+ * @param c Client
+ * @param playlist Playlist to inspect
+ * @param sharep Where to put sharing status
+ * @return 0 on success, non-0 on error
+ *
+ * Possible @p sharep values are @c public, @c private and @c shared.
+ */
+ int disorder_playlist_get_share(disorder_client *c, const char *playlist,
+ char **sharep) {
+ return disorder_simple(c, sharep,
+ "playlist-get-share", playlist, (char *)0);
+ }
+
+ /** @brief Get the sharing status of a playlist
+ * @param c Client
+ * @param playlist Playlist to modify
+ * @param share New sharing status
+ * @return 0 on success, non-0 on error
+ *
+ * Possible @p share values are @c public, @c private and @c shared.
+ */
+ int disorder_playlist_set_share(disorder_client *c, const char *playlist,
+ const char *share) {
+ return disorder_simple(c, 0,
+ "playlist-set-share", playlist, share, (char *)0);
+ }
+
+ /** @brief Lock a playlist for modifications
+ * @param c Client
+ * @param playlist Playlist to lock
+ * @return 0 on success, non-0 on error
+ */
+ int disorder_playlist_lock(disorder_client *c, const char *playlist) {
+ return disorder_simple(c, 0,
+ "playlist-lock", playlist, (char *)0);
+ }
+
+ /** @brief Unlock the locked playlist
+ * @param c Client
+ * @return 0 on success, non-0 on error
+ */
+ int disorder_playlist_unlock(disorder_client *c) {
+ return disorder_simple(c, 0,
+ "playlist-unlock", (char *)0);
+ }
+
+ /** @brief Set the contents of a playlst
+ * @param c Client
+ * @param playlist Playlist to modify
+ * @param tracks List of tracks
+ * @param ntracks Length of @p tracks (or -1 to count up to the first NULL)
+ * @return 0 on success, non-0 on error
+ */
+ int disorder_playlist_set(disorder_client *c,
+ const char *playlist,
+ char **tracks,
+ int ntracks) {
+ return disorder_simple_body(c, 0, tracks, ntracks,
+ "playlist-set", playlist, (char *)0);
+ }
+
/*
Local Variables:
c-basic-offset:2
const char *priority,
const char *action,
...);
+int disorder_adopt(disorder_client *c, const char *id);
+ int disorder_playlist_delete(disorder_client *c,
+ const char *playlist);
+ int disorder_playlist_get(disorder_client *c, const char *playlist,
+ char ***tracksp, int *ntracksp);
+ int disorder_playlists(disorder_client *c,
+ char ***playlistsp, int *nplaylists);
+ int disorder_playlist_get_share(disorder_client *c, const char *playlist,
+ char **sharep);
+ int disorder_playlist_set_share(disorder_client *c, const char *playlist,
+ const char *share);
+ int disorder_playlist_lock(disorder_client *c, const char *playlist);
+ int disorder_playlist_unlock(disorder_client *c);
+ int disorder_playlist_set(disorder_client *c,
+ const char *playlist,
+ char **tracks,
+ int ntracks);
#endif /* CLIENT_H */
c->new_max = 100;
c->reminder_interval = 600; /* 10m */
c->new_bias_age = 7 * 86400; /* 1 week */
- c->new_bias = 9000000; /* 100 times the base weight */
+ c->new_bias = 4500000; /* 50 times the base weight */
+ c->sox_generation = DEFAULT_SOX_GENERATION;
+ 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);
static void logentry_user_delete(disorder_eclient *c, int nvec, char **vec);
static void logentry_user_edit(disorder_eclient *c, int nvec, char **vec);
static void logentry_rights_changed(disorder_eclient *c, int nvec, char **vec);
+static void logentry_adopted(disorder_eclient *c, int nvec, char **vec);
+ static void logentry_playlist_created(disorder_eclient *c, int nvec, char **vec);
+ static void logentry_playlist_deleted(disorder_eclient *c, int nvec, char **vec);
+ static void logentry_playlist_modified(disorder_eclient *c, int nvec, char **vec);
/* Tables ********************************************************************/
"adduser", user, password, rights, (char *)0);
}
+/** @brief Adopt a track
+ * @param c Client
+ * @param completed Called on completion
+ * @param id Track ID
+ * @param v Passed to @p completed
+ */
+int disorder_eclient_adopt(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *id,
+ void *v) {
+ return simple(c, no_response_opcallback, (void (*)())completed, v,
+ "adopt", id, (char *)0);
+}
+
+ /** @brief Get the list of playlists
+ * @param c Client
+ * @param completed Called with list of playlists
+ * @param v Passed to @p completed
+ *
+ * The playlist list is not sorted in any particular order.
+ */
+ int disorder_eclient_playlists(disorder_eclient *c,
+ disorder_eclient_list_response *completed,
+ void *v) {
+ return simple(c, list_response_opcallback, (void (*)())completed, v,
+ "playlists", (char *)0);
+ }
+
+ /** @brief Delete a playlist
+ * @param c Client
+ * @param completed Called on completion
+ * @param playlist Playlist to delete
+ * @param v Passed to @p completed
+ */
+ int disorder_eclient_playlist_delete(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *playlist,
+ void *v) {
+ return simple(c, no_response_opcallback, (void (*)())completed, v,
+ "playlist-delete", playlist, (char *)0);
+ }
+
+ /** @brief Lock a playlist
+ * @param c Client
+ * @param completed Called on completion
+ * @param playlist Playlist to lock
+ * @param v Passed to @p completed
+ */
+ int disorder_eclient_playlist_lock(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *playlist,
+ void *v) {
+ return simple(c, no_response_opcallback, (void (*)())completed, v,
+ "playlist-lock", playlist, (char *)0);
+ }
+
+ /** @brief Unlock the locked a playlist
+ * @param c Client
+ * @param completed Called on completion
+ * @param v Passed to @p completed
+ */
+ int disorder_eclient_playlist_unlock(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ void *v) {
+ return simple(c, no_response_opcallback, (void (*)())completed, v,
+ "playlist-unlock", (char *)0);
+ }
+
+ /** @brief Set a playlist's sharing
+ * @param c Client
+ * @param completed Called on completion
+ * @param playlist Playlist to modify
+ * @param sharing @c "public" or @c "private"
+ * @param v Passed to @p completed
+ */
+ int disorder_eclient_playlist_set_share(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *playlist,
+ const char *sharing,
+ void *v) {
+ return simple(c, no_response_opcallback, (void (*)())completed, v,
+ "playlist-set-share", playlist, sharing, (char *)0);
+ }
+
+ /** @brief Get a playlist's sharing
+ * @param c Client
+ * @param completed Called with sharing status
+ * @param playlist Playlist to inspect
+ * @param v Passed to @p completed
+ */
+ int disorder_eclient_playlist_get_share(disorder_eclient *c,
+ disorder_eclient_string_response *completed,
+ const char *playlist,
+ void *v) {
+ return simple(c, string_response_opcallback, (void (*)())completed, v,
+ "playlist-get-share", playlist, (char *)0);
+ }
+
+ /** @brief Set a playlist
+ * @param c Client
+ * @param completed Called on completion
+ * @param playlist Playlist to modify
+ * @param tracks List of tracks
+ * @param ntracks Number of tracks
+ * @param v Passed to @p completed
+ */
+ int disorder_eclient_playlist_set(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *playlist,
+ char **tracks,
+ int ntracks,
+ void *v) {
+ return simple_body(c, no_response_opcallback, (void (*)())completed, v,
+ ntracks, tracks,
+ "playlist-set", playlist, (char *)0);
+ }
+
+ /** @brief Get a playlist's contents
+ * @param c Client
+ * @param completed Called with playlist contents
+ * @param playlist Playlist to inspect
+ * @param v Passed to @p completed
+ */
+ int disorder_eclient_playlist_get(disorder_eclient *c,
+ disorder_eclient_list_response *completed,
+ const char *playlist,
+ void *v) {
+ return simple(c, list_response_opcallback, (void (*)())completed, v,
+ "playlist-get", playlist, (char *)0);
+ }
+
/* Log clients ***************************************************************/
/** @brief Monitor the server log
/*
* This file is part of DisOrder.
- * Copyright (C) 2006, 2007 Richard Kettlewell
+ * Copyright (C) 2006-2008 Richard Kettlewell
*
- * This program is free software; you can redistribute it and/or modify
+ * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file lib/eclient.h
* @brief Client code for event-driven programs
/** @brief Called when your rights change */
void (*rights_changed)(void *v, rights_type new_rights);
+ /** @brief Called when a track is adopted */
+ void (*adopted)(void *v, const char *id, const char *who);
++
+ /** @brief Called when a new playlist is created */
+ void (*playlist_created)(void *v, const char *playlist, const char *sharing);
+
+ /** @brief Called when a playlist is modified */
+ void (*playlist_modified)(void *v, const char *playlist, const char *sharing);
+
+ /** @brief Called when a new playlist is deleted */
+ void (*playlist_deleted)(void *v, const char *playlist);
} disorder_eclient_log_callbacks;
/* State bits */
void *v);
void disorder_eclient_enable_connect(disorder_eclient *c);
void disorder_eclient_disable_connect(disorder_eclient *c);
+int disorder_eclient_adopt(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *id,
+ void *v);
+ int disorder_eclient_playlists(disorder_eclient *c,
+ disorder_eclient_list_response *completed,
+ void *v);
+ int disorder_eclient_playlist_delete(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *playlist,
+ void *v);
+ int disorder_eclient_playlist_lock(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *playlist,
+ void *v);
+ int disorder_eclient_playlist_unlock(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ void *v);
+ int disorder_eclient_playlist_set_share(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *playlist,
+ const char *sharing,
+ void *v);
+ int disorder_eclient_playlist_get_share(disorder_eclient *c,
+ disorder_eclient_string_response *completed,
+ const char *playlist,
+ void *v);
+ int disorder_eclient_playlist_set(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ const char *playlist,
+ char **tracks,
+ int ntracks,
+ void *v);
+ int disorder_eclient_playlist_get(disorder_eclient *c,
+ disorder_eclient_list_response *completed,
+ const char *playlist,
+ void *v);
+
#endif
/*
/* sanity checks */
assert(opened == 1);
--opened;
- if((err = trackdb_tracksdb->close(trackdb_tracksdb, 0)))
- fatal(0, "error closing tracks.db: %s", db_strerror(err));
- if((err = trackdb_searchdb->close(trackdb_searchdb, 0)))
- fatal(0, "error closing search.db: %s", db_strerror(err));
- if((err = trackdb_tagsdb->close(trackdb_tagsdb, 0)))
- fatal(0, "error closing tags.db: %s", db_strerror(err));
- if((err = trackdb_prefsdb->close(trackdb_prefsdb, 0)))
- fatal(0, "error closing prefs.db: %s", db_strerror(err));
- if((err = trackdb_globaldb->close(trackdb_globaldb, 0)))
- fatal(0, "error closing global.db: %s", db_strerror(err));
- if((err = trackdb_noticeddb->close(trackdb_noticeddb, 0)))
- fatal(0, "error closing noticed.db: %s", db_strerror(err));
- if((err = trackdb_scheduledb->close(trackdb_scheduledb, 0)))
- fatal(0, "error closing schedule.db: %s", db_strerror(err));
- if((err = trackdb_usersdb->close(trackdb_usersdb, 0)))
- fatal(0, "error closing users.db: %s", db_strerror(err));
- if((err = trackdb_playlistsdb->close(trackdb_playlistsdb, 0)))
- fatal(0, "error closing playlists.db: %s", db_strerror(err));
- trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0;
- trackdb_tagsdb = trackdb_globaldb = 0;
+#define CLOSE(N, V) do { \
+ if(V && (err = V->close(V, 0))) \
+ fatal(0, "error closing %s: %s", N, db_strerror(err)); \
+ V = 0; \
+} while(0)
+ CLOSE("tracks.db", trackdb_tracksdb);
+ CLOSE("search.db", trackdb_searchdb);
+ CLOSE("tags.db", trackdb_tagsdb);
+ CLOSE("prefs.db", trackdb_prefsdb);
+ CLOSE("global.db", trackdb_globaldb);
+ CLOSE("noticed.db", trackdb_noticeddb);
+ CLOSE("schedule.db", trackdb_scheduledb);
+ CLOSE("users.db", trackdb_usersdb);
++ CLOSE("playlists.db", trackdb_playlistsdb);
D(("closed databases"));
}
"""Add a scheduled event"""
self._simple("schedule-add", str(when), priority, action, *rest)
+ def adopt(self, id):
+ """Adopt a randomly picked track"""
+ self._simple("adopt", id)
+
+ def playlist_delete(self, playlist):
+ """Delete a playlist"""
+ res, details = self._simple("playlist-delete", playlist)
+ if res == 555:
+ raise operationError(res, details, "playlist-delete")
+
+ def playlist_get(self, playlist):
+ """Get the contents of a playlist
+
+ The return value is an array of track names, or None if there is no
+ such playlist."""
+ res, details = self._simple("playlist-get", playlist)
+ if res == 555:
+ return None
+ return self._body()
+
+ def playlist_lock(self, playlist):
+ """Lock a playlist. Playlists can only be modified when locked."""
+ self._simple("playlist-lock", playlist)
+
+ def playlist_unlock(self):
+ """Unlock the locked playlist."""
+ self._simple("playlist-unlock")
+
+ def playlist_set(self, playlist, tracks):
+ """Set the contents of a playlist. The playlist must be locked.
+
+ Arguments:
+ playlist -- Playlist to set
+ tracks -- Array of tracks"""
+ self._simple_body(tracks, "playlist-set", playlist)
+
+ def playlist_set_share(self, playlist, share):
+ """Set the sharing status of a playlist"""
+ self._simple("playlist-set-share", playlist, share)
+
+ def playlist_get_share(self, playlist):
+ """Returns the sharing status of a playlist"""
+ res, details = self._simple("playlist-get-share", playlist)
+ if res == 555:
+ return None
+ return _split(details)[0]
+
+ def playlists(self):
+ """Returns the list of visible playlists"""
+ self._simple("playlists")
+ return self._body()
+
########################################################################
# I/O infrastructure
tags new rtp-address adduser users edituser deluser userinfo
setup-guest schedule-del schedule-list
schedule-set-global schedule-unset-global schedule-play
+ adopt
+ playlist-del playlist-get playlist-set playlists
-h --help -H --help-commands --version -V --config -c
--length --debug -d" \
disorder
return 1;
}
+static int c_adopt(struct conn *c,
+ char **vec,
+ int attribute((unused)) nvec) {
+ struct queue_entry *q;
+
+ if(!c->who) {
+ sink_writes(ev_writer_sink(c->w), "550 no identity\n");
+ return 1;
+ }
+ if(!(q = queue_find(vec[0]))) {
+ sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n");
+ return 1;
+ }
+ if(q->origin != origin_random) {
+ sink_writes(ev_writer_sink(c->w), "550 not a random track\n");
+ return 1;
+ }
+ q->origin = origin_adopted;
+ q->submitter = xstrdup(c->who);
+ eventlog("adopted", q->id, q->submitter, (char *)0);
+ queue_write();
+ sink_writes(ev_writer_sink(c->w), "250 OK\n");
+ 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) {
+ return fetch_body(c, c_playlist_set_body, vec[0]);
+ }
+
+ static int c_playlist_set_body(struct conn *c,
+ char **body,
+ int nbody,
+ void *u) {
+ const char *playlist = u;
+ int err;
+
+ if(!c->locked_playlist
+ || strcmp(playlist, c->locked_playlist)) {
+ sink_writes(ev_writer_sink(c->w), "550 Playlist is not locked\n");
+ return 1;
+ }
+ if(!(err = trackdb_playlist_set(playlist, c->who,
+ body, nbody, 0))) {
+ sink_printf(ev_writer_sink(c->w), "250 OK\n");
+ return 1;
+ } else
+ return playlist_response(c, err);
+ }
+
+ 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\n", 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\n");
+ 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\n");
+ 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 attribute((unused)) **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;