chiark / gitweb /
Synchronize 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 #include "validity.h"
37
38 static 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);
44 static 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);
50 static int trackdb_playlist_list_tid(const char *who,
51                                      char ***playlistsp,
52                                      int *nplaylistsp,
53                                      DB_TXN *tid);
54 static int trackdb_playlist_delete_tid(const char *name,
55                                        const char *who,
56                                        DB_TXN *tid);
57
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  */
63 static int playlist_may_read(const char *name,
64                              const char *who,
65                              const char *share) {
66   char *owner;
67   
68   if(playlist_parse_name(name, &owner, 0))
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  */
88 static int playlist_may_write(const char *name,
89                               const char *who,
90                               const char attribute((unused)) *share) {
91   char *owner;
92   
93   if(playlist_parse_name(name, &owner, 0))
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
115  * - @c ENOENT if the playlist doesn't exist
116  * - @c EINVAL if the playlist name is invalid
117  * - @c EACCES if the playlist cannot be read by @p who
118  */
119 int trackdb_playlist_get(const char *name,
120                          const char *who,
121                          char ***tracksp,
122                          int *ntracksp,
123                          char **sharep) {
124   int e;
125
126   if(playlist_parse_name(name, 0, 0)) {
127     disorder_error(0, "invalid playlist name '%s'", name);
128     return EINVAL;
129   }
130   WITH_TRANSACTION(trackdb_playlist_get_tid(name, who,
131                                             tracksp, ntracksp, sharep,
132                                             tid));
133   /* Don't expose libdb error codes too much */
134   if(e == DB_NOTFOUND)
135     e = ENOENT;
136   return e;
137 }
138
139 static 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"))) {
153     disorder_error(0, "playlist '%s' has no 'sharing' key", name);
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"))) {
164     disorder_error(0, "playlist '%s' has no 'count' key", name);
165     s = "0";
166   }
167   ntracks = atoi(s);
168   if(ntracks < 0) {
169     disorder_error(0, "playlist '%s' has negative count", name);
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))) {
183         disorder_error(0, "playlist '%s' lacks track %d", name, n);
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
197  * @param who User modifying playlist
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  *
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  *
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  */
218 int 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   
226   if(playlist_parse_name(name, &owner, 0)) {
227     disorder_error(0, "invalid playlist name '%s'", name);
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")) {
236         disorder_error(0, "playlist '%s' must be public or private", name);
237         return EINVAL;
238       }
239     } else {
240       /* Playlists with no owner must be shared */
241       if(strcmp(share, "shared")) {
242         disorder_error(0, "playlist '%s' must be shared", name);
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
253 static 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;
262   const char *event = "playlist_modified";
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);
281     event = "playlist_created";
282   }
283   /* Check that the modification is allowed */
284   if(!(s = kvp_get(k, "sharing"))) {
285     disorder_error(0, "playlist '%s' has no 'sharing' key", name);
286     s = "private";
287   }
288   if(!playlist_may_write(name, who, s))
289     return EACCES;
290   /* If no change was requested then don't even create */
291   if(!share && !tracks)
292     return 0;
293   /* Set the new values */
294   if(share)
295     kvp_set(&k, "sharing", share);
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) {
302       disorder_error(0, "invalid track count %d", ntracks);
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 */
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;
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  */
337 void 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
346 static 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),
361                                 "sharing");
362
363     /* Extract owner; malformed names are skipped */
364     if(playlist_parse_name(name, &owner, 0)) {
365       disorder_error(0, "invalid playlist name '%s' found in database", name);
366       continue;
367     }
368     if(!share) {
369       disorder_error(0, "playlist '%s' has no 'sharing' key", name);
370       continue;
371     }
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   }
382   trackdb_closecursor(c);
383   switch(e) {
384   case DB_NOTFOUND:
385     break;
386   case DB_LOCK_DEADLOCK:
387     return e;
388   default:
389     disorder_fatal(0, "c->c_get: %s", db_strerror(e));
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
408  * - @c ENOENT if the playlist doesn't exist
409  */
410 int trackdb_playlist_delete(const char *name,
411                             const char *who) {
412   int e;
413   char *owner;
414   
415   if(playlist_parse_name(name, &owner, 0)) {
416     disorder_error(0, "invalid playlist name '%s'", name);
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));
421   if(e == DB_NOTFOUND)
422     e = ENOENT;
423   return e;
424 }
425
426 static 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"))) {
437     disorder_error(0, "playlist '%s' has no 'sharing' key", name);
438     s = "private";
439   }
440   if(!playlist_may_write(name, who, s))
441     return EACCES;
442   /* Delete the playlist */
443   e = trackdb_delkey(trackdb_playlistsdb, name, tid);
444   if(!e)
445     eventlog("playlist_deleted", name, 0);
446   return e;
447 }
448
449 /*
450 Local Variables:
451 c-basic-offset:2
452 comment-column:40
453 fill-column:79
454 indent-tabs-mode:nil
455 End:
456 */