2 * This file is part of DisOrder
3 * Copyright (C) 2008 Richard Kettlewell
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 /** @file lib/trackdb-playlists.c
21 * @brief Track database playlist support
23 * This file implements reading and modification of playlists, including access
24 * control, but not locking or event logging (at least yet).
30 #include "trackdb-int.h"
33 #include "configuration.h"
38 static int trackdb_playlist_get_tid(const char *name,
44 static int trackdb_playlist_set_tid(const char *name,
50 static int trackdb_playlist_list_tid(const char *who,
54 static int trackdb_playlist_delete_tid(const char *name,
58 /** @brief Check read access rights
59 * @param name Playlist name
60 * @param who Who wants to read
61 * @param share Playlist share status
63 static int playlist_may_read(const char *name,
68 if(playlist_parse_name(name, &owner, 0))
70 /* Anyone can read shared playlists */
73 /* You can always read playlists you own */
74 if(!strcmp(owner, who))
76 /* You can read public playlists */
77 if(!strcmp(share, "public"))
79 /* Anything else is prohibited */
83 /** @brief Check modify access rights
84 * @param name Playlist name
85 * @param who Who wants to modify
86 * @param share Playlist share status
88 static int playlist_may_write(const char *name,
90 const char attribute((unused)) *share) {
93 if(playlist_parse_name(name, &owner, 0))
95 /* Anyone can modify shared playlists */
98 /* You can always modify playlists you own */
99 if(!strcmp(owner, who))
101 /* Anything else is prohibited */
105 /** @brief Get playlist data
106 * @param name Name of playlist
107 * @param who Who wants to know
108 * @param tracksp Where to put list of tracks, or NULL
109 * @param ntracksp Where to put count of tracks, or NULL
110 * @param sharep Where to put sharing type, or NULL
111 * @return 0 on success, non-0 on error
113 * Possible return values:
115 * - @c ENOENT if the playlist doesn't exist
116 * - @c EINVAL if the playlist name is invalid
117 * - @c EACCES if the playlist cannot be read by @p who
119 int trackdb_playlist_get(const char *name,
126 if(playlist_parse_name(name, 0, 0)) {
127 disorder_error(0, "invalid playlist name '%s'", name);
130 WITH_TRANSACTION(trackdb_playlist_get_tid(name, who,
131 tracksp, ntracksp, sharep,
133 /* Don't expose libdb error codes too much */
139 static int trackdb_playlist_get_tid(const char *name,
149 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
151 /* Get sharability */
152 if(!(s = kvp_get(k, "sharing"))) {
153 disorder_error(0, "playlist '%s' has no 'sharing' key", name);
156 /* Check the read is allowed */
157 if(!playlist_may_read(name, who, s))
159 /* Return sharability */
161 *sharep = xstrdup(s);
162 /* Get track count */
163 if(!(s = kvp_get(k, "count"))) {
164 disorder_error(0, "playlist '%s' has no 'count' key", name);
169 disorder_error(0, "playlist '%s' has negative count", name);
172 /* Return track count */
177 char **tracks = xcalloc(ntracks + 1, sizeof (char *));
180 for(int n = 0; n < ntracks; ++n) {
181 snprintf(b, sizeof b, "%d", n);
182 if(!(s = kvp_get(k, b))) {
183 disorder_error(0, "playlist '%s' lacks track %d", name, n);
186 tracks[n] = xstrdup(s);
189 /* Return track list */
195 /** @brief Modify or create a playlist
196 * @param name Playlist name
197 * @param who User modifying playlist
198 * @param tracks List of tracks to set, or NULL to leave alone
199 * @param ntracks Length of @p tracks
200 * @param share Sharing status, or NULL to leave alone
201 * @return 0 on success, non-0 on error
203 * If the playlist exists it is just modified.
205 * If the playlist does not exist it is created. The default set of tracks is
206 * none, and the default sharing is private (if it is an owned one) or shared
209 * If neither @c tracks nor @c share are set then we only do an access check.
210 * The database is never modified (even to create the playlist) in this
213 * Possible return values:
215 * - @c EINVAL if the playlist name is invalid
216 * - @c EACCES if the playlist cannot be modified by @p who
218 int trackdb_playlist_set(const char *name,
226 if(playlist_parse_name(name, &owner, 0)) {
227 disorder_error(0, "invalid playlist name '%s'", name);
230 /* Check valid share types */
233 /* Playlists with an owner must be public or private */
234 if(strcmp(share, "public")
235 && strcmp(share, "private")) {
236 disorder_error(0, "playlist '%s' must be public or private", name);
240 /* Playlists with no owner must be shared */
241 if(strcmp(share, "shared")) {
242 disorder_error(0, "playlist '%s' must be shared", name);
247 /* We've checked as much as we can for now, now go and attempt the change */
248 WITH_TRANSACTION(trackdb_playlist_set_tid(name, who, tracks, ntracks, share,
253 static int trackdb_playlist_set_tid(const char *name,
262 const char *event = "playlist_modified";
264 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid))
267 /* If the playlist doesn't exist set some defaults */
268 if(e == DB_NOTFOUND) {
269 char *defshare, *owner;
271 if(playlist_parse_name(name, &owner, &defshare))
273 /* Can't create a non-shared playlist belonging to someone else. In fact
274 * this should be picked up by playlist_may_write() below but it's clearer
276 if(owner && strcmp(owner, who))
279 kvp_set(&k, "count", 0);
280 kvp_set(&k, "sharing", defshare);
281 event = "playlist_created";
283 /* Check that the modification is allowed */
284 if(!(s = kvp_get(k, "sharing"))) {
285 disorder_error(0, "playlist '%s' has no 'sharing' key", name);
288 if(!playlist_may_write(name, who, s))
290 /* If no change was requested then don't even create */
291 if(!share && !tracks)
293 /* Set the new values */
295 kvp_set(&k, "sharing", share);
300 /* Sanity check track count */
301 if(ntracks < 0 || ntracks > config->playlist_max) {
302 disorder_error(0, "invalid track count %d", ntracks);
306 for(n = 0; n < ntracks; ++n) {
307 snprintf(b, sizeof b, "%d", n);
308 kvp_set(&k, b, tracks[n]);
310 /* Get the old track count */
311 if((s = kvp_get(k, "count")))
315 /* Delete old slots */
316 for(; n < oldcount; ++n) {
317 snprintf(b, sizeof b, "%d", n);
318 kvp_set(&k, b, NULL);
320 /* Set the new count */
321 snprintf(b, sizeof b, "%d", ntracks);
322 kvp_set(&k, "count", b);
324 /* Store the resulting record */
325 e = trackdb_putdata(trackdb_playlistsdb, name, k, tid, 0);
328 eventlog(event, name, kvp_get(k, "sharing"), (char *)0);
332 /** @brief Get a list of playlists
333 * @param who Who wants to know
334 * @param playlistsp Where to put list of playlists
335 * @param nplaylistsp Where to put count of playlists, or NULL
337 void trackdb_playlist_list(const char *who,
342 WITH_TRANSACTION(trackdb_playlist_list_tid(who, playlistsp, nplaylistsp,
346 static int trackdb_playlist_list_tid(const char *who,
356 c = trackdb_opencursor(trackdb_playlistsdb, tid);
357 memset(k, 0, sizeof k);
358 while(!(e = c->c_get(c, k, prepare_data(d), DB_NEXT))) {
359 char *name = xstrndup(k->data, k->size), *owner;
360 const char *share = kvp_get(kvp_urldecode(d->data, d->size),
363 /* Extract owner; malformed names are skipped */
364 if(playlist_parse_name(name, &owner, 0)) {
365 disorder_error(0, "invalid playlist name '%s' found in database", name);
369 disorder_error(0, "playlist '%s' has no 'sharing' key", name);
372 /* Always list public and shared playlists
373 * Only list private ones to their owner
374 * Don't list anything else
376 if(!strcmp(share, "public")
377 || !strcmp(share, "shared")
378 || (!strcmp(share, "private")
379 && owner && !strcmp(owner, who)))
380 vector_append(v, name);
382 trackdb_closecursor(c);
386 case DB_LOCK_DEADLOCK:
389 disorder_fatal(0, "c->c_get: %s", db_strerror(e));
393 *playlistsp = v->vec;
395 *nplaylistsp = v->nvec;
399 /** @brief Delete a playlist
400 * @param name Playlist name
401 * @param who Who is deleting it
402 * @return 0 on success, non-0 on error
404 * Possible return values:
406 * - @c EINVAL if the playlist name is invalid
407 * - @c EACCES if the playlist cannot be modified by @p who
408 * - @c ENOENT if the playlist doesn't exist
410 int trackdb_playlist_delete(const char *name,
415 if(playlist_parse_name(name, &owner, 0)) {
416 disorder_error(0, "invalid playlist name '%s'", name);
419 /* We've checked as much as we can for now, now go and attempt the change */
420 WITH_TRANSACTION(trackdb_playlist_delete_tid(name, who, tid));
426 static int trackdb_playlist_delete_tid(const char *name,
433 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
435 /* Check that modification is allowed */
436 if(!(s = kvp_get(k, "sharing"))) {
437 disorder_error(0, "playlist '%s' has no 'sharing' key", name);
440 if(!playlist_may_write(name, who, s))
442 /* Delete the playlist */
443 e = trackdb_delkey(trackdb_playlistsdb, name, tid);
445 eventlog("playlist_deleted", name, 0);