chiark / gitweb /
More playlist testing. Fix sharing status of un-owned playlists.
[disorder] / lib / trackdb-playlists.c
CommitLineData
2563dc1f
RK
1/*
2 * This file is part of DisOrder
3 * Copyright (C) 2008 Richard Kettlewell
4 *
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.
9 *
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.
14 *
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
18 * USA
19 */
20/** @file lib/trackdb-playlists.c
21 * @brief Track database playlist support
22 *
23 * This file implements reading and modification of playlists, including access
24 * control, but not locking or event logging (at least yet).
25 */
26#include "common.h"
27
28#include <errno.h>
29
30#include "trackdb-int.h"
31#include "mem.h"
32#include "log.h"
33#include "configuration.h"
34#include "vector.h"
35
36static int trackdb_playlist_get_tid(const char *name,
37 const char *who,
38 char ***tracksp,
39 int *ntracksp,
40 char **sharep,
41 DB_TXN *tid);
42static int trackdb_playlist_set_tid(const char *name,
43 const char *who,
44 char **tracks,
45 int ntracks,
46 const char *share,
47 DB_TXN *tid);
48static int trackdb_playlist_list_tid(const char *who,
49 char ***playlistsp,
50 int *nplaylistsp,
51 DB_TXN *tid);
52static int trackdb_playlist_delete_tid(const char *name,
53 const char *who,
54 DB_TXN *tid);
55
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
61 *
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()).
64 */
65int playlist_parse_name(const char *name,
66 char **ownerp,
67 char **sharep) {
68 const char *dot = strchr(name, '.'), *share;
69 char *owner;
70
71 if(dot) {
72 /* Owned playlist */
73 owner = xstrndup(name, dot - name);
74 if(!valid_username(owner))
75 return -1;
76 if(!valid_username(dot + 1))
77 return -1;
78 share = "private";
79 } else {
80 /* Shared playlist */
81 if(!valid_username(name))
82 return -1;
83 owner = 0;
97b379d2 84 share = "shared";
2563dc1f
RK
85 }
86 if(ownerp)
87 *ownerp = owner;
88 if(sharep)
89 *sharep = xstrdup(share);
90 return 0;
91}
92
93/** @brief Check read access rights
94 * @param name Playlist name
95 * @param who Who wants to read
96 * @param share Playlist share status
97 */
98static int playlist_may_read(const char *name,
99 const char *who,
100 const char *share) {
101 char *owner;
102
440af55d 103 if(playlist_parse_name(name, &owner, 0))
2563dc1f
RK
104 return 0;
105 /* Anyone can read shared playlists */
106 if(!owner)
107 return 1;
108 /* You can always read playlists you own */
109 if(!strcmp(owner, who))
110 return 1;
111 /* You can read public playlists */
112 if(!strcmp(share, "public"))
113 return 1;
114 /* Anything else is prohibited */
115 return 0;
116}
117
118/** @brief Check modify access rights
119 * @param name Playlist name
120 * @param who Who wants to modify
121 * @param share Playlist share status
122 */
123static int playlist_may_write(const char *name,
124 const char *who,
125 const char attribute((unused)) *share) {
126 char *owner;
127
440af55d 128 if(playlist_parse_name(name, &owner, 0))
2563dc1f
RK
129 return 0;
130 /* Anyone can modify shared playlists */
131 if(!owner)
132 return 1;
133 /* You can always modify playlists you own */
134 if(!strcmp(owner, who))
135 return 1;
136 /* Anything else is prohibited */
137 return 0;
138}
139
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
147 *
148 * Possible return values:
149 * - @c 0 on success
ddbf05c8 150 * - @c ENOENT if the playlist doesn't exist
2563dc1f
RK
151 * - @c EINVAL if the playlist name is invalid
152 * - @c EACCES if the playlist cannot be read by @p who
153 */
154int trackdb_playlist_get(const char *name,
155 const char *who,
156 char ***tracksp,
157 int *ntracksp,
158 char **sharep) {
159 int e;
160
440af55d 161 if(playlist_parse_name(name, 0, 0)) {
2563dc1f
RK
162 error(0, "invalid playlist name '%s'", name);
163 return EINVAL;
164 }
165 WITH_TRANSACTION(trackdb_playlist_get_tid(name, who,
166 tracksp, ntracksp, sharep,
167 tid));
ddbf05c8
RK
168 /* Don't expose libdb error codes too much */
169 if(e == DB_NOTFOUND)
170 e = ENOENT;
2563dc1f
RK
171 return e;
172}
173
174static int trackdb_playlist_get_tid(const char *name,
175 const char *who,
176 char ***tracksp,
177 int *ntracksp,
178 char **sharep,
179 DB_TXN *tid) {
180 struct kvp *k;
181 int e, ntracks;
182 const char *s;
183
184 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
185 return e;
186 /* Get sharability */
187 if(!(s = kvp_get(k, "sharing"))) {
188 error(0, "playlist '%s' has no 'sharing' key", name);
189 s = "private";
190 }
191 /* Check the read is allowed */
192 if(!playlist_may_read(name, who, s))
193 return EACCES;
194 /* Return sharability */
195 if(sharep)
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);
200 s = "0";
201 }
202 ntracks = atoi(s);
203 if(ntracks < 0) {
204 error(0, "playlist '%s' has negative count", name);
205 ntracks = 0;
206 }
207 /* Return track count */
208 if(ntracksp)
209 *ntracksp = ntracks;
210 if(tracksp) {
211 /* Get track list */
212 char **tracks = xcalloc(ntracks + 1, sizeof (char *));
213 char b[16];
214
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);
219 s = "unknown";
220 }
221 tracks[n] = xstrdup(s);
222 }
223 tracks[ntracks] = 0;
224 /* Return track list */
225 *tracksp = tracks;
226 }
227 return 0;
228}
229
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
236 *
237 * If the playlist exists it is just modified.
238 *
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
241 * (otherwise).
242 *
ddbf05c8
RK
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
245 * situation.
246 *
2563dc1f
RK
247 * Possible return values:
248 * - @c 0 on success
249 * - @c EINVAL if the playlist name is invalid
250 * - @c EACCES if the playlist cannot be modified by @p who
251 */
252int trackdb_playlist_set(const char *name,
253 const char *who,
254 char **tracks,
255 int ntracks,
256 const char *share) {
257 int e;
258 char *owner;
259
440af55d 260 if(playlist_parse_name(name, &owner, 0)) {
2563dc1f
RK
261 error(0, "invalid playlist name '%s'", name);
262 return EINVAL;
263 }
264 /* Check valid share types */
265 if(share) {
266 if(owner) {
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);
271 return EINVAL;
272 }
273 } else {
274 /* Playlists with no owner must be shared */
275 if(strcmp(share, "shared")) {
276 error(0, "playlist '%s' must be shared", name);
277 return EINVAL;
278 }
279 }
280 }
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,
283 tid));
284 return e;
285}
286
287static int trackdb_playlist_set_tid(const char *name,
288 const char *who,
289 char **tracks,
290 int ntracks,
291 const char *share,
292 DB_TXN *tid) {
293 struct kvp *k;
294 int e;
295 const char *s;
296
297 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid))
298 && e != DB_NOTFOUND)
299 return e;
300 /* If the playlist doesn't exist set some defaults */
301 if(e == DB_NOTFOUND) {
302 char *defshare, *owner;
303
304 if(playlist_parse_name(name, &owner, &defshare))
305 return EINVAL;
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
308 * to do it here. */
309 if(owner && strcmp(owner, who))
310 return EACCES;
311 k = 0;
312 kvp_set(&k, "count", 0);
313 kvp_set(&k, "sharing", defshare);
314 }
315 /* Check that the modification is allowed */
316 if(!(s = kvp_get(k, "sharing"))) {
317 error(0, "playlist '%s' has no 'sharing' key", name);
318 s = "private";
319 }
320 if(!playlist_may_write(name, who, s))
321 return EACCES;
ddbf05c8
RK
322 /* If no change was requested then don't even create */
323 if(!share && !tracks)
324 return 0;
2563dc1f
RK
325 /* Set the new values */
326 if(share)
440af55d 327 kvp_set(&k, "sharing", share);
2563dc1f
RK
328 if(tracks) {
329 char b[16];
330 int oldcount, n;
331
332 /* Sanity check track count */
333 if(ntracks < 0 || ntracks > config->playlist_max) {
334 error(0, "invalid track count %d", ntracks);
335 return EINVAL;
336 }
337 /* Set the tracks */
338 for(n = 0; n < ntracks; ++n) {
339 snprintf(b, sizeof b, "%d", n);
340 kvp_set(&k, b, tracks[n]);
341 }
342 /* Get the old track count */
343 if((s = kvp_get(k, "count")))
344 oldcount = atoi(s);
345 else
346 oldcount = 0;
347 /* Delete old slots */
348 for(; n < oldcount; ++n) {
349 snprintf(b, sizeof b, "%d", n);
350 kvp_set(&k, b, NULL);
351 }
352 /* Set the new count */
353 snprintf(b, sizeof b, "%d", ntracks);
354 kvp_set(&k, "count", b);
355 }
356 /* Store the resulting record */
357 return trackdb_putdata(trackdb_playlistsdb, name, k, tid, 0);
358}
359
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
364 */
365void trackdb_playlist_list(const char *who,
366 char ***playlistsp,
367 int *nplaylistsp) {
368 int e;
369
370 WITH_TRANSACTION(trackdb_playlist_list_tid(who, playlistsp, nplaylistsp,
371 tid));
372}
373
374static int trackdb_playlist_list_tid(const char *who,
375 char ***playlistsp,
376 int *nplaylistsp,
377 DB_TXN *tid) {
378 struct vector v[1];
379 DBC *c;
380 DBT k[1], d[1];
381 int e;
382
383 vector_init(v);
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),
440af55d 389 "sharing");
2563dc1f
RK
390
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);
394 continue;
395 }
440af55d
RK
396 if(!share) {
397 error(0, "playlist '%s' has no 'sharing' key", name);
398 continue;
399 }
2563dc1f
RK
400 /* Always list public and shared playlists
401 * Only list private ones to their owner
402 * Don't list anything else
403 */
404 if(!strcmp(share, "public")
405 || !strcmp(share, "shared")
406 || (!strcmp(share, "private")
407 && owner && !strcmp(owner, who)))
408 vector_append(v, name);
409 }
440af55d 410 trackdb_closecursor(c);
2563dc1f
RK
411 switch(e) {
412 case DB_NOTFOUND:
413 break;
414 case DB_LOCK_DEADLOCK:
415 return e;
416 default:
417 fatal(0, "c->c_get: %s", db_strerror(e));
418 }
419 vector_terminate(v);
420 if(playlistsp)
421 *playlistsp = v->vec;
422 if(nplaylistsp)
423 *nplaylistsp = v->nvec;
424 return 0;
425}
426
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
431 *
432 * Possible return values:
433 * - @c 0 on success
434 * - @c EINVAL if the playlist name is invalid
435 * - @c EACCES if the playlist cannot be modified by @p who
ddbf05c8 436 * - @c ENOENT if the playlist doesn't exist
2563dc1f
RK
437 */
438int trackdb_playlist_delete(const char *name,
439 const char *who) {
440 int e;
441 char *owner;
442
440af55d 443 if(playlist_parse_name(name, &owner, 0)) {
2563dc1f
RK
444 error(0, "invalid playlist name '%s'", name);
445 return EINVAL;
446 }
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));
ddbf05c8
RK
449 if(e == DB_NOTFOUND)
450 e = ENOENT;
2563dc1f
RK
451 return e;
452}
453
454static int trackdb_playlist_delete_tid(const char *name,
455 const char *who,
456 DB_TXN *tid) {
457 struct kvp *k;
458 int e;
459 const char *s;
460
461 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
462 return e;
463 /* Check that modification is allowed */
464 if(!(s = kvp_get(k, "sharing"))) {
465 error(0, "playlist '%s' has no 'sharing' key", name);
466 s = "private";
467 }
468 if(!playlist_may_write(name, who, s))
469 return EACCES;
470 /* Delete the playlist */
471 return trackdb_delkey(trackdb_playlistsdb, name, tid);
472}
473
474/*
475Local Variables:
476c-basic-offset:2
477comment-column:40
478fill-column:79
479indent-tabs-mode:nil
480End:
481*/