An absolute wake time is now calculated and updated by set_server_bits(), TimerTimeout() and tclTimerTimeout(). This is then used to calculate the actual select() timeout, instead of having those functions calculate a relative timeout value themselves. This significantly simplifies those functions, since the underlying values tend to be recorded as absolute times. It also allows us to disentangle tclTimeTimeout from TimerTimeout(), and move the responsibility for calculating the QUEUE_SENDS wake time from TimerTimeout() to set_server_bits() where it belongs. The QUEUE_SENDS and CONNECT_DELAY wake up times will also now be correctly calculated, based on the last sent time and connect time respectively.
608 lines
14 KiB
C
608 lines
14 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
|
|
* 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 && 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:
|
|
{
|
|
#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;
|
|
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 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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
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 = 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 %ld %d %0.3f %s%s", t->ref, t->server,
|
|
t->window, (long)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 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 (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;
|
|
}
|