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 ENOENT 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
,
168 /* Don't expose libdb error codes too much */
174 static int trackdb_playlist_get_tid(const char *name
,
184 if((e
= trackdb_getdata(trackdb_playlistsdb
, name
, &k
, tid
)))
186 /* Get sharability */
187 if(!(s
= kvp_get(k
, "sharing"))) {
188 error(0, "playlist '%s' has no 'sharing' key", name
);
191 /* Check the read is allowed */
192 if(!playlist_may_read(name
, who
, s
))
194 /* Return sharability */
196 *sharep
= xstrdup(s
);
197 /* Get track count */
198 if(!(s
= kvp_get(k
, "count"))) {
199 error(0, "playlist '%s' has no 'count' key", name
);
204 error(0, "playlist '%s' has negative count", name
);
207 /* Return track count */
212 char **tracks
= xcalloc(ntracks
+ 1, sizeof (char *));
215 for(int n
= 0; n
< ntracks
; ++n
) {
216 snprintf(b
, sizeof b
, "%d", n
);
217 if(!(s
= kvp_get(k
, b
))) {
218 error(0, "playlist '%s' lacks track %d", name
, n
);
221 tracks
[n
] = xstrdup(s
);
224 /* Return track list */
230 /** @brief Modify or create a playlist
231 * @param name Playlist name
232 * @param tracks List of tracks to set, or NULL to leave alone
233 * @param ntracks Length of @p tracks
234 * @param share Sharing status, or NULL to leave alone
235 * @return 0 on success, non-0 on error
237 * If the playlist exists it is just modified.
239 * If the playlist does not exist it is created. The default set of tracks is
240 * none, and the default sharing is private (if it is an owned one) or shared
243 * If neither @c tracks nor @c share are set then we only do an access check.
244 * The database is never modified (even to create the playlist) in this
247 * Possible return values:
249 * - @c EINVAL if the playlist name is invalid
250 * - @c EACCES if the playlist cannot be modified by @p who
252 int trackdb_playlist_set(const char *name
,
260 if(playlist_parse_name(name
, &owner
, 0)) {
261 error(0, "invalid playlist name '%s'", name
);
264 /* Check valid share types */
267 /* Playlists with an owner must be public or private */
268 if(strcmp(share
, "public")
269 && strcmp(share
, "private")) {
270 error(0, "playlist '%s' must be public or private", name
);
274 /* Playlists with no owner must be shared */
275 if(strcmp(share
, "shared")) {
276 error(0, "playlist '%s' must be shared", name
);
281 /* We've checked as much as we can for now, now go and attempt the change */
282 WITH_TRANSACTION(trackdb_playlist_set_tid(name
, who
, tracks
, ntracks
, share
,
287 static int trackdb_playlist_set_tid(const char *name
,
297 if((e
= trackdb_getdata(trackdb_playlistsdb
, name
, &k
, tid
))
300 /* If the playlist doesn't exist set some defaults */
301 if(e
== DB_NOTFOUND
) {
302 char *defshare
, *owner
;
304 if(playlist_parse_name(name
, &owner
, &defshare
))
306 /* Can't create a non-shared playlist belonging to someone else. In fact
307 * this should be picked up by playlist_may_write() below but it's clearer
309 if(owner
&& strcmp(owner
, who
))
312 kvp_set(&k
, "count", 0);
313 kvp_set(&k
, "sharing", defshare
);
315 /* Check that the modification is allowed */
316 if(!(s
= kvp_get(k
, "sharing"))) {
317 error(0, "playlist '%s' has no 'sharing' key", name
);
320 if(!playlist_may_write(name
, who
, s
))
322 /* If no change was requested then don't even create */
323 if(!share
&& !tracks
)
325 /* Set the new values */
327 kvp_set(&k
, "sharing", share
);
332 /* Sanity check track count */
333 if(ntracks
< 0 || ntracks
> config
->playlist_max
) {
334 error(0, "invalid track count %d", ntracks
);
338 for(n
= 0; n
< ntracks
; ++n
) {
339 snprintf(b
, sizeof b
, "%d", n
);
340 kvp_set(&k
, b
, tracks
[n
]);
342 /* Get the old track count */
343 if((s
= kvp_get(k
, "count")))
347 /* Delete old slots */
348 for(; n
< oldcount
; ++n
) {
349 snprintf(b
, sizeof b
, "%d", n
);
350 kvp_set(&k
, b
, NULL
);
352 /* Set the new count */
353 snprintf(b
, sizeof b
, "%d", ntracks
);
354 kvp_set(&k
, "count", b
);
356 /* Store the resulting record */
357 return trackdb_putdata(trackdb_playlistsdb
, name
, k
, tid
, 0);
360 /** @brief Get a list of playlists
361 * @param who Who wants to know
362 * @param playlistsp Where to put list of playlists
363 * @param nplaylistsp Where to put count of playlists, or NULL
365 void trackdb_playlist_list(const char *who
,
370 WITH_TRANSACTION(trackdb_playlist_list_tid(who
, playlistsp
, nplaylistsp
,
374 static int trackdb_playlist_list_tid(const char *who
,
384 c
= trackdb_opencursor(trackdb_playlistsdb
, tid
);
385 memset(k
, 0, sizeof k
);
386 while(!(e
= c
->c_get(c
, k
, prepare_data(d
), DB_NEXT
))) {
387 char *name
= xstrndup(k
->data
, k
->size
), *owner
;
388 const char *share
= kvp_get(kvp_urldecode(d
->data
, d
->size
),
391 /* Extract owner; malformed names are skipped */
392 if(playlist_parse_name(name
, &owner
, 0)) {
393 error(0, "invalid playlist name '%s' found in database", name
);
397 error(0, "playlist '%s' has no 'sharing' key", name
);
400 /* Always list public and shared playlists
401 * Only list private ones to their owner
402 * Don't list anything else
404 if(!strcmp(share
, "public")
405 || !strcmp(share
, "shared")
406 || (!strcmp(share
, "private")
407 && owner
&& !strcmp(owner
, who
)))
408 vector_append(v
, name
);
410 trackdb_closecursor(c
);
414 case DB_LOCK_DEADLOCK
:
417 fatal(0, "c->c_get: %s", db_strerror(e
));
421 *playlistsp
= v
->vec
;
423 *nplaylistsp
= v
->nvec
;
427 /** @brief Delete a playlist
428 * @param name Playlist name
429 * @param who Who is deleting it
430 * @return 0 on success, non-0 on error
432 * Possible return values:
434 * - @c EINVAL if the playlist name is invalid
435 * - @c EACCES if the playlist cannot be modified by @p who
436 * - @c ENOENT if the playlist doesn't exist
438 int trackdb_playlist_delete(const char *name
,
443 if(playlist_parse_name(name
, &owner
, 0)) {
444 error(0, "invalid playlist name '%s'", name
);
447 /* We've checked as much as we can for now, now go and attempt the change */
448 WITH_TRANSACTION(trackdb_playlist_delete_tid(name
, who
, tid
));
454 static int trackdb_playlist_delete_tid(const char *name
,
461 if((e
= trackdb_getdata(trackdb_playlistsdb
, name
, &k
, tid
)))
463 /* Check that modification is allowed */
464 if(!(s
= kvp_get(k
, "sharing"))) {
465 error(0, "playlist '%s' has no 'sharing' key", name
);
468 if(!playlist_may_write(name
, who
, s
))
470 /* Delete the playlist */
471 return trackdb_delkey(trackdb_playlistsdb
, name
, tid
);