chiark / gitweb /
examples/disorder.init.in: Read settings from `/etc/default/disorder'.
[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"
5c102112 35#include "eventlog.h"
7f7c3819 36#include "validity.h"
2563dc1f
RK
37
38static int trackdb_playlist_get_tid(const char *name,
39 const char *who,
40 char ***tracksp,
41 int *ntracksp,
42 char **sharep,
43 DB_TXN *tid);
44static int trackdb_playlist_set_tid(const char *name,
45 const char *who,
46 char **tracks,
47 int ntracks,
48 const char *share,
49 DB_TXN *tid);
50static int trackdb_playlist_list_tid(const char *who,
51 char ***playlistsp,
52 int *nplaylistsp,
53 DB_TXN *tid);
54static int trackdb_playlist_delete_tid(const char *name,
55 const char *who,
56 DB_TXN *tid);
57
2563dc1f
RK
58/** @brief Check read access rights
59 * @param name Playlist name
60 * @param who Who wants to read
61 * @param share Playlist share status
62 */
63static int playlist_may_read(const char *name,
64 const char *who,
65 const char *share) {
66 char *owner;
67
440af55d 68 if(playlist_parse_name(name, &owner, 0))
2563dc1f
RK
69 return 0;
70 /* Anyone can read shared playlists */
71 if(!owner)
72 return 1;
73 /* You can always read playlists you own */
74 if(!strcmp(owner, who))
75 return 1;
76 /* You can read public playlists */
77 if(!strcmp(share, "public"))
78 return 1;
79 /* Anything else is prohibited */
80 return 0;
81}
82
83/** @brief Check modify access rights
84 * @param name Playlist name
85 * @param who Who wants to modify
86 * @param share Playlist share status
87 */
88static int playlist_may_write(const char *name,
89 const char *who,
90 const char attribute((unused)) *share) {
91 char *owner;
92
440af55d 93 if(playlist_parse_name(name, &owner, 0))
2563dc1f
RK
94 return 0;
95 /* Anyone can modify shared playlists */
96 if(!owner)
97 return 1;
98 /* You can always modify playlists you own */
99 if(!strcmp(owner, who))
100 return 1;
101 /* Anything else is prohibited */
102 return 0;
103}
104
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
112 *
113 * Possible return values:
114 * - @c 0 on success
ddbf05c8 115 * - @c ENOENT if the playlist doesn't exist
2563dc1f
RK
116 * - @c EINVAL if the playlist name is invalid
117 * - @c EACCES if the playlist cannot be read by @p who
118 */
119int trackdb_playlist_get(const char *name,
120 const char *who,
121 char ***tracksp,
122 int *ntracksp,
123 char **sharep) {
124 int e;
125
440af55d 126 if(playlist_parse_name(name, 0, 0)) {
2e9ba080 127 disorder_error(0, "invalid playlist name '%s'", name);
2563dc1f
RK
128 return EINVAL;
129 }
130 WITH_TRANSACTION(trackdb_playlist_get_tid(name, who,
131 tracksp, ntracksp, sharep,
132 tid));
ddbf05c8
RK
133 /* Don't expose libdb error codes too much */
134 if(e == DB_NOTFOUND)
135 e = ENOENT;
2563dc1f
RK
136 return e;
137}
138
139static int trackdb_playlist_get_tid(const char *name,
140 const char *who,
141 char ***tracksp,
142 int *ntracksp,
143 char **sharep,
144 DB_TXN *tid) {
145 struct kvp *k;
146 int e, ntracks;
147 const char *s;
148
149 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
150 return e;
151 /* Get sharability */
152 if(!(s = kvp_get(k, "sharing"))) {
2e9ba080 153 disorder_error(0, "playlist '%s' has no 'sharing' key", name);
2563dc1f
RK
154 s = "private";
155 }
156 /* Check the read is allowed */
157 if(!playlist_may_read(name, who, s))
158 return EACCES;
159 /* Return sharability */
160 if(sharep)
161 *sharep = xstrdup(s);
162 /* Get track count */
163 if(!(s = kvp_get(k, "count"))) {
2e9ba080 164 disorder_error(0, "playlist '%s' has no 'count' key", name);
2563dc1f
RK
165 s = "0";
166 }
167 ntracks = atoi(s);
168 if(ntracks < 0) {
2e9ba080 169 disorder_error(0, "playlist '%s' has negative count", name);
2563dc1f
RK
170 ntracks = 0;
171 }
172 /* Return track count */
173 if(ntracksp)
174 *ntracksp = ntracks;
175 if(tracksp) {
176 /* Get track list */
177 char **tracks = xcalloc(ntracks + 1, sizeof (char *));
178 char b[16];
179
180 for(int n = 0; n < ntracks; ++n) {
181 snprintf(b, sizeof b, "%d", n);
182 if(!(s = kvp_get(k, b))) {
2e9ba080 183 disorder_error(0, "playlist '%s' lacks track %d", name, n);
2563dc1f
RK
184 s = "unknown";
185 }
186 tracks[n] = xstrdup(s);
187 }
188 tracks[ntracks] = 0;
189 /* Return track list */
190 *tracksp = tracks;
191 }
192 return 0;
193}
194
195/** @brief Modify or create a playlist
196 * @param name Playlist name
c0f52b2c 197 * @param who User modifying playlist
2563dc1f
RK
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
202 *
203 * If the playlist exists it is just modified.
204 *
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
207 * (otherwise).
208 *
ddbf05c8
RK
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
211 * situation.
212 *
2563dc1f
RK
213 * Possible return values:
214 * - @c 0 on success
215 * - @c EINVAL if the playlist name is invalid
216 * - @c EACCES if the playlist cannot be modified by @p who
217 */
218int trackdb_playlist_set(const char *name,
219 const char *who,
220 char **tracks,
221 int ntracks,
222 const char *share) {
223 int e;
224 char *owner;
225
440af55d 226 if(playlist_parse_name(name, &owner, 0)) {
2e9ba080 227 disorder_error(0, "invalid playlist name '%s'", name);
2563dc1f
RK
228 return EINVAL;
229 }
230 /* Check valid share types */
231 if(share) {
232 if(owner) {
233 /* Playlists with an owner must be public or private */
234 if(strcmp(share, "public")
235 && strcmp(share, "private")) {
2e9ba080 236 disorder_error(0, "playlist '%s' must be public or private", name);
2563dc1f
RK
237 return EINVAL;
238 }
239 } else {
240 /* Playlists with no owner must be shared */
241 if(strcmp(share, "shared")) {
2e9ba080 242 disorder_error(0, "playlist '%s' must be shared", name);
2563dc1f
RK
243 return EINVAL;
244 }
245 }
246 }
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,
249 tid));
250 return e;
251}
252
253static int trackdb_playlist_set_tid(const char *name,
254 const char *who,
255 char **tracks,
256 int ntracks,
257 const char *share,
258 DB_TXN *tid) {
259 struct kvp *k;
260 int e;
261 const char *s;
5c102112 262 const char *event = "playlist_modified";
2563dc1f
RK
263
264 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid))
265 && e != DB_NOTFOUND)
266 return e;
267 /* If the playlist doesn't exist set some defaults */
268 if(e == DB_NOTFOUND) {
269 char *defshare, *owner;
270
271 if(playlist_parse_name(name, &owner, &defshare))
272 return EINVAL;
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
275 * to do it here. */
276 if(owner && strcmp(owner, who))
277 return EACCES;
278 k = 0;
279 kvp_set(&k, "count", 0);
280 kvp_set(&k, "sharing", defshare);
5c102112 281 event = "playlist_created";
2563dc1f
RK
282 }
283 /* Check that the modification is allowed */
284 if(!(s = kvp_get(k, "sharing"))) {
2e9ba080 285 disorder_error(0, "playlist '%s' has no 'sharing' key", name);
2563dc1f
RK
286 s = "private";
287 }
288 if(!playlist_may_write(name, who, s))
289 return EACCES;
ddbf05c8
RK
290 /* If no change was requested then don't even create */
291 if(!share && !tracks)
292 return 0;
2563dc1f
RK
293 /* Set the new values */
294 if(share)
440af55d 295 kvp_set(&k, "sharing", share);
2563dc1f
RK
296 if(tracks) {
297 char b[16];
298 int oldcount, n;
299
300 /* Sanity check track count */
301 if(ntracks < 0 || ntracks > config->playlist_max) {
2e9ba080 302 disorder_error(0, "invalid track count %d", ntracks);
2563dc1f
RK
303 return EINVAL;
304 }
305 /* Set the tracks */
306 for(n = 0; n < ntracks; ++n) {
307 snprintf(b, sizeof b, "%d", n);
308 kvp_set(&k, b, tracks[n]);
309 }
310 /* Get the old track count */
311 if((s = kvp_get(k, "count")))
312 oldcount = atoi(s);
313 else
314 oldcount = 0;
315 /* Delete old slots */
316 for(; n < oldcount; ++n) {
317 snprintf(b, sizeof b, "%d", n);
318 kvp_set(&k, b, NULL);
319 }
320 /* Set the new count */
321 snprintf(b, sizeof b, "%d", ntracks);
322 kvp_set(&k, "count", b);
323 }
324 /* Store the resulting record */
5c102112
RK
325 e = trackdb_putdata(trackdb_playlistsdb, name, k, tid, 0);
326 /* Log the event */
327 if(!e)
328 eventlog(event, name, kvp_get(k, "sharing"), (char *)0);
329 return e;
2563dc1f
RK
330}
331
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
336 */
337void trackdb_playlist_list(const char *who,
338 char ***playlistsp,
339 int *nplaylistsp) {
340 int e;
341
342 WITH_TRANSACTION(trackdb_playlist_list_tid(who, playlistsp, nplaylistsp,
343 tid));
344}
345
346static int trackdb_playlist_list_tid(const char *who,
347 char ***playlistsp,
348 int *nplaylistsp,
349 DB_TXN *tid) {
350 struct vector v[1];
351 DBC *c;
352 DBT k[1], d[1];
353 int e;
354
355 vector_init(v);
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),
440af55d 361 "sharing");
2563dc1f
RK
362
363 /* Extract owner; malformed names are skipped */
364 if(playlist_parse_name(name, &owner, 0)) {
2e9ba080 365 disorder_error(0, "invalid playlist name '%s' found in database", name);
2563dc1f
RK
366 continue;
367 }
440af55d 368 if(!share) {
2e9ba080 369 disorder_error(0, "playlist '%s' has no 'sharing' key", name);
440af55d
RK
370 continue;
371 }
2563dc1f
RK
372 /* Always list public and shared playlists
373 * Only list private ones to their owner
374 * Don't list anything else
375 */
376 if(!strcmp(share, "public")
377 || !strcmp(share, "shared")
378 || (!strcmp(share, "private")
379 && owner && !strcmp(owner, who)))
380 vector_append(v, name);
381 }
440af55d 382 trackdb_closecursor(c);
2563dc1f
RK
383 switch(e) {
384 case DB_NOTFOUND:
385 break;
386 case DB_LOCK_DEADLOCK:
387 return e;
388 default:
2e9ba080 389 disorder_fatal(0, "c->c_get: %s", db_strerror(e));
2563dc1f
RK
390 }
391 vector_terminate(v);
392 if(playlistsp)
393 *playlistsp = v->vec;
394 if(nplaylistsp)
395 *nplaylistsp = v->nvec;
396 return 0;
397}
398
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
403 *
404 * Possible return values:
405 * - @c 0 on success
406 * - @c EINVAL if the playlist name is invalid
407 * - @c EACCES if the playlist cannot be modified by @p who
ddbf05c8 408 * - @c ENOENT if the playlist doesn't exist
2563dc1f
RK
409 */
410int trackdb_playlist_delete(const char *name,
411 const char *who) {
412 int e;
413 char *owner;
414
440af55d 415 if(playlist_parse_name(name, &owner, 0)) {
2e9ba080 416 disorder_error(0, "invalid playlist name '%s'", name);
2563dc1f
RK
417 return EINVAL;
418 }
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));
ddbf05c8
RK
421 if(e == DB_NOTFOUND)
422 e = ENOENT;
2563dc1f
RK
423 return e;
424}
425
426static int trackdb_playlist_delete_tid(const char *name,
427 const char *who,
428 DB_TXN *tid) {
429 struct kvp *k;
430 int e;
431 const char *s;
432
433 if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
434 return e;
435 /* Check that modification is allowed */
436 if(!(s = kvp_get(k, "sharing"))) {
2e9ba080 437 disorder_error(0, "playlist '%s' has no 'sharing' key", name);
2563dc1f
RK
438 s = "private";
439 }
440 if(!playlist_may_write(name, who, s))
441 return EACCES;
442 /* Delete the playlist */
5c102112
RK
443 e = trackdb_delkey(trackdb_playlistsdb, name, tid);
444 if(!e)
445 eventlog("playlist_deleted", name, 0);
446 return e;
2563dc1f
RK
447}
448
449/*
450Local Variables:
451c-basic-offset:2
452comment-column:40
453fill-column:79
454indent-tabs-mode:nil
455End:
456*/