chiark / gitweb /
Merge from trunk
[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 tracks List of tracks to set, or NULL to leave alone
234  * @param ntracks Length of @p tracks
235  * @param share Sharing status, or NULL to leave alone
236  * @return 0 on success, non-0 on error
237  *
238  * If the playlist exists it is just modified.
239  *
240  * If the playlist does not exist it is created.  The default set of tracks is
241  * none, and the default sharing is private (if it is an owned one) or shared
242  * (otherwise).
243  *
244  * If neither @c tracks nor @c share are set then we only do an access check.
245  * The database is never modified (even to create the playlist) in this
246  * situation.
247  *
248  * Possible return values:
249  * - @c 0 on success
250  * - @c EINVAL if the playlist name is invalid
251  * - @c EACCES if the playlist cannot be modified by @p who
252  */
253 int trackdb_playlist_set(const char *name,
254                          const char *who,
255                          char **tracks,
256                          int ntracks,
257                          const char *share) {
258   int e;
259   char *owner;
260   
261   if(playlist_parse_name(name, &owner, 0)) {
262     error(0, "invalid playlist name '%s'", name);
263     return EINVAL;
264   }
265   /* Check valid share types */
266   if(share) {
267     if(owner) {
268       /* Playlists with an owner must be public or private */
269       if(strcmp(share, "public")
270          && strcmp(share, "private")) {
271         error(0, "playlist '%s' must be public or private", name);
272         return EINVAL;
273       }
274     } else {
275       /* Playlists with no owner must be shared */
276       if(strcmp(share, "shared")) {
277         error(0, "playlist '%s' must be shared", name);
278         return EINVAL;
279       }
280     }        
281   }
282   /* We've checked as much as we can for now, now go and attempt the change */
283   WITH_TRANSACTION(trackdb_playlist_set_tid(name, who, tracks, ntracks, share,
284                                             tid));
285   return e;
286 }
287
288 static int trackdb_playlist_set_tid(const char *name,
289                                     const char *who,
290                                     char **tracks,
291                                     int ntracks,
292                                     const char *share,
293                                     DB_TXN *tid) {
294   struct kvp *k;
295   int e;
296   const char *s;
297   const char *event = "playlist_modified";
298
299   if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid))
300      && e != DB_NOTFOUND)
301     return e;
302   /* If the playlist doesn't exist set some defaults */
303   if(e == DB_NOTFOUND) {
304     char *defshare, *owner;
305
306     if(playlist_parse_name(name, &owner, &defshare))
307       return EINVAL;
308     /* Can't create a non-shared playlist belonging to someone else.  In fact
309      * this should be picked up by playlist_may_write() below but it's clearer
310      * to do it here. */
311     if(owner && strcmp(owner, who))
312       return EACCES;
313     k = 0;
314     kvp_set(&k, "count", 0);
315     kvp_set(&k, "sharing", defshare);
316     event = "playlist_created";
317   }
318   /* Check that the modification is allowed */
319   if(!(s = kvp_get(k, "sharing"))) {
320     error(0, "playlist '%s' has no 'sharing' key", name);
321     s = "private";
322   }
323   if(!playlist_may_write(name, who, s))
324     return EACCES;
325   /* If no change was requested then don't even create */
326   if(!share && !tracks)
327     return 0;
328   /* Set the new values */
329   if(share)
330     kvp_set(&k, "sharing", share);
331   if(tracks) {
332     char b[16];
333     int oldcount, n;
334
335     /* Sanity check track count */
336     if(ntracks < 0 || ntracks > config->playlist_max) {
337       error(0, "invalid track count %d", ntracks);
338       return EINVAL;
339     }
340     /* Set the tracks */
341     for(n = 0; n < ntracks; ++n) {
342       snprintf(b, sizeof b, "%d", n);
343       kvp_set(&k, b, tracks[n]);
344     }
345     /* Get the old track count */
346     if((s = kvp_get(k, "count")))
347       oldcount = atoi(s);
348     else
349       oldcount = 0;
350     /* Delete old slots */
351     for(; n < oldcount; ++n) {
352       snprintf(b, sizeof b, "%d", n);
353       kvp_set(&k, b, NULL);
354     }
355     /* Set the new count */
356     snprintf(b, sizeof b, "%d", ntracks);
357     kvp_set(&k, "count", b);
358   }
359   /* Store the resulting record */
360   e = trackdb_putdata(trackdb_playlistsdb, name, k, tid, 0);
361   /* Log the event */
362   if(!e)
363     eventlog(event, name, kvp_get(k, "sharing"), (char *)0);
364   return e;
365 }
366
367 /** @brief Get a list of playlists
368  * @param who Who wants to know
369  * @param playlistsp Where to put list of playlists
370  * @param nplaylistsp Where to put count of playlists, or NULL
371  */
372 void trackdb_playlist_list(const char *who,
373                            char ***playlistsp,
374                            int *nplaylistsp) {
375   int e;
376
377   WITH_TRANSACTION(trackdb_playlist_list_tid(who, playlistsp, nplaylistsp,
378                                              tid));
379 }
380
381 static int trackdb_playlist_list_tid(const char *who,
382                                      char ***playlistsp,
383                                      int *nplaylistsp,
384                                      DB_TXN *tid) {
385   struct vector v[1];
386   DBC *c;
387   DBT k[1], d[1];
388   int e;
389
390   vector_init(v);
391   c = trackdb_opencursor(trackdb_playlistsdb, tid);
392   memset(k, 0, sizeof k);
393   while(!(e = c->c_get(c, k, prepare_data(d), DB_NEXT))) {
394     char *name = xstrndup(k->data, k->size), *owner;
395     const char *share = kvp_get(kvp_urldecode(d->data, d->size),
396                                 "sharing");
397
398     /* Extract owner; malformed names are skipped */
399     if(playlist_parse_name(name, &owner, 0)) {
400       error(0, "invalid playlist name '%s' found in database", name);
401       continue;
402     }
403     if(!share) {
404       error(0, "playlist '%s' has no 'sharing' key", name);
405       continue;
406     }
407     /* Always list public and shared playlists
408      * Only list private ones to their owner
409      * Don't list anything else
410      */
411     if(!strcmp(share, "public")
412        || !strcmp(share, "shared")
413        || (!strcmp(share, "private")
414            && owner && !strcmp(owner, who)))
415       vector_append(v, name);
416   }
417   trackdb_closecursor(c);
418   switch(e) {
419   case DB_NOTFOUND:
420     break;
421   case DB_LOCK_DEADLOCK:
422     return e;
423   default:
424     fatal(0, "c->c_get: %s", db_strerror(e));
425   }
426   vector_terminate(v);
427   if(playlistsp)
428     *playlistsp = v->vec;
429   if(nplaylistsp)
430     *nplaylistsp = v->nvec;
431   return 0;
432 }
433
434 /** @brief Delete a playlist
435  * @param name Playlist name
436  * @param who Who is deleting it
437  * @return 0 on success, non-0 on error
438  *
439  * Possible return values:
440  * - @c 0 on success
441  * - @c EINVAL if the playlist name is invalid
442  * - @c EACCES if the playlist cannot be modified by @p who
443  * - @c ENOENT if the playlist doesn't exist
444  */
445 int trackdb_playlist_delete(const char *name,
446                             const char *who) {
447   int e;
448   char *owner;
449   
450   if(playlist_parse_name(name, &owner, 0)) {
451     error(0, "invalid playlist name '%s'", name);
452     return EINVAL;
453   }
454   /* We've checked as much as we can for now, now go and attempt the change */
455   WITH_TRANSACTION(trackdb_playlist_delete_tid(name, who, tid));
456   if(e == DB_NOTFOUND)
457     e = ENOENT;
458   return e;
459 }
460
461 static int trackdb_playlist_delete_tid(const char *name,
462                                        const char *who,
463                                        DB_TXN *tid) {
464   struct kvp *k;
465   int e;
466   const char *s;
467
468   if((e = trackdb_getdata(trackdb_playlistsdb, name, &k, tid)))
469     return e;
470   /* Check that modification is allowed */
471   if(!(s = kvp_get(k, "sharing"))) {
472     error(0, "playlist '%s' has no 'sharing' key", name);
473     s = "private";
474   }
475   if(!playlist_may_write(name, who, s))
476     return EACCES;
477   /* Delete the playlist */
478   e = trackdb_delkey(trackdb_playlistsdb, name, tid);
479   if(!e)
480     eventlog("playlist_deleted", name, 0);
481   return e;
482 }
483
484 /*
485 Local Variables:
486 c-basic-offset:2
487 comment-column:40
488 fill-column:79
489 indent-tabs-mode:nil
490 End:
491 */