2 * This file is part of DisOrder
3 * Copyright (C) 2008 Richard Kettlewell
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 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 /** @file server/schedule.c
20 * @brief Scheduled events
22 * @ref trackdb_scheduledb is a mapping from ID strings to encoded
23 * key-value pairs called 'actiondata'.
25 * Possible actiondata keys are:
26 * - @b when: when to perform this action (required)
27 * - @b who: originator for action (required)
28 * - @b action: action to perform (required)
29 * - @b track: for @c action=play, the track to play
30 * - @b key: for @c action=set-global, the global pref to set
31 * - @b value: for @c action=set-global, the value to set (omit to unset)
32 * - @b priority: the importance of this action
33 * - @b recurs: how the event recurs; NOT IMPLEMENTED
34 * - ...others to be defined
36 * Possible actions are:
37 * - @b play: play a track
38 * - @b set-global: set or unset a global pref
39 * - ...others to be defined
41 * Possible priorities are:
42 * - @b junk: junk actions that are in the past at startup are discarded
43 * - @b normal: normal actions that are in the past at startup are run
44 * immediately. (This the default.)
45 * - ...others to be defined
47 * On startup the schedule database is read and a timeout set on the event loop
48 * for each action. Similarly when an action is added, a timeout is set on the
49 * event loop. The timeout has the ID attached as user data so that the action
50 * can easily be found again.
52 * Recurring events are NOT IMPLEMENTED yet but this is the proposed
55 * Recurring events are updated with a new 'when' field when they are processed
56 * (event on error). Non-recurring events are just deleted after processing.
58 * The recurs field is a whitespace-delimited list of criteria:
59 * - nn:nn or nn:nn:nn define a time of day, in local time. There must be
60 * at least one of these but can be more than one.
61 * - a day name (monday, tuesday, ...) defines the days of the week on
62 * which the event will recur. There can be more than one.
63 * - a day number and month name (1 january, 5 february, ...) defines
64 * the days of the year on which the event will recur. There can be
65 * more than one of these.
67 * Day and month names are case insensitive. Multiple languages are
68 * likely to be supported, especially if people send me pointers to
69 * their month and day names. Abbreviations are NOT supported, as
70 * there is more of a risk of a clash between different languages that
73 * If there are no week or year days then the event recurs every day.
75 * If there are both week and year days then the union of them is
76 * taken, rather than the intersection.
78 * TODO: support recurring events.
80 * TODO: add disorder-dump support
82 #include "disorder-server.h"
84 static int schedule_trigger(ev_source *ev,
85 const struct timeval *now,
87 static int schedule_lookup(const char *id,
88 struct kvp *actiondata);
90 /** @brief List of required fields in a scheduled event */
91 static const char *const schedule_required[] = {"when", "who", "action"};
93 /** @brief Number of elements in @ref schedule_required */
94 #define NREQUIRED (int)(sizeof schedule_required / sizeof *schedule_required)
96 /** @brief Parse a scheduled event key and data
97 * @param k Pointer to key
98 * @param d Pointer to data
99 * @param idp Where to store event ID
100 * @param actiondatap Where to store parsed data
101 * @param whenp Where to store timestamp
102 * @return 0 on success, non-0 on error
104 * Rejects entries that are invalid in various ways.
106 static int schedule_parse(const DBT *k,
109 struct kvp **actiondatap,
112 struct kvp *actiondata;
115 /* Reject bogus keys */
116 if(!k->size || k->size > 128) {
117 error(0, "bogus schedule.db key (%lu bytes)", (unsigned long)k->size);
120 id = xstrndup(k->data, k->size);
121 actiondata = kvp_urldecode(d->data, d->size);
122 /* Reject items without the required fields */
123 for(n = 0; n < NREQUIRED; ++n) {
124 if(!kvp_get(actiondata, schedule_required[n])) {
125 error(0, "scheduled event %s: missing required field '%s'",
126 id, schedule_required[n]);
130 /* Return the results */
134 *actiondatap = actiondata;
136 *whenp = (time_t)atoll(kvp_get(actiondata, "when"));
140 /** @brief Delete via a cursor
141 * @return 0 or @c DB_LOCK_DEADLOCK */
142 static int cdel(DBC *cursor) {
145 switch(err = cursor->c_del(cursor, 0)) {
148 case DB_LOCK_DEADLOCK:
149 error(0, "error deleting from schedule.db: %s", db_strerror(err));
152 fatal(0, "error deleting from schedule.db: %s", db_strerror(err));
157 /** @brief Initialize the schedule
158 * @param ev Event loop
159 * @param tid Transaction ID
161 * Sets a callback for all action times except for junk actions that are
162 * already in the past, which are discarded.
164 static int schedule_init_tid(ev_source *ev,
170 cursor = trackdb_opencursor(trackdb_scheduledb, tid);
171 while(!(err = cursor->c_get(cursor, prepare_data(&k), prepare_data(&d),
174 struct kvp *actiondata;
177 /* Parse the key. We destroy bogus entries on sight. */
178 if(schedule_parse(&k, &d, &id, &actiondata, &when.tv_sec)) {
179 if((err = cdel(cursor)))
184 /* The action might be in the past */
185 if(when.tv_sec < time(0)) {
186 const char *priority = kvp_get(actiondata, "priority");
188 if(priority && !strcmp(priority, "junk")) {
189 /* Junk actions that are in the past are discarded during startup */
190 /* TODO recurring events should be handled differently here */
191 info("junk event %s is in the past, discarding", id);
198 /* Arrange a callback when the scheduled event is due */
199 ev_timeout(ev, 0/*handlep*/, &when, schedule_trigger, id);
205 case DB_LOCK_DEADLOCK:
206 error(0, "error querying schedule.db: %s", db_strerror(err));
209 fatal(0, "error querying schedule.db: %s", db_strerror(err));
212 if(trackdb_closecursor(cursor))
213 err = DB_LOCK_DEADLOCK;
217 /** @brief Initialize the schedule
218 * @param ev Event loop
220 * Sets a callback for all action times except for junk actions that are
221 * already in the past, which are discarded.
223 void schedule_init(ev_source *ev) {
225 WITH_TRANSACTION(schedule_init_tid(ev, tid));
228 /******************************************************************************/
230 /** @brief Create a scheduled event
232 * @param actiondata Action data
233 * @param tid Containing transaction
235 static int schedule_add_tid(const char *id,
236 struct kvp *actiondata,
241 memset(&k, 0, sizeof k);
244 switch(err = trackdb_scheduledb->put(trackdb_scheduledb, tid, &k,
245 encode_data(&d, actiondata),
249 case DB_LOCK_DEADLOCK:
250 error(0, "error updating schedule.db: %s", db_strerror(err));
255 fatal(0, "error updating schedule.db: %s", db_strerror(err));
260 /** @brief Create a scheduled event
261 * @param ev Event loop
262 * @param actiondata Action actiondata
263 * @return Scheduled event ID or NULL on error
265 * Events are rejected if they lack the required fields, if the user
266 * is not allowed to perform them or if they are scheduled for a time
269 const char *schedule_add(ev_source *ev,
270 struct kvp *actiondata) {
275 /* TODO: handle recurring events */
276 /* Check that the required field are present */
277 for(n = 0; n < NREQUIRED; ++n) {
278 if(!kvp_get(actiondata, schedule_required[n])) {
279 error(0, "new scheduled event is missing required field '%s'",
280 schedule_required[n]);
284 /* Check that the user is allowed to do whatever it is */
285 if(schedule_lookup("[new]", actiondata) < 0)
287 when.tv_sec = atoll(kvp_get(actiondata, "when"));
289 /* Reject events in the past */
290 if(when.tv_sec <= time(0)) {
291 error(0, "new scheduled event is in the past");
296 WITH_TRANSACTION(schedule_add_tid(id, actiondata, tid));
297 } while(e == DB_KEYEXIST);
298 ev_timeout(ev, 0/*handlep*/, &when, schedule_trigger, (void *)id);
302 /******************************************************************************/
304 /** @brief Get the action data for a scheduled event
306 * @return Event data or NULL
308 struct kvp *schedule_get(const char *id) {
310 struct kvp *actiondata;
312 WITH_TRANSACTION(trackdb_getdata(trackdb_scheduledb, id, &actiondata, tid));
313 /* Check that the required field are present */
314 for(n = 0; n < NREQUIRED; ++n) {
315 if(!kvp_get(actiondata, schedule_required[n])) {
316 error(0, "scheduled event %s is missing required field '%s'",
317 id, schedule_required[n]);
324 /******************************************************************************/
326 /** @brief Delete a scheduled event
327 * @param id Event to delete
328 * @return 0 on success, non-0 if it did not exist
330 int schedule_del(const char *id) {
333 WITH_TRANSACTION(trackdb_delkey(trackdb_scheduledb, id, tid));
334 return e == 0 ? 0 : -1;
337 /******************************************************************************/
339 /** @brief Get a list of scheduled events
340 * @param neventsp Where to put count of events (or NULL)
341 * @return 0-terminate list of ID strings
343 char **schedule_list(int *neventsp) {
348 WITH_TRANSACTION(trackdb_listkeys(trackdb_scheduledb, v, tid));
354 /******************************************************************************/
356 static void schedule_play(ev_source *ev,
359 struct kvp *actiondata) {
360 const char *track = kvp_get(actiondata, "track");
361 struct queue_entry *q;
363 /* This stuff has rather a lot in common with c_play() */
365 error(0, "scheduled event %s: no track field", id);
368 if(!trackdb_exists(track)) {
369 error(0, "scheduled event %s: no such track as %s", id, track);
372 if(!(track = trackdb_resolve(track))) {
373 error(0, "scheduled event %s: cannot resolve track %s", id, track);
376 info("scheduled event %s: %s play %s", id, who, track);
377 q = queue_add(track, who, WHERE_START);
379 if(q == qhead.next && playing)
384 static void schedule_set_global(ev_source attribute((unused)) *ev,
387 struct kvp *actiondata) {
388 const char *key = kvp_get(actiondata, "key");
389 const char *value = kvp_get(actiondata, "value");
392 error(0, "scheduled event %s: no key field", id);
396 error(0, "scheduled event %s: cannot set internal global preferences (%s)",
401 info("scheduled event %s: %s set-global %s=%s", id, who, key, value);
403 info("scheduled event %s: %s set-global %s unset", id, who, key);
404 trackdb_set_global(key, value, who);
407 /** @brief Table of schedule actions
409 * Must be kept sorted.
413 void (*callback)(ev_source *ev,
414 const char *id, const char *who,
415 struct kvp *actiondata);
417 } schedule_actions[] = {
418 { "play", schedule_play, RIGHT_PLAY },
419 { "set-global", schedule_set_global, RIGHT_GLOBAL_PREFS },
422 /** @brief Look up a scheduled event
424 * @param actiondata Event description
425 * @return index in schedule_actions[] on success, -1 on error
427 * Unknown events are rejected as are those that the user is not allowed to do.
429 static int schedule_lookup(const char *id,
430 struct kvp *actiondata) {
431 const char *who = kvp_get(actiondata, "who");
432 const char *action = kvp_get(actiondata, "action");
434 struct kvp *userinfo;
438 /* Look up the action */
439 n = TABLE_FIND(schedule_actions, name, action);
441 error(0, "scheduled event %s: unrecognized action '%s'", id, action);
445 if(!(userinfo = trackdb_getuserinfo(who))) {
446 error(0, "scheduled event %s: user '%s' does not exist", id, who);
449 /* Check that they have suitable rights */
450 if(!(rights = kvp_get(userinfo, "rights"))) {
451 error(0, "scheduled event %s: user %s' has no rights???", id, who);
454 if(parse_rights(rights, &r, 1)) {
455 error(0, "scheduled event %s: user %s has invalid rights '%s'",
459 if(!(r & schedule_actions[n].right)) {
460 error(0, "scheduled event %s: user %s lacks rights for action %s",
467 /** @brief Called when an action is due */
468 static int schedule_trigger(ev_source *ev,
469 const struct timeval attribute((unused)) *now,
471 const char *action, *id = u;
472 struct kvp *actiondata = schedule_get(id);
477 /* schedule_get() enforces these being present */
478 action = kvp_get(actiondata, "action");
479 /* Look up the action */
480 n = schedule_lookup(id, actiondata);
483 /* Go ahead and do it */
484 schedule_actions[n].callback(ev, id, kvp_get(actiondata, "who"), actiondata);
486 /* TODO: rewrite recurring events for their next trigger time,
487 * rather than deleting them */