chiark / gitweb /
Trivial resampler fixes
[disorder] / lib / trackdb-playlists.c
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 #include "eventlog.h"
36
37 static int trackdb_playlist_get_tid(const char *name,
38                                     const char *who,
39                                     char ***tracksp,
40                                     int *ntracksp,
41                                     char **sharep,
42                                     DB_TXN *tid);
43 static int trackdb_playlist_set_tid(const char *name,
44                                     const char *who,
45                                     char **tracks,
46                                     int ntracks,
47                                     const char *share,
48                                     DB_TXN *tid);
49 static int trackdb_playlist_list_tid(const char *who,
50                                      char ***playlistsp,
51                                      int *nplaylistsp,
52                                      DB_TXN *tid);
53 static int trackdb_playlist_delete_tid(const char *name,
54                                        const char *who,
55                                        DB_TXN *tid);
56
57 /** @brief Parse a playlist name
58  * @param name Playlist name
59  * @param ownerp Where to put owner, or NULL
60  * @param sharep Where to put default sharing, or NULL
61  * @return 0 on success, -1 on error
62  *
63  * Playlists take the form USER.PLAYLIST or just PLAYLIST.  The PLAYLIST part
64  * is alphanumeric and nonempty.  USER is a username (see valid_username()).
65  */
66 int playlist_parse_name(const char *name,
67                         char **ownerp,
68                         char **sharep) {
69   const char *dot = strchr(name, '.'), *share;
70   char *owner;
71
72   if(dot) {
73     /* Owned playlist */
74     owner = xstrndup(name, dot - name);
75     if(!valid_username(owner))
76       return -1;
77     if(!valid_username(dot + 1))
78       return -1;
79     share = "private";
80   } else {
81     /* Shared playlist */
82     if(!valid_username(name))
83       return -1;
84     owner = 0;
85     share = "shared";
86   }
87   if(ownerp)
88     *ownerp = owner;
89   if(sharep)
90     *sharep = xstrdup(share);
91   return 0;
92 }
93
94 /** @brief Check read access rights
95  * @param name Playlist name
96  * @param who Who wants to read
97  * @param share Playlist share status
98  */
99 static int playlist_may_read(const char *name,
100                              const char *who,
101                              const char *share) {
102   char *owner;
103   
104   if(playlist_parse_name(name, &owner, 0))
105     return 0;
106   /* Anyone can read shared playlists */
107   if(!owner)
108     return 1;
109   /* You can always read playlists you own */
110   if(!strcmp(owner, who))
111     return 1;
112   /* You can read public playlists */
113   if(!strcmp(share, "public"))
114     return 1;
115   /* Anything else is prohibited */
116   return 0;
117 }
118
119 /** @brief Check modify access rights
120  * @param name Playlist name
121  * @param who Who wants to modify
122  * @param share Playlist share status
123  */
124 static int playlist_may_write(const char *name,
125                               const char *who,
126                               const char attribute((unused)) *share) {
127   char *owner;
128   
129   if(playlist_parse_name(name, &owner, 0))
130     return 0;
131   /* Anyone can modify shared playlists */
132   if(!owner)
133     return 1;
134   /* You can always modify playlists you own */
135   if(!strcmp(owner, who))
136     return 1;
137   /* Anything else is prohibited */
138   return 0;
139 }
140
141 /** @brief Get playlist data
142  * @param name Name of playlist
143  * @param who Who wants to know
144  * @param tracksp Where to put list of tracks, or NULL
145  * @param ntracksp Where to put count of tracks, or NULL
146  * @param sharep Where to put sharing type, or NULL
147  * @return 0 on success, non-0 on error
148  *
149  * Possible return values:
150  * - @c 0 on success
151  * - @c ENOENT if the playlist doesn't exist
152  * - @c EINVAL if the playlist name is invalid
153  * - @c EACCES if the playlist cannot be read by @p who
154  */
155 int trackdb_playlist_get(const char *name,
156                          const char *who,
157                          char ***tracksp,
158                          int *ntracksp,
159                          char **sharep) {
160   int e;
161
162   if(playlist_parse_name(name, 0, 0)) {
163     error(0, "invalid playlist name '%s'", name);
164     return EINVAL;
165   }
166   WITH_TRANSACTION(trackdb_playlist_get_tid(name, who,
167                                             tracksp, ntracksp, sharep,
168                                             tid));
169   /* Don't expose libdb error codes too much */
170   if(e == DB_NOTFOUND)
171     e = ENOENT;
172   return e;
173 }
174
175 static int trackdb_playlist_get_tid(const char *name,
176                                     const char *who,
177                                     char ***tracksp,
178                                     int *ntracksp,
179                                     char **sharep,
180                                     DB_TXN *tid) {
181   struct kvp *k;
182   int e, ntracks;
183   const char *s;
184
185   if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
186     return e;
187   /* Get sharability */
188   if(!(s = kvp_get(k, "sharing"))) {
189     error(0, "playlist '%s' has no 'sharing' key", name);
190     s = "private";
191   }
192   /* Check the read is allowed */
193   if(!playlist_may_read(name, who, s))
194     return EACCES;
195   /* Return sharability */
196   if(sharep)
197     *sharep = xstrdup(s);
198   /* Get track count */
199   if(!(s = kvp_get(k, "count"))) {
200     error(0, "playlist '%s' has no 'count' key", name);
201     s = "0";
202   }
203   ntracks = atoi(s);
204   if(ntracks < 0) {
205     error(0, "playlist '%s' has negative count", name);
206     ntracks = 0;
207   }
208   /* Return track count */
209   if(ntracksp)
210     *ntracksp = ntracks;
211   if(tracksp) {
212     /* Get track list */
213     char **tracks = xcalloc(ntracks + 1, sizeof (char *));
214     char b[16];
215
216     for(int n = 0; n < ntracks; ++n) {
217       snprintf(b, sizeof b, "%d", n);
218       if(!(s = kvp_get(k, b))) {
219         error(0, "playlist '%s' lacks track %d", name, n);
220         s = "unknown";
221       }
222       tracks[n] = xstrdup(s);
223     }
224     tracks[ntracks] = 0;
225     /* Return track list */
226     *tracksp = tracks;
227   }
228   return 0;
229 }
230
231 /** @brief Modify or create a playlist
232  * @param name Playlist name
233  * @param who User modifying playlist
234  * @param tracks List of tracks to set, or NULL to leave alone
235  * @param ntracks Length of @p tracks
236  * @param share Sharing status, or NULL to leave alone
237  * @return 0 on success, non-0 on error
238  *
239  * If the playlist exists it is just modified.
240  *
241  * If the playlist does not exist it is created.  The default set of tracks is
242  * none, and the default sharing is private (if it is an owned one) or shared
243  * (otherwise).
244  *
245  * If neither @c tracks nor @c share are set then we only do an access check.
246  * The database is never modified (even to create the playlist) in this
247  * situation.
248  *
249  * Possible return values:
250  * - @c 0 on success
251  * - @c EINVAL if the playlist name is invalid
252  * - @c EACCES if the playlist cannot be modified by @p who
253  */
254 int trackdb_playlist_set(const char *name,
255                          const char *who,
256                          char **tracks,
257                          int ntracks,
258                          const char *share) {
259   int e;
260   char *owner;
261   
262   if(playlist_parse_name(name, &owner, 0)) {
263     error(0, "invalid playlist name '%s'", name);
264     return EINVAL;
265   }
266   /* Check valid share types */
267   if(share) {
268     if(owner) {
269       /* Playlists with an owner must be public or private */
270       if(strcmp(share, "public")
271          && strcmp(share, "private")) {
272         error(0, "playlist '%s' must be public or private", name);
273         return EINVAL;
274       }
275     } else {
276       /* Playlists with no owner must be shared */
277       if(strcmp(share, "shared")) {
278         error(0, "playlist '%s' must be shared", name);
279         return EINVAL;
280       }
281     }        
282   }
283   /* We've checked as much as we can for now, now go and attempt the change */
284   WITH_TRANSACTION(trackdb_playlist_set_tid(name, who, tracks, ntracks, share,
285                                             tid));
286   return e;
287 }
288
289 static int trackdb_playlist_set_tid(const char *name,
290                                     const char *who,
291                                     char **tracks,
292                                     int ntracks,
293                                     const char *share,
294                                     DB_TXN *tid) {
295   struct kvp *k;
296   int e;
297   const char *s;
298   const char *event = "playlist_modified";
299
300   if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid))
301      && e != DB_NOTFOUND)
302     return e;
303   /* If the playlist doesn't exist set some defaults */
304   if(e == DB_NOTFOUND) {
305     char *defshare, *owner;
306
307     if(playlist_parse_name(name, &owner, &defshare))
308       return EINVAL;
309     /* Can't create a non-shared playlist belonging to someone else.  In fact
310      * this should be picked up by playlist_may_write() below but it's clearer
311      * to do it here. */
312     if(owner && strcmp(owner, who))
313       return EACCES;
314     k = 0;
315     kvp_set(&k, "count", 0);
316     kvp_set(&k, "sharing", defshare);
317     event = "playlist_created";
318   }
319   /* Check that the modification is allowed */
320   if(!(s = kvp_get(k, "sharing"))) {
321     error(0, "playlist '%s' has no 'sharing' key", name);
322     s = "private";
323   }
324   if(!playlist_may_write(name, who, s))
325     return EACCES;
326   /* If no change was requested then don't even create */
327   if(!share && !tracks)
328     return 0;
329   /* Set the new values */
330   if(share)
331     kvp_set(&k, "sharing", share);
332   if(tracks) {
333     char b[16];
334     int oldcount, n;
335
336     /* Sanity check track count */
337     if(ntracks < 0 || ntracks > config->playlist_max) {
338       error(0, "invalid track count %d", ntracks);
339       return EINVAL;
340     }
341     /* Set the tracks */
342     for(n = 0; n < ntracks; ++n) {
343       snprintf(b, sizeof b, "%d", n);
344       kvp_set(&k, b, tracks[n]);
345     }
346     /* Get the old track count */
347     if((s = kvp_get(k, "count")))
348       oldcount = atoi(s);
349     else
350       oldcount = 0;
351     /* Delete old slots */
352     for(; n < oldcount; ++n) {
353       snprintf(b, sizeof b, "%d", n);
354       kvp_set(&k, b, NULL);
355     }
356     /* Set the new count */
357     snprintf(b, sizeof b, "%d", ntracks);
358     kvp_set(&k, "count", b);
359   }
360   /* Store the resulting record */
361   e = trackdb_putdata(trackdb_playlistsdb, name, k, tid, 0);
362   /* Log the event */
363   if(!e)
364     eventlog(event, name, kvp_get(k, "sharing"), (char *)0);
365   return e;
366 }
367
368 /** @brief Get a list of playlists
369  * @param who Who wants to know
370  * @param playlistsp Where to put list of playlists
371  * @param nplaylistsp Where to put count of playlists, or NULL
372  */
373 void trackdb_playlist_list(const char *who,
374                            char ***playlistsp,
375                            int *nplaylistsp) {
376   int e;
377
378   WITH_TRANSACTION(trackdb_playlist_list_tid(who, playlistsp, nplaylistsp,
379                                              tid));
380 }
381
382 static int trackdb_playlist_list_tid(const char *who,
383                                      char ***playlistsp,
384                                      int *nplaylistsp,
385                                      DB_TXN *tid) {
386   struct vector v[1];
387   DBC *c;
388   DBT k[1], d[1];
389   int e;
390
391   vector_init(v);
392   c = trackdb_opencursor(trackdb_playlistsdb, tid);
393   memset(k, 0, sizeof k);
394   while(!(e = c->c_get(c, k, prepare_data(d), DB_NEXT))) {
395     char *name = xstrndup(k->data, k->size), *owner;
396     const char *share = kvp_get(kvp_urldecode(d->data, d->size),
397                                 "sharing");
398
399     /* Extract owner; malformed names are skipped */
400     if(playlist_parse_name(name, &owner, 0)) {
401       error(0, "invalid playlist name '%s' found in database", name);
402       continue;
403     }
404     if(!share) {
405       error(0, "playlist '%s' has no 'sharing' key", name);
406       continue;
407     }
408     /* Always list public and shared playlists
409      * Only list private ones to their owner
410      * Don't list anything else
411      */
412     if(!strcmp(share, "public")
413        || !strcmp(share, "shared")
414        || (!strcmp(share, "private")
415            && owner && !strcmp(owner, who)))
416       vector_append(v, name);
417   }
418   trackdb_closecursor(c);
419   switch(e) {
420   case DB_NOTFOUND:
421     break;
422   case DB_LOCK_DEADLOCK:
423     return e;
424   default:
425     fatal(0, "c->c_get: %s", db_strerror(e));
426   }
427   vector_terminate(v);
428   if(playlistsp)
429     *playlistsp = v->vec;
430   if(nplaylistsp)
431     *nplaylistsp = v->nvec;
432   return 0;
433 }
434
435 /** @brief Delete a playlist
436  * @param name Playlist name
437  * @param who Who is deleting it
438  * @return 0 on success, non-0 on error
439  *
440  * Possible return values:
441  * - @c 0 on success
442  * - @c EINVAL if the playlist name is invalid
443  * - @c EACCES if the playlist cannot be modified by @p who
444  * - @c ENOENT if the playlist doesn't exist
445  */
446 int trackdb_playlist_delete(const char *name,
447                             const char *who) {
448   int e;
449   char *owner;
450   
451   if(playlist_parse_name(name, &owner, 0)) {
452     error(0, "invalid playlist name '%s'", name);
453     return EINVAL;
454   }
455   /* We've checked as much as we can for now, now go and attempt the change */
456   WITH_TRANSACTION(trackdb_playlist_delete_tid(name, who, tid));
457   if(e == DB_NOTFOUND)
458     e = ENOENT;
459   return e;
460 }
461
462 static int trackdb_playlist_delete_tid(const char *name,
463                                        const char *who,
464                                        DB_TXN *tid) {
465   struct kvp *k;
466   int e;
467   const char *s;
468
469   if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
470     return e;
471   /* Check that modification is allowed */
472   if(!(s = kvp_get(k, "sharing"))) {
473     error(0, "playlist '%s' has no 'sharing' key", name);
474     s = "private";
475   }
476   if(!playlist_may_write(name, who, s))
477     return EACCES;
478   /* Delete the playlist */
479   e = trackdb_delkey(trackdb_playlistsdb, name, tid);
480   if(!e)
481     eventlog("playlist_deleted", name, 0);
482   return e;
483 }
484
485 /*
486 Local Variables:
487 c-basic-offset:2
488 comment-column:40
489 fill-column:79
490 indent-tabs-mode:nil
491 End:
492 */