/* * timer.c -- handles timers in ircII * Copyright 1993, 1996 Matthew Green * This file organized/adapted by Jeremy Nelson * * This used to be in edit.c, and it used to only allow you to * register ircII commands to be executed later. I needed more * generality then that, specifically the ability to register * any function to be called, so i pulled it all together into * this file and called it timer.c */ #include "irc.h" static char cvsrevision[] = "$Id$"; CVS_REVISION(timer_c) #include "struct.h" #include "ircaux.h" #include "lastlog.h" #include "window.h" #include "timer.h" #include "hook.h" #include "output.h" #include "commands.h" #include "misc.h" #include "vars.h" #include "server.h" #include "screen.h" #include "tcl_bx.h" #define MAIN_SOURCE #include "modval.h" static void show_timer (char *command); void BX_delete_all_timers (void); int timer_exists (char *); static TimerList *get_timer (char *ref); #define RETURN_INT(x) return m_strdup(ltoa(x)) /* * timercmd: the bit that handles the TIMER command. If there are no * arguments, then just list the currently pending timers; if we are * given a -DELETE flag, attempt to delete the timer from the list. Else * consider it to be a timer to add, and add it. */ BUILT_IN_COMMAND(timercmd) { char *waittime, *flag; char *want = empty_string; char *ptr; double interval; long events = -2; int update = 0; int winref = current_window->refnum; while (*args == '-' || *args == '/') { flag = next_arg(args, &args); if (!flag || !*flag) break; if (!my_strnicmp(flag+1, "D", 1)) /* DELETE */ { if (!(ptr = next_arg(args, &args))) say("%s: Need a timer reference number for -DELETE", command); else { if (timer_exists(ptr)) delete_timer(ptr); else if (!my_strnicmp(ptr, "A", 1)) delete_all_timers(); } return; } else if (!my_strnicmp(flag+1, "REP", 3)) { char *na = next_arg(args, &args); if (!na || !*na) { say("%s: Missing argument to -REPEAT", command); return; } if (!strcmp(na, "*") || !strcmp(na, "-1")) events = -1; else if ((events = my_atol(na)) == 0) return; } else if (!my_strnicmp(flag+1, "REF", 3)) /* REFNUM */ { want = next_arg(args, &args); if (!want || !*want) { say("%s: Missing argument to -REFNUM", command); return; } } else if (!my_strnicmp(flag + 1, "L", 1)) show_timer(command); else if (!my_strnicmp(flag + 1, "W", 1)) /* WINDOW */ { char *na; if ((na = next_arg(args, &args))) winref = get_winref_by_desc(na); if (winref == -1 && my_stricmp(na, "-1")) { say("%s: That window doesn't exist!", command); return; } } else if (!my_strnicmp(flag + 1, "UPDATE", 1)) /* UPDATE */ update = 1; else say("%s: %s no such flag", command, flag); } /* else check to see if we have no args -> list */ waittime = next_arg(args, &args); if (update || waittime) { if (update && !timer_exists(want)) { say("%s: To use -UPDATE you must specify a valid refnum", command); return; } if (!waittime) interval = -1; else interval = atof(waittime) * 1000.0; if (!update && events == -2) events = 1; else if (events == -2) events = -1; add_timer(update, want, interval, events, NULL, args, subargs, winref, NULL); } else show_timer(command); return; } /* * This is put here on purpose -- we don't want any of the above functions * to have any knowledge of this struct. */ static TimerList *PendingTimers; static char *schedule_timer (TimerList *ntimer); static char *current_exec_timer = empty_string; /* * ExecuteTimers: checks to see if any currently pending timers have * gone off, and if so, execute them, delete them, etc, setting the * current_exec_timer, so that we can't remove the timer while its * still executing. * * changed the behavior: timers will not hook while we are waiting. */ extern void ExecuteTimers (void) { TimerList *current; static int parsingtimer = 0; int old_from_server = from_server; struct timeval now1; /* We do NOT want to parse timers while waiting * cause it gets icky recursive */ if (!PendingTimers || parsingtimer) return; get_time(&now1); parsingtimer = 1; while (PendingTimers && time_cmp(&now1, &PendingTimers->time) >= 0) { int old_refnum = current_window->refnum; current = PendingTimers; PendingTimers = current->next; make_window_current_by_winref(current->window); /* * Restore from_server and curr_scr_win from when the * timer was registered */ if (is_server_connected(current->server)) from_server = current->server; else if (is_server_connected(get_window_server(0))) from_server = get_window_server(0); else from_server = -1; /* * If a callback function was registered, then * we use it. If no callback function was registered, * then we use ''parse_line''. */ current_exec_timer = current->ref; if (current->callback) (*current->callback)(current->command, current->subargs); else parse_line("TIMER",(char *)current->command, current->subargs, 0, 0, 1); current_exec_timer = empty_string; from_server = old_from_server; make_window_current_by_winref(old_refnum); /* * Clean up or reschedule the timer */ switch (current->events) { case 0: case 1: { /* callback cleans up command */ if (!current->callback) new_free(¤t->command); new_free(¤t->subargs); new_free(¤t->whom); new_free(¤t); break; } default: current->events--; case -1: { time_offset(¤t->time, current->interval); schedule_timer(current); break; } } } parsingtimer = 0; } /* * show_timer: Display a list of all the TIMER commands that are * pending to be executed. */ static void show_timer (char *command) { TimerList *tmp; struct timeval current; double time_left; int count = 0; for (tmp = PendingTimers; tmp; tmp = tmp->next) count++; if (!count) { say("%s: No commands pending to be executed", command); return; } get_time(¤t); put_it("%s", convert_output_format(fget_string_var(FORMAT_TIMER_FSET), "%s %s %s %s","Timer","Seconds","Events","Command")); for (tmp = PendingTimers; tmp; tmp = tmp->next) { char buf[40]; time_left = BX_time_diff(current, tmp->time); if (time_left < 0) time_left = 0; snprintf(buf, sizeof buf, "%0.3f", time_left); put_it("%s", convert_output_format(fget_string_var(FORMAT_TIMER_FSET), "%s %s %d %s %s", tmp->ref, buf, tmp->events, tmp->callback? "(internal callback)" : (tmp->command? tmp->command : ""), tmp->whom ? tmp->whom : empty_string )); } } /* * create_timer_ref: returns the lowest unused reference number for a timer * * This will never return 0 for a refnum because that is what atol() returns * on case of error, so that it can never happen that a timer has a refnum * of zero which would be tripped if the user did say, * /TIMER -refnUm foobar 3 blah blah blah * which should elicit an error, not be silently punted. */ static int create_timer_ref (char *refnum_want, char *refnum_gets) { TimerList *tmp; int refnum = 0; /* Max of 10 characters. */ if (strlen(refnum_want) > REFNUM_MAX) refnum_want[REFNUM_MAX] = 0; /* If the user doesn't care */ if (!strcmp(refnum_want, empty_string)) { /* Find the lowest refnum available */ for (tmp = PendingTimers; tmp; tmp = tmp->next) { long ref; ref = my_atol(tmp->ref); if (refnum < ref) refnum = ref; } strlcpy(refnum_gets, ltoa(refnum+1), REFNUM_MAX); } else { /* See if the refnum is available */ for (tmp = PendingTimers; tmp; tmp = tmp->next) { if (!my_stricmp(tmp->ref, refnum_want)) return -1; } strlcpy(refnum_gets, refnum_want, REFNUM_MAX); } return 0; } /* * Deletes a refnum. This does cleanup only if the timer is a * user-defined timer, otherwise no clean up is done (the caller * is responsible to handle it) This shouldnt output an error, * it should be more general and return -1 and let the caller * handle it. Probably will be that way in a future release. */ extern int BX_delete_timer (char *ref) { TimerList *tmp, *prev; if (current_exec_timer != empty_string) { say("You may not remove a TIMER from another TIMER"); return -1; } for (prev = tmp = PendingTimers; tmp; prev = tmp, tmp = tmp->next) { /* can only delete user created timers */ if (!my_stricmp(tmp->ref, ref)) { if (tmp == prev) PendingTimers = PendingTimers->next; else prev->next = tmp->next; if (!tmp->callback) new_free(&tmp->command); new_free(&tmp->subargs); new_free(&tmp->whom); new_free((char **)&tmp); return 0; } } say("TIMER: Can't delete %s, no such refnum", ref); return -1; } void BX_delete_all_timers (void) { while (PendingTimers) delete_timer(PendingTimers->ref); return; } int timer_exists (char *ref) { if (get_timer(ref)) return 1; return 0; } BUILT_IN_FUNCTION(function_istimer) { char *timer = NULL; if ((timer = next_arg(input, &input))) if (timer_exists(timer)) RETURN_INT(1); RETURN_INT(0); } int timer_callback_exists (void *ref) { TimerList *t; for (t = PendingTimers; t; t = t->next) { if (t->callback && ref == t->callback) return 1; } return 0; } static TimerList *get_timer (char *ref) { TimerList *tmp; for (tmp = PendingTimers; tmp; tmp = tmp->next) { if (!my_stricmp(tmp->ref, ref) || (tmp->whom && !my_stricmp(tmp->whom, ref))) return tmp; } return NULL; } char *function_timer(char *n, char *args) { char *ref = next_arg(args, &args); TimerList *t = NULL; double time_left; if (ref && *ref) t = get_timer(ref); if (!t) return m_strdup(empty_string); time_left = time_until(&t->time); if (time_left < 0) time_left = 0.0; return m_sprintf("%s %d %d %.16g %d %0.3f %s%s", t->ref, t->server, t->window, t->interval, t->events, time_left, t->callback ? "(internal callback) " : t->command, t->whom ? t->whom : ""); } /* * You call this to register a timer callback. * * The arguments: * refnum_want: The refnum requested. This should only be specified * by the user, functions wanting callbacks should specify * the value -1 which means "don't care". * The rest of the arguments are dependent upon the value of "callback" * -- if "callback" is NULL then: * callback: NULL * what: some ircII commands to run when the timer goes off * subargs: what to use to expand $0's, etc in the 'what' variable. * * -- if "callback" is non-NULL then: * callback: function to call when timer goes off * what: argument to pass to "callback" function. Should be some * non-auto storage, perhaps a struct or a malloced char * * array. The caller is responsible for disposing of this * area when it is called, since the timer mechanism does not * know anything of the nature of the argument. * subargs: should be NULL, its ignored anyhow. */ char *BX_add_timer(int update, char *refnum_want, double when, long events, int (callback) (void *, char *), char *what, char *subargs, int winref, char *whom) { TimerList *ntimer, *otimer = NULL; char refnum_got[REFNUM_MAX+1] = ""; ntimer = (TimerList *) new_malloc(sizeof(TimerList)); get_time(&ntimer->time); time_offset(&ntimer->time, when / 1000.0); ntimer->interval = when / 1000; ntimer->events = events; ntimer->server = from_server; ntimer->window = winref; if (whom) ntimer->whom = m_strdup(whom); if (update) otimer = get_timer(refnum_want); if (otimer) { if (when == -1) { ntimer->time = otimer->time; ntimer->interval = otimer->interval; } if (events == -1) ntimer->events = otimer->events; ntimer->callback = otimer->callback; if (what && *what) { ntimer->command = (void *)what; if (subargs) ntimer->subargs = m_strdup(subargs); } else { ntimer->command = m_strdup(otimer->command); ntimer->subargs = m_strdup(otimer->subargs); } delete_timer(refnum_want); } else { if ((ntimer->callback = callback) != NULL) ntimer->command = (void *)what; else ntimer->command = m_strdup(what); if (subargs) ntimer->subargs = m_strdup(subargs); } if (create_timer_ref(refnum_want, refnum_got) == -1) { say("TIMER: Refnum %s already exists", refnum_want); new_free(&ntimer->command); new_free(&ntimer->subargs); new_free(&ntimer->whom); new_free(&ntimer); return NULL; } strcpy(ntimer->ref, refnum_got); /* we've created it, now put it in order */ return schedule_timer(ntimer); } static char *schedule_timer (TimerList *ntimer) { TimerList **slot; /* we've created it, now put it in order */ for (slot = &PendingTimers; *slot; slot = &(*slot)->next) { if (time_cmp(&(*slot)->time, &ntimer->time) > 0) break; } ntimer->next = *slot; *slot = ntimer; return ntimer->ref; } /* * TimerTimeout: Called from irc_io to help create the timeout * part of the call to select. */ void TimerTimeout(struct timeval *wake_time) { if (PendingTimers && time_cmp(wake_time, &PendingTimers->time) > 0) *wake_time = PendingTimers->time; }