stream replay and subscription update

This commit is contained in:
Olof hagsand 2018-10-28 17:17:36 +01:00
parent 25c761202e
commit fb0d0606e3
5 changed files with 285 additions and 138 deletions

View file

@ -85,6 +85,7 @@
``` ```
### Minor changes ### Minor changes
* Allow new lines in CLI prompts
* uri_percent_encode() and xml_chardata_encode() changed to use stdarg parameters * uri_percent_encode() and xml_chardata_encode() changed to use stdarg parameters
* Added CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml as option and in example (so you dont need to provide -f command-line option). * Added CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml as option and in example (so you dont need to provide -f command-line option).
* Yang 1.1 action syntax added (but function is not supported) * Yang 1.1 action syntax added (but function is not supported)

View file

@ -84,8 +84,8 @@ ce_find_bypid(struct client_entry *ce_list,
/*! Stream callback for netconf stream notification (RFC 5277) /*! Stream callback for netconf stream notification (RFC 5277)
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] event Event as XML * @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_cb_add * @param[in] arg Extra argument provided in stream_ss_add
* @see stream_cb_add * @see stream_ss_add
*/ */
static int static int
ce_event_cb(clicon_handle h, ce_event_cb(clicon_handle h,
@ -116,7 +116,7 @@ ce_event_cb(clicon_handle h,
* Close down everything wrt clients (eg sockets, subscriptions) * Close down everything wrt clients (eg sockets, subscriptions)
* Finally actually remove client struct in handle * Finally actually remove client struct in handle
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] ce Client hadnle * @param[in] ce Client handle
* @see backend_client_delete for actual deallocation of client entry struct * @see backend_client_delete for actual deallocation of client entry struct
*/ */
int int
@ -137,7 +137,7 @@ backend_client_rm(clicon_handle h,
ce->ce_s = 0; ce->ce_s = 0;
} }
/* for all streams */ /* for all streams */
stream_cb_delete(h, NULL, ce_event_cb, (void*)ce); stream_ss_delete_all(h, ce_event_cb, (void*)ce);
break; break;
} }
ce_prev = &c->ce_next; ce_prev = &c->ce_next;
@ -807,7 +807,6 @@ from_client_delete_config(clicon_handle h,
return retval; return retval;
} }
/*! Internal message: Create subscription for notifications see RFC 5277 /*! Internal message: Create subscription for notifications see RFC 5277
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xe Netconf request xml tree * @param[in] xe Netconf request xml tree
@ -874,13 +873,20 @@ from_client_create_subscription(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
if (stream_cb_add(h, stream, selector, stoptime?&stop:NULL, if (stream_ss_add(h, stream, selector,
starttime?&start:NULL, stoptime?&stop:NULL,
ce_event_cb, (void*)ce) < 0) ce_event_cb, (void*)ce) < 0)
goto done; goto done;
/* Replay of this stream to specific subscription according to start and stop*/ /* Replay of this stream to specific subscription according to start and
if (starttime) /* XXX No this must be done _after_ this RPC completes */ * stop (if present).
if (stream_replay(h, stream, &start, stoptime?&stop:NULL) < 0) * RFC 5277: If <startTime> is not present, this is not a replay
* subscription.
* Schedule the replay to occur right after this RPC completes, eg "now"
*/
if (starttime){
if (stream_replay_trigger(h, stream, ce_event_cb, (void*)ce) < 0)
goto done; goto done;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>"); cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok: ok:
retval = 0; retval = 0;
@ -1274,7 +1280,7 @@ from_client_msg(clicon_handle h,
} }
else if (strcmp(name, "close-session") == 0){ else if (strcmp(name, "close-session") == 0){
xmldb_unlock_all(h, pid); xmldb_unlock_all(h, pid);
stream_cb_delete(h, NULL, ce_event_cb, (void*)ce); stream_ss_delete_all(h, ce_event_cb, (void*)ce);
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>"); cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
} }
else if (strcmp(name, "kill-session") == 0){ else if (strcmp(name, "kill-session") == 0){

View file

@ -42,15 +42,16 @@
/* Subscription callback /* Subscription callback
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] event Event as XML * @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_cb_add * @param[in] arg Extra argument provided in stream_ss_add
* @see stream_cb_add * @see stream_ss_add
*/ */
typedef int (*stream_fn_t)(clicon_handle h, cxobj *event, void *arg); typedef int (*stream_fn_t)(clicon_handle h, cxobj *event, void *arg);
struct stream_subscription{ struct stream_subscription{
struct stream_subscription *ss_next; qelem_t ss_q; /* queue header */
char *ss_stream; /* Name of associated stream */ char *ss_stream; /* Name of associated stream */
char *ss_xpath; /* Filter selector as xpath */ char *ss_xpath; /* Filter selector as xpath */
struct timeval ss_starttime; /* Replay starttime */
struct timeval ss_stoptime; /* Replay stoptime */ struct timeval ss_stoptime; /* Replay stoptime */
stream_fn_t ss_fn; /* Callback when event occurs */ stream_fn_t ss_fn; /* Callback when event occurs */
void *ss_arg; /* Callback argument */ void *ss_arg; /* Callback argument */
@ -82,19 +83,26 @@ event_stream_t *stream_find(clicon_handle h, const char *name);
int stream_register(clicon_handle h, const char *name, const char *description, int replay_enabled); int stream_register(clicon_handle h, const char *name, const char *description, int replay_enabled);
int stream_delete_all(event_stream_t *es); int stream_delete_all(event_stream_t *es);
int stream_get_xml(clicon_handle h, int access, cbuf *cb); int stream_get_xml(clicon_handle h, int access, cbuf *cb);
struct stream_subscription *stream_cb_add(clicon_handle h, char *stream, int stream_timer_setup(int fd, void *arg);
char *xpath, struct timeval *stop, stream_fn_t fn, void *arg); /* Subscriptions */
int stream_cb_delete(clicon_handle h, char *stream, stream_fn_t fn, void *arg); struct stream_subscription *stream_ss_add(clicon_handle h, char *stream,
int stream_notify_xml(clicon_handle h, event_stream_t *es, cxobj *xevent); char *xpath, struct timeval *start, struct timeval *stop,
stream_fn_t fn, void *arg);
int stream_ss_rm(event_stream_t *es, struct stream_subscription *ss);
struct stream_subscription *stream_ss_find(event_stream_t *es,
stream_fn_t fn, void *arg);
int stream_ss_delete_all(clicon_handle h, stream_fn_t fn, void *arg);
int stream_notify_xml(clicon_handle h, event_stream_t *es, struct timeval *tv, cxobj *xevent);
#if defined(__GNUC__) && __GNUC__ >= 3 #if defined(__GNUC__) && __GNUC__ >= 3
int stream_notify(clicon_handle h, char *stream, const char *event, ...) __attribute__ ((format (printf, 3, 4))); int stream_notify(clicon_handle h, char *stream, const char *event, ...) __attribute__ ((format (printf, 3, 4)));
#else #else
int stream_notify(clicon_handle h, char *stream, const char *event, ...); int stream_notify(clicon_handle h, char *stream, const char *event, ...);
#endif #endif
/* Replay */
int stream_replay(clicon_handle h, char *stream, struct timeval *start, struct timeval *stop);
int stream_replay_add(event_stream_t *es, struct timeval *tv, cxobj *xv); int stream_replay_add(event_stream_t *es, struct timeval *tv, cxobj *xv);
int stream_replay_trigger(clicon_handle h, char *stream, stream_fn_t fn, void *arg);
/* Experimental publish streams using SSE. CLIXON_PUBLISH_STREAMS should be set */ /* Experimental publish streams using SSE. CLIXON_PUBLISH_STREAMS should be set */
int stream_publish(clicon_handle h, char *stream); int stream_publish(clicon_handle h, char *stream);

View file

@ -34,9 +34,17 @@
* Event notification streams according to RFC5277 * Event notification streams according to RFC5277
* The stream implementation has three parts: * The stream implementation has three parts:
* 1) Base stream handling: stream_find/register/delete_all/get_xml * 1) Base stream handling: stream_find/register/delete_all/get_xml
* 2) Callback and notification handling (stream_cb_add/delete/timeout, stream_notify, etc * 2) Stream subscription handling (stream_ss_add/delete/timeout, stream_notify, etc
* 3) Stream replay: stream_replay/_add * 3) Stream replay: stream_replay/_add
* 4) nginx/nchan publish code (use --enable-publish config option) * 4) nginx/nchan publish code (use --enable-publish config option)
*
* +---------------+ * +---------------+ * +---------------+
* | clicon_handle |--------->| event_stream |--------->| subscription |
* +---------------+ +---------------+ +---------------+
* \ * +---------------+
* +-->| replay |
* +---------------+
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
@ -58,6 +66,7 @@
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_err.h" #include "clixon_err.h"
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_event.h"
#include "clixon_string.h" #include "clixon_string.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_handle.h" #include "clixon_handle.h"
@ -68,6 +77,9 @@
#include "clixon_xpath.h" #include "clixon_xpath.h"
#include "clixon_stream.h" #include "clixon_stream.h"
/* Go through and timeout subscription timers [s] */
#define STREAM_TIMER_TIMEOUT_S 5
/*! Find an event notification stream given name /*! Find an event notification stream given name
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] name Name of stream * @param[in] name Name of stream
@ -129,6 +141,7 @@ stream_delete_all(event_stream_t *es)
{ {
event_stream_t *e_next; event_stream_t *e_next;
struct stream_replay *r; struct stream_replay *r;
struct stream_subscription *ss;
while (es){ while (es){
e_next = es->es_next; e_next = es->es_next;
@ -136,6 +149,8 @@ stream_delete_all(event_stream_t *es)
free(es->es_name); free(es->es_name);
if (es->es_description) if (es->es_description)
free(es->es_description); free(es->es_description);
while ((ss = es->es_subscription) != NULL)
stream_ss_rm(es, ss);
while ((r = es->es_replay) != NULL){ while ((r = es->es_replay) != NULL){
if (r->r_xml) if (r->r_xml)
xml_free(r->r_xml); xml_free(r->r_xml);
@ -186,6 +201,54 @@ stream_get_xml(clicon_handle h,
return 0; return 0;
} }
/*! Check all stream subscription stop timers, set up new timer
* @param[in] fd No-op
* @param[in] arg Clicon handle
* @note format is given by event_reg_timeout callback function (fd not needed)
*/
int
stream_timer_setup(int fd,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
struct timeval now;
struct timeval t;
struct timeval t1 = {STREAM_TIMER_TIMEOUT_S, 0};
event_stream_t *es;
struct stream_subscription *ss;
struct stream_subscription *ss1;
/* Go thru callbacks and see if any have timed out, if so remove them
* Could also be done by a separate timer.
*/
gettimeofday(&now, NULL);
/* for all event streams, remove subscription if past stop time */
for (es=clicon_stream(h); es; es=es->es_next){
if ((ss = es->es_subscription) != NULL)
do {
if (timerisset(&ss->ss_stoptime) && timercmp(&ss->ss_stoptime, &now, <)){
ss1 = NEXTQ(struct stream_subscription *, ss);
if (stream_ss_rm(es, ss) < 0)
goto done;
ss = ss1;
}
else
ss = NEXTQ(struct stream_subscription *, ss);
} while (ss && ss != es->es_subscription);
}
/* Initiate new timer */
timeradd(&now, &t1, &t);
if (event_reg_timeout(t,
stream_timer_setup, /* this function */
h, /* clicon handle */
"stream timer setup") < 0)
goto done;
retval = 0;
done:
return retval;
}
#ifdef NYI #ifdef NYI
/*! Delete single notification event stream /*! Delete single notification event stream
* XXX notused * XXX notused
@ -197,21 +260,22 @@ stream_del()
} }
#endif #endif
/*! Add an event notification callback to a stream given a callback function /*! Add an event notification callback to a stream given a callback function
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] stream Name of stream * @param[in] stream Name of stream
* @param[in] xpath Filter selector - xpath * @param[in] xpath Filter selector - xpath
* @param[in] stoptime If set, * @param[in] startime If set, Make a replay
* @param[in] stoptime If set, dont continue past this time
* @param[in] fn Callback when event occurs * @param[in] fn Callback when event occurs
* @param[in] arg Argument to use with callback. Also handle when deleting * @param[in] arg Argument to use with callback. Also handle when deleting
* @retval 0 OK * @retval 0 OK
* @retval -1 Error, ie no such stream * @retval -1 Error, ie no such stream
*/ */
struct stream_subscription * struct stream_subscription *
stream_cb_add(clicon_handle h, stream_ss_add(clicon_handle h,
char *stream, char *stream,
char *xpath, char *xpath,
struct timeval *starttime,
struct timeval *stoptime, struct timeval *stoptime,
stream_fn_t fn, stream_fn_t fn,
void *arg) void *arg)
@ -235,14 +299,15 @@ stream_cb_add(clicon_handle h,
} }
if (stoptime) if (stoptime)
ss->ss_stoptime = *stoptime; ss->ss_stoptime = *stoptime;
if (starttime)
ss->ss_starttime = *starttime;
if (xpath && (ss->ss_xpath = strdup(xpath)) == NULL){ if (xpath && (ss->ss_xpath = strdup(xpath)) == NULL){
clicon_err(OE_CFG, errno, "strdup"); clicon_err(OE_CFG, errno, "strdup");
goto done; goto done;
} }
ss->ss_fn = fn; ss->ss_fn = fn;
ss->ss_arg = arg; ss->ss_arg = arg;
ss->ss_next = es->es_subscription; ADDQ(ss, es->es_subscription);
es->es_subscription = ss;
return ss; return ss;
done: done:
if (ss) if (ss)
@ -250,7 +315,7 @@ stream_cb_add(clicon_handle h,
return NULL; return NULL;
} }
/*! Delete event notification callback to a stream given a callback and arg /*! Delete event stream subscription to a stream given a callback and arg
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] stream Name of stream or NULL for all streams * @param[in] stream Name of stream or NULL for all streams
* @param[in] fn Callback when event occurs * @param[in] fn Callback when event occurs
@ -258,37 +323,50 @@ stream_cb_add(clicon_handle h,
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
*/ */
static int int
stream_cb_rm(event_stream_t *es, stream_ss_rm(event_stream_t *es,
struct stream_subscription *ssrm) struct stream_subscription *ss)
{ {
struct stream_subscription **ss_prev; DELQ(ss, es->es_subscription, struct stream_subscription *);
struct stream_subscription *ss;
struct stream_subscription *ss_next;
ss_prev = &es->es_subscription;
for (ss = *ss_prev; ss; ss = ss_next){
ss_next = ss->ss_next;
if (ss == ssrm){
*ss_prev = ss->ss_next;
if (ss->ss_stream) if (ss->ss_stream)
free(ss->ss_stream); free(ss->ss_stream);
if (ss->ss_xpath) if (ss->ss_xpath)
free(ss->ss_xpath); free(ss->ss_xpath);
free(ss); free(ss);
continue;
// break; if more > 1
}
ss_prev = &ss->ss_next;
}
return 0; return 0;
} }
/*! Remove stream subscription identified with fn and arg /*! Find stream callback given callback function and its (unique) argument
* @param[in] es Pointer to event stream
* @param[in] fn Stream callback
* @param[in] arg Argument - typically unique client handle
* @retval ss Event stream subscription structure
* @retval NULL Not found
*/
struct stream_subscription *
stream_ss_find(event_stream_t *es,
stream_fn_t fn,
void *arg)
{
struct stream_subscription *ss;
if ((ss = es->es_subscription) != NULL)
do {
if (fn == ss->ss_fn && arg == ss->ss_arg)
return ss;
ss = NEXTQ(struct stream_subscription *, ss);
} while (ss && ss != es->es_subscription);
return NULL;
}
/*! Remove stream subscription identified with fn and arg in all streams
* @param[in] h Clicon handle
* @param[in] stream Name of stream or NULL for all streams
* @param[in] fn Stream callback
* @param[in] arg Argument - typically unique client handle
*/ */
int int
stream_cb_delete(clicon_handle h, stream_ss_delete_all(clicon_handle h,
char *stream,
stream_fn_t fn, stream_fn_t fn,
void *arg) void *arg)
{ {
@ -296,39 +374,10 @@ stream_cb_delete(clicon_handle h,
event_stream_t *es; event_stream_t *es;
struct stream_subscription *ss; struct stream_subscription *ss;
for (es=clicon_stream(h); es; es=es->es_next){ for (es=clicon_stream(h); es; es=es->es_next)
if (stream && strcmp(stream, es->es_name)!=0) if ((ss = stream_ss_find(es, fn, arg)) != NULL)
continue; if (stream_ss_rm(es, ss) < 0)
for (ss = es->es_subscription; ss; ss = ss->ss_next){
if (fn == ss->ss_fn && arg == ss->ss_arg){
if (stream_cb_rm(es, ss) < 0)
goto done; goto done;
break; /* scb_rm removes element, must break - but only one match */
}
}
}
retval = 0;
done:
return retval;
}
/*! Check if any stream subscriptions have timed out, if so remove them
*/
int
stream_cb_timeout(event_stream_t *es,
struct timeval *tv)
{
int retval = -1;
struct stream_subscription *ss;
again: /* If we remove must start again */
for (ss = es->es_subscription; ss; ss = ss->ss_next){
if (timerisset(&ss->ss_stoptime) && timercmp(&ss->ss_stoptime, tv, <)){
if (stream_cb_rm(es, ss) < 0)
goto done;
goto again;
}
}
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -337,29 +386,37 @@ stream_cb_timeout(event_stream_t *es,
/*! Stream notify event and distribute to all registered callbacks /*! Stream notify event and distribute to all registered callbacks
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream * @param[in] stream Name of event stream. CLICON is predefined as LOG stream
* @param[in] tv Timestamp. Dont notify if subscription has stoptime<tv
* @param[in] event Notification as xml tree * @param[in] event Notification as xml tree
* @retval 0 OK * @retval 0 OK
* @retval -1 Error with clicon_err called * @retval -1 Error with clicon_err called
* @see stream_notify * @see stream_notify
* @see stream_ss_timeout where subscriptions are removed if stoptime<now
*/ */
int int
stream_notify_xml(clicon_handle h, stream_notify_xml(clicon_handle h,
event_stream_t *es, event_stream_t *es,
struct timeval *tv,
cxobj *xevent) cxobj *xevent)
{ {
int retval = -1; int retval = -1;
struct stream_subscription *ss; struct stream_subscription *ss;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
/* Go thru all global (handle) subscriptions and find matches */ /* Go thru all subscriptions and find matches */
for (ss = es->es_subscription; ss; ss = ss->ss_next){ if ((ss = es->es_subscription) != NULL)
/* Check if this is of interest, ie not larger than stoptime? */ do {
if (timerisset(&ss->ss_stoptime) && /* stoptime has passed */
timercmp(&ss->ss_stoptime, tv, <))
;
else /* xpath match */
if (ss->ss_xpath == NULL || if (ss->ss_xpath == NULL ||
strlen(ss->ss_xpath)==0 || strlen(ss->ss_xpath)==0 ||
xpath_first(xevent, "%s", ss->ss_xpath) != NULL) xpath_first(xevent, "%s", ss->ss_xpath) != NULL)
if ((*ss->ss_fn)(h, xevent, ss->ss_arg) < 0) if ((*ss->ss_fn)(h, xevent, ss->ss_arg) < 0)
goto done; goto done;
} ss = NEXTQ(struct stream_subscription *, ss);
} while (ss && ss != es->es_subscription);
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -411,18 +468,11 @@ stream_notify(clicon_handle h,
clicon_err(OE_YANG, 0, "No yang spec"); clicon_err(OE_YANG, 0, "No yang spec");
goto done; goto done;
} }
gettimeofday(&tv, NULL);
#if 1
/* Go thru callbacks and see if any have timed out, if so remove them
* Could also be done by a separate timer.
*/
if (stream_cb_timeout(es, &tv) < 0)
goto done;
#endif
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
gettimeofday(&tv, NULL);
if (time2str(tv, timestr, sizeof(timestr)) < 0){ if (time2str(tv, timestr, sizeof(timestr)) < 0){
clicon_err(OE_UNIX, errno, "time2str"); clicon_err(OE_UNIX, errno, "time2str");
goto done; goto done;
@ -432,7 +482,7 @@ stream_notify(clicon_handle h,
goto done; goto done;
if (xml_rootchild(xev, 0, &xev) < 0) if (xml_rootchild(xev, 0, &xev) < 0)
goto done; goto done;
if (stream_notify_xml(h, es, xev) < 0) if (stream_notify_xml(h, es, &tv, xev) < 0)
goto done; goto done;
if (es->es_replay_enabled){ if (es->es_replay_enabled){
if (stream_replay_add(es, &tv, xev) < 0) if (stream_replay_add(es, &tv, xev) < 0)
@ -451,8 +501,8 @@ stream_notify(clicon_handle h,
return retval; return retval;
} }
/*! Replay a stream by sending notification messages
/*! Replay a stream * @see RFC5277 Sec 2.1.1:
* Start Time: * Start Time:
A parameter, <startTime>, used to trigger the replay feature A parameter, <startTime>, used to trigger the replay feature
and indicate that the replay should start at the time and indicate that the replay should start at the time
@ -474,42 +524,39 @@ stream_notify(clicon_handle h,
compliant to [RFC3339]. Implementations must support time compliant to [RFC3339]. Implementations must support time
zones. zones.
* Should we fork??
* Assume no future sample timestamps. * Assume no future sample timestamps.
*/ */
int static int
stream_replay(clicon_handle h, stream_replay_notify(clicon_handle h,
char *stream, event_stream_t *es,
struct timeval *start, struct stream_subscription *ss)
struct timeval *stop)
{ {
int retval = -1; int retval = -1;
event_stream_t *es;
struct stream_replay *r; struct stream_replay *r;
if ((es = stream_find(h, stream)) == NULL){ /* If <startTime> is not present, this is not a replay */
clicon_err(OE_CFG, ENOENT, "Stream %s not found", stream); if (!timerisset(&ss->ss_starttime))
goto done; goto ok;
}
if (!es->es_replay_enabled) if (!es->es_replay_enabled)
goto ok; goto ok;
/* Get replay linked list */
if ((r = es->es_replay) == NULL) if ((r = es->es_replay) == NULL)
goto ok; goto ok;
/* First loop to skip until start */ /* First loop to skip until start */
if (start){
do { do {
if (timercmp(&r->r_tv, start, >=)) if (timercmp(&r->r_tv, &ss->ss_starttime, >=))
break; break;
r = NEXTQ(struct stream_replay *, r); r = NEXTQ(struct stream_replay *, r);
} while (r && r!=es->es_replay); } while (r && r!=es->es_replay);
if (r == NULL) if (r == NULL)
goto ok; goto ok; /* No samples to replay */
}
/* Then notify until stop */ /* Then notify until stop */
do { do {
if (stop && timercmp(&r->r_tv, stop, >)) if (timerisset(&ss->ss_stoptime) &&
timercmp(&r->r_tv, &ss->ss_stoptime, >))
break; break;
if (stream_notify_xml(h, es, r->r_xml) < 0) if ((*ss->ss_fn)(h, r->r_xml, ss->ss_arg) < 0)
goto done; goto done;
r = NEXTQ(struct stream_replay *, r); r = NEXTQ(struct stream_replay *, r);
} while (r && r!=es->es_replay); } while (r && r!=es->es_replay);
@ -545,6 +592,88 @@ stream_replay_add(event_stream_t *es,
return retval; return retval;
} }
/* tmp struct for timeout callback containing clicon handle,
* stream and subscription
*/
struct replay_arg{
clicon_handle ra_h;
char *ra_stream; /* Name of stream - malloced: free by cb */
stream_fn_t ra_fn; /* Stream callback */
void *ra_arg; /* Argument - typically unique client handle */
};
/*! Timeout callback for replaying stream
* @param[in] fd Ignore
* @param[in] arg tmp struct including clicon handle, stream and subscription
*/
static int
stream_replay_cb(int fd,
void *arg)
{
int retval = -1;
struct replay_arg *ra= (struct replay_arg*)arg;
event_stream_t *es;
struct stream_subscription *ss;
if (ra == NULL)
goto ok;
if (ra->ra_stream == NULL)
goto ok;
if ((es = stream_find(ra->ra_h, ra->ra_stream)) == NULL)
goto ok;
if ((ss = stream_ss_find(es, ra->ra_fn, ra->ra_arg)) == NULL)
goto ok;
if (stream_replay_notify(ra->ra_h, es, ss) < 0)
goto done;
ok:
retval = 0;
done:
if (ra){
if (ra->ra_stream)
free(ra->ra_stream);
free(ra);
}
return retval;
}
/*! Schedule stream replay to occur asap, eg "now"
*
* @param[in] h clicon handle
* @param[in] stream Name of stream
* @param[in] fn Stream callback
* @param[in] arg Argument - typically unique client handle
*/
int
stream_replay_trigger(clicon_handle h,
char *stream,
stream_fn_t fn,
void *arg)
{
int retval = -1;
struct timeval now;
struct replay_arg *ra;
if ((ra = malloc(sizeof(*ra))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(ra, 0, sizeof(*ra));
ra->ra_h = h;
if ((ra->ra_stream = strdup(stream)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
ra->ra_fn = fn;
ra->ra_arg = arg;
gettimeofday(&now, NULL);
if (event_reg_timeout(now, stream_replay_cb, ra,
"create-subscribtion stream replay") < 0)
goto done;
retval = 0;
done:
return retval;
}
#ifdef CLIXON_PUBLISH_STREAMS #ifdef CLIXON_PUBLISH_STREAMS
/* SSE support using Nginx Nchan. This code needs to be enabled at configure /* SSE support using Nginx Nchan. This code needs to be enabled at configure
* time using: --enable-publish configure option * time using: --enable-publish configure option
@ -649,8 +778,8 @@ url_post(char *url,
* Push via curl_post to publish stream event * Push via curl_post to publish stream event
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] event Event as XML * @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_cb_add * @param[in] arg Extra argument provided in stream_ss_add
* @see stream_cb_add * @see stream_ss_add
*/ */
static int static int
stream_publish_cb(clicon_handle h, stream_publish_cb(clicon_handle h,
@ -711,7 +840,7 @@ stream_publish(clicon_handle h,
#ifdef CLIXON_PUBLISH_STREAMS #ifdef CLIXON_PUBLISH_STREAMS
int retval = -1; int retval = -1;
if (stream_cb_add(h, stream, NULL, stream_publish_cb, (void*)stream) < 0) if (stream_ss_add(h, stream, NULL, stream_publish_cb, (void*)stream) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:

View file

@ -49,5 +49,8 @@ expecteof "$PROG" 0 '<x a="t"/>' '^<x a="t"/>$'
new "Single quotes for attributes (returns double quotes but at least parses right)" new "Single quotes for attributes (returns double quotes but at least parses right)"
expecteof "$PROG" 0 "<x a='t'/>" '^<x a="t"/>$' expecteof "$PROG" 0 "<x a='t'/>" '^<x a="t"/>$'
new "Mixed quotes"
expecteof "$PROG" 0 "<x a='t' b=\"q\"/>" '^<x a="t" b="q"/>$'
rm -rf $dir rm -rf $dir