bitchx/source/timer.c

548 lines
13 KiB
C

/*
* 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(&current->command);
new_free(&current->subargs);
new_free(&current->whom);
new_free(&current);
break;
}
default:
current->events--;
case -1:
{
time_offset(&current->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(&current);
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;
}