From: Richard Kettlewell Date: Tue, 17 Feb 2009 20:29:50 +0000 (+0000) Subject: Merge playlist branch against trunk to date. X-Git-Tag: 5.0~86^2~4^2~1 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/812b526d127c6657e571db8b33a58137af6709cd Merge playlist branch against trunk to date. --- 812b526d127c6657e571db8b33a58137af6709cd diff --cc clients/disorder.c index 8dc6d7f,b57a8de..4161d93 --- a/clients/disorder.c +++ b/clients/disorder.c @@@ -577,11 -588,61 +598,66 @@@ static void cf_schedule_unset_global(ch 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; diff --cc disobedience/Makefile.am index e7072c7,db74563..94a4c78 --- a/disobedience/Makefile.am +++ b/disobedience/Makefile.am @@@ -27,8 -29,7 +27,8 @@@ disobedience_SOURCES=disobedience.h dis 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) diff --cc disobedience/log.c index 652c4e9,9a94e06..f1c4f79 --- a/disobedience/log.c +++ b/disobedience/log.c @@@ -41,7 -43,12 +41,13 @@@ static void log_state(void *v, unsigne 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 = { @@@ -59,7 -66,9 +65,10 @@@ .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 */ @@@ -204,13 -213,23 +213,30 @@@ static void log_rights_changed(void att --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 diff --cc lib/client.c index 00d7cf5,5c4cac5..f8a2c9e --- a/lib/client.c +++ b/lib/client.c @@@ -1302,15 -1361,103 +1359,112 @@@ int disorder_schedule_add(disorder_clie 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 diff --cc lib/client.h index 89f3037,37920ba..f7ff728 --- a/lib/client.h +++ b/lib/client.h @@@ -131,7 -133,22 +131,23 @@@ int disorder_schedule_add(disorder_clie 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 */ diff --cc lib/configuration.c index 2a0b89c,934e52b..cd83224 --- a/lib/configuration.c +++ b/lib/configuration.c @@@ -1195,8 -1199,9 +1197,10 @@@ static struct config *config_default(vo 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); diff --cc lib/eclient.c index 40639f3,8e89a31..676aa06 --- a/lib/eclient.c +++ b/lib/eclient.c @@@ -186,7 -191,9 +189,10 @@@ static void logentry_user_confirm(disor 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 ********************************************************************/ @@@ -1406,20 -1465,123 +1465,137 @@@ int disorder_eclient_adduser(disorder_e "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 diff --cc lib/eclient.h index ce5c582,2bbc367..fae610c --- a/lib/eclient.h +++ b/lib/eclient.h @@@ -1,19 -1,21 +1,19 @@@ /* * 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 . */ /** @file lib/eclient.h * @brief Client code for event-driven programs @@@ -166,8 -168,14 +166,17 @@@ typedef struct disorder_eclient_log_cal /** @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 */ @@@ -486,10 -496,40 +497,44 @@@ int disorder_eclient_adduser(disorder_e 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 /* diff --cc lib/trackdb.c index e1bbfc8,a364446..b98752f --- a/lib/trackdb.c +++ b/lib/trackdb.c @@@ -490,19 -494,26 +498,20 @@@ void trackdb_close(void) /* 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")); } diff --cc python/disorder.py.in index e873e49,47d7090..3e1541e --- a/python/disorder.py.in +++ b/python/disorder.py.in @@@ -905,10 -909,54 +907,58 @@@ class client """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 diff --cc scripts/completion.bash index 89eb63d,d18a31d..2766b17 --- a/scripts/completion.bash +++ b/scripts/completion.bash @@@ -32,7 -34,7 +32,8 @@@ complete -o default 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 diff --cc server/server.c index 2874357,64d29e0..320cbb1 --- a/server/server.c +++ b/server/server.c @@@ -1573,31 -1618,152 +1616,177 @@@ static int c_schedule_add(struct conn * 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;