/* * 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: timer.c,v 1.1.1.1 2003/04/11 01:09:07 dan Exp $"; 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 * arguements, then just list the currently pending timers, if we are * give 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 doesnt 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 dont 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 && BX_time_diff(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: { #if 1 double milli, seconds; milli = current->interval * 1000 * 1000 + current->time.tv_usec; seconds = current->time.tv_sec + (milli / 1000000); milli = ((unsigned long)current) % 1000000; current->time.tv_sec = seconds; current->time.tv_usec = milli; #else current->time.tv_usec += (current->interval * 1000); current->time.tv_sec += (current->time.tv_usec / 1000000); current->time.tv_usec %= 1000000; #endif schedule_timer(current); break; } } if (current && current->delete) { if (!current->callback) new_free(¤t->command); new_free(¤t->subargs); new_free(¤t->whom); new_free(¤t); } } 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; sprintf(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 doesnt 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; } strmcpy(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; } strmcpy(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; } int kill_timer(char *ref) { TimerList *tmp; for (tmp = PendingTimers; tmp; tmp = tmp->next) { /* can only delete user created timers */ if (!my_stricmp(tmp->ref, ref)) { tmp->delete = 1; return 0; } } return -1; } int get_delete_timer(char *ref) { TimerList *tmp; for (tmp = PendingTimers; tmp; tmp = tmp->next) { /* can only delete user created timers */ if (!my_stricmp(tmp->ref, ref)) return tmp->delete; } 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; char *out = NULL; TimerList *tmp; ref = next_arg(args, &args); if (ref && *ref && (tmp = get_timer(ref))) { double time_left; struct timeval current; char buf[40]; get_time(¤t); time_left = BX_time_diff(current, tmp->time); if (time_left < 0) time_left = 0.0; sprintf(buf, "%0.3f", time_left); malloc_sprintf(&out, "%s %d %d %d %d %s %s %s", tmp->ref, tmp->server, tmp->window, tmp->interval, tmp->events, buf, tmp->callback? "(internal callback)" : (tmp->command? tmp->command : ""), tmp->whom ? tmp->whom : empty_string ); return ref; } return m_strdup(empty_string); } /* * You call this to register a timer callback. * * The arguments: * refnum_want: The refnUm requested. This should only be sepcified * by the user, functions wanting callbacks should specify * the value -1 which means "dont care". * The rest of the arguments are dependant 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] = ""; double seconds = 0.0, milli = 0.0; extern double fmod(double, double); ntimer = (TimerList *) new_malloc(sizeof(TimerList)); get_time(&ntimer->time); #if 1 milli = when * 1000 + ntimer->time.tv_usec; seconds = ntimer->time.tv_sec + (milli / 1000000); milli = ((unsigned long)milli) % 1000000; ntimer->time.tv_sec = seconds; ntimer->time.tv_usec = milli; #else ntimer->time.tv_usec += (unsigned long)when; ntimer->time.tv_sec += ((when + ntimer->time.tv_usec) / 1000); ntimer->time.tv_usec %= 1000; #endif 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 (BX_time_diff((*slot)->time, ntimer->time) < 0) break; } ntimer->next = *slot; *slot = ntimer; return ntimer->ref; } static struct timeval current; /* * TimerTimeout: Called from irc_io to help create the timeout * part of the call to select. */ time_t TimerTimeout (void) { time_t timeout_in; time_t t = MAGIC_TIMEOUT; if (is_server_queue() && (t = get_int_var(QUEUE_SENDS_VAR))) ; else t = MAGIC_TIMEOUT; get_time(¤t); if (!PendingTimers) timeout_in = tclTimerTimeout(MAGIC_TIMEOUT); else { timeout_in = (time_t)(BX_time_diff(current, PendingTimers->time) * 1000); timeout_in = (time_t)(tclTimerTimeout((timeout_in < 0) ? 0 : timeout_in)); } if (t < timeout_in) timeout_in = t * 1000; return (timeout_in < 0) ? 0 : timeout_in + 100; }