From fda749ac67ffb2007593a3ebc539fe563f292f88 Mon Sep 17 00:00:00 2001 From: Rob Swindell <rob@synchro.net> Date: Sun, 20 Dec 2020 22:24:53 -0800 Subject: [PATCH] Fix next-forced-exclusive event time calculation Jump the time forward (in 24-hour chunks) to find the next date/time the event will run rather than just adding 24-hours and assuming it's an event that runs every day (of the week or month) at a specific time. Also, expose the next-run-date/time for an event as a new `next_run` property for `xtrn_area.event[]` (in `time_t` format) for easier debugging of these kinds of issues. Also expose the error log level as a new property: `error_level` while we're here. --- src/sbbs3/data.cpp | 64 +++++++++----- src/sbbs3/js_xtrn_area.c | 178 +++++++++++++++++++++++++-------------- src/sbbs3/sbbs.h | 1 + src/sbbs3/scfgdefs.h | 17 +--- 4 files changed, 160 insertions(+), 100 deletions(-) diff --git a/src/sbbs3/data.cpp b/src/sbbs3/data.cpp index 4e4e587a79..96f52ace4e 100644 --- a/src/sbbs3/data.cpp +++ b/src/sbbs3/data.cpp @@ -133,6 +133,45 @@ int sbbs_t::getuserxfers(int fromuser, int destuser, char *fname) return(found); } +/****************************************************************************/ +/* Return date/time that the specified event should run next */ +/****************************************************************************/ +extern "C" time_t DLLCALL getnexteventtime(event_t* event) +{ + struct tm tm; + time_t t = time(NULL); + + if(event->misc & EVENT_DISABLED) + return 0; + + if(event->days == 0 || event->freq != 0) + return 0; + + if(localtime_r(&t, &tm) == NULL) + return 0; + + tm.tm_hour = event->time / 60; + tm.tm_min = event->time % 60; + tm.tm_sec = 0; + tm.tm_isdst = -1; /* Do not adjust for DST */ + t = mktime(&tm); + + if(event->last >= t) + t += 24 * 60 * 60; /* already ran today, so add 24hrs */ + + do { + if(localtime_r(&t, &tm) == NULL) + return 0; + if((event->days & (1 << tm.tm_wday)) + && (event->mdays == 0 || (event->mdays & (1 << tm.tm_mday))) + && (event->months == 0 || (event->months & (1 << tm.tm_mon)))) + break; + t += 24 * 60 * 60; + } while(t > 0); + + return t; +} + /****************************************************************************/ /* Return time of next forced timed event */ /* 'event' may be NULL */ @@ -140,40 +179,21 @@ int sbbs_t::getuserxfers(int fromuser, int destuser, char *fname) extern "C" time_t DLLCALL getnextevent(scfg_t* cfg, event_t* event) { int i; - time_t now=time(NULL); time_t event_time=0; time_t thisevent; - time_t tmptime; - struct tm tm, last_tm; - - if(localtime_r(&now,&tm) == NULL) - return 0; for(i=0;i<cfg->total_events;i++) { if(!cfg->event[i]->node || cfg->event[i]->node>cfg->sys_nodes || cfg->event[i]->misc&EVENT_DISABLED) continue; if(!(cfg->event[i]->misc&EVENT_FORCE) - || (!(cfg->event[i]->misc&EVENT_EXCL) && cfg->event[i]->node!=cfg->node_num) - || !(cfg->event[i]->days&(1<<tm.tm_wday)) - || (cfg->event[i]->mdays!=0 && !(cfg->event[i]->mdays&(1<<tm.tm_mday))) - || (cfg->event[i]->months!=0 && !(cfg->event[i]->months&(1<<tm.tm_mon)))) + || (!(cfg->event[i]->misc&EVENT_EXCL) && cfg->event[i]->node!=cfg->node_num)) continue; - tm.tm_hour=cfg->event[i]->time/60; - tm.tm_min=cfg->event[i]->time%60; - tm.tm_sec=0; - tm.tm_isdst=-1; /* Do not adjust for DST */ - thisevent=mktime(&tm); - if(thisevent == -1) + thisevent = getnexteventtime(cfg->event[i]); + if(thisevent <= 0) continue; - tmptime=cfg->event[i]->last; - if(localtime_r(&tmptime,&last_tm) == NULL) - memset(&last_tm,0,sizeof(last_tm)); - - if(tm.tm_mday==last_tm.tm_mday && tm.tm_mon==last_tm.tm_mon) - thisevent+=24L*60L*60L; /* already ran today, so add 24hrs */ if(!event_time || thisevent<event_time) { event_time=thisevent; if(event!=NULL) diff --git a/src/sbbs3/js_xtrn_area.c b/src/sbbs3/js_xtrn_area.c index 804a77e2b5..590400ac07 100644 --- a/src/sbbs3/js_xtrn_area.c +++ b/src/sbbs3/js_xtrn_area.c @@ -1,9 +1,5 @@ -/* js_xtrn_area.c */ - /* Synchronet JavaScript "External Program Area" Object */ -/* $Id: js_xtrn_area.c,v 1.31 2019/01/05 06:33:39 rswindell Exp $ */ - /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * @@ -17,21 +13,9 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -83,12 +67,14 @@ static char* event_prop_desc[] = { "command-line" ,"startup directory" ,"node number" - ,"time to execute" + ,"time to execute (minutes since midnight)" ,"frequency to execute" ,"days of week to execute (bitfield)" ,"days of month to execute (bitfield)" ,"months of year to execute (bitfield)" - ,"date/time last run (in time_t format)" + ,"date/time of last run (in time_t format)" + ,"date/time of next run (in time_t format)" + ,"error log level" ,"toggle options (bitfield)" ,NULL }; @@ -105,6 +91,40 @@ static char* xedit_prop_desc[] = { #endif +/* Event Object Properties */ +enum { + EVENT_PROP_CMD, + EVENT_PROP_STARTUP_DIR, + EVENT_PROP_NODE_NUM, + EVENT_PROP_TIME, + EVENT_PROP_FREQ, + EVENT_PROP_DAYS, + EVENT_PROP_MDAYS, + EVENT_PROP_MONTHS, + EVENT_PROP_LAST_RUN, + EVENT_PROP_NEXT_RUN, + EVENT_PROP_ERRLEVEL, + EVENT_PROP_MISC +}; + +static jsSyncPropertySpec js_event_properties[] = { +/* name ,tinyid ,flags ,ver */ + + { "cmd" ,EVENT_PROP_CMD ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "startup_dir" ,EVENT_PROP_STARTUP_DIR ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "node_num" ,EVENT_PROP_NODE_NUM ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "time" ,EVENT_PROP_TIME ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "freq" ,EVENT_PROP_FREQ ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "days" ,EVENT_PROP_DAYS ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "mdays" ,EVENT_PROP_MDAYS ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "months" ,EVENT_PROP_MONTHS ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "last_run" ,EVENT_PROP_LAST_RUN ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { "next_run" ,EVENT_PROP_NEXT_RUN ,JSPROP_ENUMERATE|JSPROP_READONLY ,31802}, + { "error_level" ,EVENT_PROP_ERRLEVEL ,JSPROP_ENUMERATE|JSPROP_READONLY ,31802}, + { "settings" ,EVENT_PROP_MISC ,JSPROP_ENUMERATE|JSPROP_READONLY ,311}, + { NULL } +}; + BOOL DLLCALL js_CreateXtrnProgProperties(JSContext* cx, JSObject* obj, xtrn_t* xtrn) { JSString* js_str; @@ -182,6 +202,79 @@ BOOL DLLCALL js_CreateXtrnProgProperties(JSContext* cx, JSObject* obj, xtrn_t* x return(TRUE); } +static JSBool js_event_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + const char* p = NULL; + JSString* js_str; + jsval idval; + jsint tiny; + event_t* event; + + if((event = JS_GetPrivate(cx, obj)) == NULL) + return JS_FALSE; + + JS_IdToValue(cx, id, &idval); + tiny = JSVAL_TO_INT(idval); + + switch(tiny) { + case EVENT_PROP_CMD: + p = event->cmd; + break; + case EVENT_PROP_STARTUP_DIR: + p = event->dir; + break; + case EVENT_PROP_NODE_NUM: + *vp = UINT_TO_JSVAL(event->node); + break; + case EVENT_PROP_TIME: + *vp = UINT_TO_JSVAL(event->time); + break; + case EVENT_PROP_FREQ: + *vp = UINT_TO_JSVAL(event->freq); + break; + case EVENT_PROP_DAYS: + *vp = UINT_TO_JSVAL(event->days); + break; + case EVENT_PROP_MDAYS: + *vp = UINT_TO_JSVAL(event->mdays); + break; + case EVENT_PROP_MONTHS: + *vp = UINT_TO_JSVAL(event->months); + break; + case EVENT_PROP_LAST_RUN: + *vp = UINT_TO_JSVAL(event->last); + break; + case EVENT_PROP_NEXT_RUN: + *vp = UINT_TO_JSVAL((uint32)getnexteventtime(event)); + break; + case EVENT_PROP_ERRLEVEL: + *vp = UINT_TO_JSVAL(event->errlevel); + break; + case EVENT_PROP_MISC: + *vp = UINT_TO_JSVAL(event->misc); + break; + } + + if(p != NULL) { /* string property */ + if((js_str = JS_NewStringCopyZ(cx, p)) == NULL) + return JS_FALSE; + *vp = STRING_TO_JSVAL(js_str); + } + return JS_TRUE; +} + +static JSClass js_event_class = { + "Event" /* name */ + ,JSCLASS_HAS_PRIVATE /* flags */ + ,JS_PropertyStub /* addProperty */ + ,JS_PropertyStub /* delProperty */ + ,js_event_get /* getProperty */ + ,JS_StrictPropertyStub /* setProperty */ + ,JS_EnumerateStub /* enumerate */ + ,JS_ResolveStub /* resolve */ + ,JS_ConvertStub /* convert */ + ,JS_FinalizeStub /* finalize */ +}; struct js_xtrn_area_priv { scfg_t *cfg; @@ -407,55 +500,16 @@ JSBool DLLCALL js_xtrn_area_resolve(JSContext* cx, JSObject* areaobj, jsid id) for(l=0;l<p->cfg->total_events;l++) { - if((eventobj=JS_NewObject(cx, NULL, NULL, NULL))==NULL) + if((eventobj=JS_NewObject(cx, &js_event_class, NULL, NULL))==NULL) return JS_FALSE; - if(!JS_DefineProperty(cx, event_array, p->cfg->event[l]->code, OBJECT_TO_JSVAL(eventobj) - ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE)) - return JS_FALSE; + JS_SetPrivate(cx, eventobj, p->cfg->event[l]); - if((js_str=JS_NewStringCopyZ(cx, p->cfg->event[l]->cmd))==NULL) - return JS_FALSE; - if(!JS_DefineProperty(cx, eventobj, "cmd", STRING_TO_JSVAL(js_str) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) - return JS_FALSE; - - if((js_str=JS_NewStringCopyZ(cx, p->cfg->event[l]->dir))==NULL) - return JS_FALSE; - if(!JS_DefineProperty(cx, eventobj, "startup_dir", STRING_TO_JSVAL(js_str) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) - return JS_FALSE; - - if(!JS_DefineProperty(cx, eventobj, "node_num", INT_TO_JSVAL(p->cfg->event[l]->node) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) + if(!js_DefineSyncProperties(cx, eventobj, js_event_properties)) return JS_FALSE; - if(!JS_DefineProperty(cx, eventobj, "time", INT_TO_JSVAL(p->cfg->event[l]->time) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) - return JS_FALSE; - - if(!JS_DefineProperty(cx, eventobj, "freq", INT_TO_JSVAL(p->cfg->event[l]->freq) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) - return JS_FALSE; - - if(!JS_DefineProperty(cx, eventobj, "days", INT_TO_JSVAL(p->cfg->event[l]->days) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) - return JS_FALSE; - - if(!JS_DefineProperty(cx, eventobj, "mdays", INT_TO_JSVAL(p->cfg->event[l]->mdays) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) - return JS_FALSE; - - if(!JS_DefineProperty(cx, eventobj, "months", INT_TO_JSVAL(p->cfg->event[l]->months) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) - return JS_FALSE; - - if(!JS_DefineProperty(cx, eventobj, "last_run", INT_TO_JSVAL(p->cfg->event[l]->last) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) - return JS_FALSE; - - if(!JS_DefineProperty(cx, eventobj, "settings", INT_TO_JSVAL(p->cfg->event[l]->misc) - ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY)) + if(!JS_DefineProperty(cx, event_array, p->cfg->event[l]->code, OBJECT_TO_JSVAL(eventobj) + ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE)) return JS_FALSE; #ifdef BUILD_JSDOCS diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h index 13dd610031..b5abef99c3 100644 --- a/src/sbbs3/sbbs.h +++ b/src/sbbs3/sbbs.h @@ -1179,6 +1179,7 @@ extern "C" { /* data.cpp */ DLLEXPORT time_t DLLCALL getnextevent(scfg_t* cfg, event_t* event); + DLLEXPORT time_t DLLCALL getnexteventtime(event_t* event); /* sockopt.c */ DLLEXPORT int DLLCALL set_socket_options(scfg_t* cfg, SOCKET sock, const char* section diff --git a/src/sbbs3/scfgdefs.h b/src/sbbs3/scfgdefs.h index 04da305be6..9ce17a5c82 100644 --- a/src/sbbs3/scfgdefs.h +++ b/src/sbbs3/scfgdefs.h @@ -1,7 +1,4 @@ /* Synchronet configuration structure (scfg_t) definition */ -// vi: tabstop=4 - -/* $Id: scfgdefs.h,v 1.62 2020/08/08 20:17:03 rswindell Exp $ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * @@ -16,21 +13,9 @@ * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * - * Anonymous FTP access to the most recent released source is available at * - * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * - * * - * Anonymous CVS access to the development source and modification history * - * is available at cvs.synchro.net:/cvsroot/sbbs, example: * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * - * (just hit return, no password is necessary) * - * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * - * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * - * You are encouraged to submit any modifications (preferably in Unix diff * - * format) via e-mail to mods@synchro.net * - * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ @@ -305,9 +290,9 @@ typedef struct { /* External Editors */ typedef struct { /* Generic Timed Event */ char code[LEN_CODE+1], /* Internal code */ - days, /* Week days to run event */ dir[LEN_DIR+1], /* Start-up directory */ cmd[LEN_CMD+1]; /* Command line */ + uint8_t days; /* Week days to run event */ uint16_t node, /* Node to execute event */ time, /* Time to run event */ freq; /* Frequency to run event */ -- GitLab