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"
36 static int trackdb_playlist_get_tid(const char *name,
42 static int trackdb_playlist_set_tid(const char *name,
48 static int trackdb_playlist_list_tid(const char *who,
52 static int trackdb_playlist_delete_tid(const char *name,
56 /** @brief Parse a playlist name
57 * @param name Playlist name
58 * @param ownerp Where to put owner, or NULL
59 * @param sharep Where to put default sharing, or NULL
60 * @return 0 on success, -1 on error
62 * Playlists take the form USER.PLAYLIST or just PLAYLIST. The PLAYLIST part
63 * is alphanumeric and nonempty. USER is a username (see valid_username()).
65 int playlist_parse_name(const char *name,
68 const char *dot = strchr(name, '.'), *share;
73 owner = xstrndup(name, dot - name);
74 if(!valid_username(owner))
76 if(!valid_username(dot + 1))
81 if(!valid_username(name))
89 *sharep = xstrdup(share);
93 /** @brief Check read access rights
94 * @param name Playlist name
95 * @param who Who wants to read
96 * @param share Playlist share status
98 static int playlist_may_read(const char *name,
103 if(!playlist_parse_name(name, &owner, 0))
105 /* Anyone can read shared playlists */
108 /* You can always read playlists you own */
109 if(!strcmp(owner, who))
111 /* You can read public playlists */
112 if(!strcmp(share, "public"))
114 /* Anything else is prohibited */
118 /** @brief Check modify access rights
119 * @param name Playlist name
120 * @param who Who wants to modify
121 * @param share Playlist share status
123 static int playlist_may_write(const char *name,
125 const char attribute((unused)) *share) {
128 if(!playlist_parse_name(name, &owner, 0))
130 /* Anyone can modify shared playlists */
133 /* You can always modify playlists you own */
134 if(!strcmp(owner, who))
136 /* Anything else is prohibited */
140 /** @brief Get playlist data
141 * @param name Name of playlist
142 * @param who Who wants to know
143 * @param tracksp Where to put list of tracks, or NULL
144 * @param ntracksp Where to put count of tracks, or NULL
145 * @param sharep Where to put sharing type, or NULL
146 * @return 0 on success, non-0 on error
148 * Possible return values:
150 * - @c DB_NOTFOUND if the playlist doesn't exist
151 * - @c EINVAL if the playlist name is invalid
152 * - @c EACCES if the playlist cannot be read by @p who
154 int trackdb_playlist_get(const char *name,
161 if(!playlist_parse_name(name, 0, 0)) {
162 error(0, "invalid playlist name '%s'", name);
165 WITH_TRANSACTION(trackdb_playlist_get_tid(name, who,
166 tracksp, ntracksp, sharep,
171 static int trackdb_playlist_get_tid(const char *name,
181 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
183 /* Get sharability */
184 if(!(s = kvp_get(k, "sharing"))) {
185 error(0, "playlist '%s' has no 'sharing' key", name);
188 /* Check the read is allowed */
189 if(!playlist_may_read(name, who, s))
191 /* Return sharability */
193 *sharep = xstrdup(s);
194 /* Get track count */
195 if(!(s = kvp_get(k, "count"))) {
196 error(0, "playlist '%s' has no 'count' key", name);
201 error(0, "playlist '%s' has negative count", name);
204 /* Return track count */
209 char **tracks = xcalloc(ntracks + 1, sizeof (char *));
212 for(int n = 0; n < ntracks; ++n) {
213 snprintf(b, sizeof b, "%d", n);
214 if(!(s = kvp_get(k, b))) {
215 error(0, "playlist '%s' lacks track %d", name, n);
218 tracks[n] = xstrdup(s);
221 /* Return track list */
227 /** @brief Modify or create a playlist
228 * @param name Playlist name
229 * @param tracks List of tracks to set, or NULL to leave alone
230 * @param ntracks Length of @p tracks
231 * @param share Sharing status, or NULL to leave alone
232 * @return 0 on success, non-0 on error
234 * If the playlist exists it is just modified.
236 * If the playlist does not exist it is created. The default set of tracks is
237 * none, and the default sharing is private (if it is an owned one) or shared
240 * Possible return values:
242 * - @c EINVAL if the playlist name is invalid
243 * - @c EACCES if the playlist cannot be modified by @p who
245 int trackdb_playlist_set(const char *name,
253 if(!playlist_parse_name(name, &owner, 0)) {
254 error(0, "invalid playlist name '%s'", name);
257 /* Check valid share types */
260 /* Playlists with an owner must be public or private */
261 if(strcmp(share, "public")
262 && strcmp(share, "private")) {
263 error(0, "playlist '%s' must be public or private", name);
267 /* Playlists with no owner must be shared */
268 if(strcmp(share, "shared")) {
269 error(0, "playlist '%s' must be shared", name);
274 /* We've checked as much as we can for now, now go and attempt the change */
275 WITH_TRANSACTION(trackdb_playlist_set_tid(name, who, tracks, ntracks, share,
280 static int trackdb_playlist_set_tid(const char *name,
290 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid))
293 /* If the playlist doesn't exist set some defaults */
294 if(e == DB_NOTFOUND) {
295 char *defshare, *owner;
297 if(playlist_parse_name(name, &owner, &defshare))
299 /* Can't create a non-shared playlist belonging to someone else. In fact
300 * this should be picked up by playlist_may_write() below but it's clearer
302 if(owner && strcmp(owner, who))
305 kvp_set(&k, "count", 0);
306 kvp_set(&k, "sharing", defshare);
308 /* Check that the modification is allowed */
309 if(!(s = kvp_get(k, "sharing"))) {
310 error(0, "playlist '%s' has no 'sharing' key", name);
313 if(!playlist_may_write(name, who, s))
315 /* Set the new values */
317 kvp_set(&k, "share", share);
322 /* Sanity check track count */
323 if(ntracks < 0 || ntracks > config->playlist_max) {
324 error(0, "invalid track count %d", ntracks);
328 for(n = 0; n < ntracks; ++n) {
329 snprintf(b, sizeof b, "%d", n);
330 kvp_set(&k, b, tracks[n]);
332 /* Get the old track count */
333 if((s = kvp_get(k, "count")))
337 /* Delete old slots */
338 for(; n < oldcount; ++n) {
339 snprintf(b, sizeof b, "%d", n);
340 kvp_set(&k, b, NULL);
342 /* Set the new count */
343 snprintf(b, sizeof b, "%d", ntracks);
344 kvp_set(&k, "count", b);
346 /* Store the resulting record */
347 return trackdb_putdata(trackdb_playlistsdb, name, k, tid, 0);
350 /** @brief Get a list of playlists
351 * @param who Who wants to know
352 * @param playlistsp Where to put list of playlists
353 * @param nplaylistsp Where to put count of playlists, or NULL
355 void trackdb_playlist_list(const char *who,
360 WITH_TRANSACTION(trackdb_playlist_list_tid(who, playlistsp, nplaylistsp,
364 static int trackdb_playlist_list_tid(const char *who,
374 c = trackdb_opencursor(trackdb_playlistsdb, tid);
375 memset(k, 0, sizeof k);
376 while(!(e = c->c_get(c, k, prepare_data(d), DB_NEXT))) {
377 char *name = xstrndup(k->data, k->size), *owner;
378 const char *share = kvp_get(kvp_urldecode(d->data, d->size),
381 /* Extract owner; malformed names are skipped */
382 if(playlist_parse_name(name, &owner, 0)) {
383 error(0, "invalid playlist name '%s' found in database", name);
386 /* Always list public and shared playlists
387 * Only list private ones to their owner
388 * Don't list anything else
390 if(!strcmp(share, "public")
391 || !strcmp(share, "shared")
392 || (!strcmp(share, "private")
393 && owner && !strcmp(owner, who)))
394 vector_append(v, name);
399 case DB_LOCK_DEADLOCK:
402 fatal(0, "c->c_get: %s", db_strerror(e));
406 *playlistsp = v->vec;
408 *nplaylistsp = v->nvec;
412 /** @brief Delete a playlist
413 * @param name Playlist name
414 * @param who Who is deleting it
415 * @return 0 on success, non-0 on error
417 * Possible return values:
419 * - @c EINVAL if the playlist name is invalid
420 * - @c EACCES if the playlist cannot be modified by @p who
421 * - @c DB_NOTFOUND if the playlist doesn't exist
423 int trackdb_playlist_delete(const char *name,
428 if(!playlist_parse_name(name, &owner, 0)) {
429 error(0, "invalid playlist name '%s'", name);
432 /* We've checked as much as we can for now, now go and attempt the change */
433 WITH_TRANSACTION(trackdb_playlist_delete_tid(name, who, tid));
437 static int trackdb_playlist_delete_tid(const char *name,
444 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
446 /* Check that modification is allowed */
447 if(!(s = kvp_get(k, "sharing"))) {
448 error(0, "playlist '%s' has no 'sharing' key", name);
451 if(!playlist_may_write(name, who, s))
453 /* Delete the playlist */
454 return trackdb_delkey(trackdb_playlistsdb, name, tid);