From: Sebastian Hammer Date: Mon, 27 Nov 2006 14:35:15 +0000 (+0000) Subject: Various features added X-Git-Tag: before.append.child~124 X-Git-Url: http://jsfdemo.indexdata.com/?a=commitdiff_plain;h=bd48f097f6b7fca53d326fb84d373fd0962e745b;p=pazpar2-moved-to-github.git Various features added --- diff --git a/200.pz b/200.pz index 3eb55fb..800a208 100644 --- a/200.pz +++ b/200.pz @@ -41,7 +41,6 @@ target ns1.gbs.spb.ru:212/nrdf target ns1.gbs.spb.ru:212/plain target ns1.gbs.spb.ru:212/rdftest target opac.sbn.it:3950/nopac -target proxy2.lib.tpu.ru:2100/prd target publib.cbx.ru:210/serial target rs6000.nshpl.library.ns.ca:210/AVR target rs6000.nshpl.library.ns.ca:210/CBR @@ -182,9 +181,6 @@ target opac.sbn.it:2100/nopac target opac.shu.ac.uk:210/INNOPAC target opac.unifi.it:210/OPAC target opac.utmem.edu:210/INNOPAC -target proxy2.lib.tpu.ru:2100/book -target proxy2.lib.tpu.ru:2100/demo -target proxy2.lib.tpu.ru:2100/tpu target rebiun.crue.org:210/absysREBIUN target rs6000.nshpl.library.ns.ca:210/ECR target ruslan.ksu.ru:210/eres diff --git a/http.c b/http.c index 0a61044..c806c40 100644 --- a/http.c +++ b/http.c @@ -1,5 +1,5 @@ /* - * $Id: http.c,v 1.3 2006-11-26 05:15:43 quinn Exp $ + * $Id: http.c,v 1.4 2006-11-27 14:35:15 quinn Exp $ */ #include @@ -157,6 +157,28 @@ static int http_buf_read(struct http_buf **b, char *buf, int len) return rd; } +void static urldecode(char *i, char *o) +{ + while (*i) + { + if (*i == '+') + { + *(o++) = ' '; + i++; + } + else if (*i == '%') + { + i++; + sscanf(i, "%2hhx", o); + i += 2; + o++; + } + else + *(o++) = *(i++); + } + *o = '\0'; +} + void http_addheader(struct http_response *r, const char *name, const char *value) { struct http_channel *c = r->channel; @@ -284,6 +306,7 @@ struct http_request *http_parse_request(struct http_channel *c, struct http_buf a = nmem_malloc(c->nmem, sizeof(struct http_argument)); *(equal++) = '\0'; a->name = nmem_strdup(c->nmem, p2); + urldecode(equal, equal); a->value = nmem_strdup(c->nmem, equal); a->next = r->arguments; r->arguments = a; diff --git a/http_command.c b/http_command.c index 0affacd..518fd78 100644 --- a/http_command.c +++ b/http_command.c @@ -1,5 +1,7 @@ /* - * $Id: http_command.c,v 1.2 2006-11-24 20:29:07 quinn Exp $ + * stat->num_hits = s->total_hits; + * stat->num_records = s->total_records; + * $Id: http_command.c,v 1.3 2006-11-27 14:35:15 quinn Exp $ */ #include @@ -67,7 +69,7 @@ static void error(struct http_response *rs, char *code, char *msg, char *txt) rs->payload = nmem_strdup(c->nmem, tmp); } -int make_sessionid() +int make_sessionid() { struct timeval t; int res; @@ -100,6 +102,11 @@ static struct http_session *locate_session(struct http_request *rq, struct http_ return 0; } +static void cmd_exit(struct http_request *rq, struct http_response *rs) +{ + yaz_log(YLOG_WARN, "exit"); + exit(0); +} static void cmd_init(struct http_request *rq, struct http_response *rs) { @@ -204,7 +211,7 @@ static void cmd_show(struct http_request *rq, struct http_response *rs) struct record *p; wrbuf_puts(c->wrbuf, "\n"); - wrbuf_printf(c->wrbuf, "%s\n", rl[i]->merge_key); + wrbuf_printf(c->wrbuf, "%s\n", rl[i]->title); for (ccount = 1, p = rl[i]->next_cluster; p; p = p->next_cluster, ccount++) ; if (ccount > 1) @@ -220,6 +227,7 @@ static void cmd_search(struct http_request *rq, struct http_response *rs) { struct http_session *s = locate_session(rq, rs); char *query = http_argbyname(rq, "query"); + char *res; if (!s) return; @@ -228,13 +236,41 @@ static void cmd_search(struct http_request *rq, struct http_response *rs) error(rs, "417", "Must supply query", 0); return; } - search(s->psession, query); + res = search(s->psession, query); + if (res) + { + error(rs, "417", res, res); + return; + } rs->payload = "OK"; } static void cmd_stat(struct http_request *rq, struct http_response *rs) { + struct http_session *s = locate_session(rq, rs); + struct http_channel *c = rq->channel; + struct statistics stat; + + if (!s) + return; + + statistics(s->psession, &stat); + + wrbuf_rewind(c->wrbuf); + wrbuf_puts(c->wrbuf, ""); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_hits); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_records); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_no_connection); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_connecting); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_initializing); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_searching); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_presenting); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_idle); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_failed); + wrbuf_printf(c->wrbuf, "%d\n", stat.num_error); + wrbuf_puts(c->wrbuf, ""); + rs->payload = nmem_strdup(c->nmem, wrbuf_buf(c->wrbuf)); } static void cmd_load(struct http_request *rq, struct http_response *rs) @@ -266,6 +302,7 @@ struct { { "show", cmd_show }, { "search", cmd_search }, { "termlist", cmd_termlist }, + { "exit", cmd_exit }, {0,0} }; diff --git a/pazpar2.c b/pazpar2.c index 4bfb6e6..b6c02ee 100644 --- a/pazpar2.c +++ b/pazpar2.c @@ -1,4 +1,4 @@ -/* $Id: pazpar2.c,v 1.5 2006-11-26 05:15:43 quinn Exp $ */ +/* $Id: pazpar2.c,v 1.6 2006-11-27 14:35:15 quinn Exp $ */; #include #include @@ -17,6 +17,7 @@ #include #include #include +#include #include "pazpar2.h" #include "eventl.h" @@ -30,6 +31,8 @@ #define MAX_DATABASES 512 #define MAX_CHUNK 10 +static void target_destroy(IOCHAN i); + struct target { struct session *session; @@ -47,6 +50,7 @@ struct target int setno; int requestid; // ID of current outstanding request int diagnostic; + IOCHAN iochan; enum target_state { No_connection, @@ -57,6 +61,7 @@ struct target Presenting, Error, Idle, + Stopped, Failed } state; }; @@ -146,11 +151,7 @@ static void send_init(IOCHAN i) t->state = Initializing; } else - { - iochan_destroy(i); - t->state = Failed; - cs_close(t->link); - } + target_destroy(i); } static void send_search(IOCHAN i) @@ -158,14 +159,20 @@ static void send_search(IOCHAN i) struct target *t = iochan_getdata(i); struct session *s = t->session; Z_APDU *a = zget_APDU(t->odr_out, Z_APDU_searchRequest); - int ndb; + int ndb, cerror, cpos; char **databaselist; Z_Query *zquery; + struct ccl_rpn_node *cn; yaz_log(YLOG_DEBUG, "Sending search"); + + cn = ccl_find_str(global_parameters.ccl_filter, s->query, &cerror, &cpos); + if (!cn) + return; a->u.searchRequest->query = zquery = odr_malloc(t->odr_out, sizeof(Z_Query)); zquery->which = Z_Query_type_1; - zquery->u.type_1 = p_query_rpn(t->odr_out, PROTO_Z3950, s->query); + zquery->u.type_1 = ccl_rpn_query(t->odr_out, cn); + ccl_rpn_delete(cn); for (ndb = 0; *t->databases[ndb]; ndb++) ; @@ -185,9 +192,8 @@ static void send_search(IOCHAN i) } else { - iochan_destroy(i); - t->state = Failed; - cs_close(t->link); + target_destroy(i); + return; } odr_reset(t->odr_out); } @@ -210,6 +216,9 @@ static void send_present(IOCHAN i) a->u.presentRequest->resultSetId = "Default"; + a->u.presentRequest->preferredRecordSyntax = yaz_oidval_to_z3950oid(t->odr_out, + CLASS_RECSYN, VAL_USMARC); + if (send_apdu(t, a) >= 0) { iochan_setflags(i, EVENT_INPUT); @@ -217,9 +226,8 @@ static void send_present(IOCHAN i) } else { - iochan_destroy(i); - t->state = Failed; - cs_close(t->link); + target_destroy(i); + return; } odr_reset(t->odr_out); } @@ -236,11 +244,7 @@ static void do_initResponse(IOCHAN i, Z_APDU *a) t->state = Idle; } else - { - t->state = Failed; - iochan_destroy(i); - cs_close(t->link); - } + target_destroy(i); } static void do_searchResponse(IOCHAN i, Z_APDU *a) @@ -254,6 +258,7 @@ static void do_searchResponse(IOCHAN i, Z_APDU *a) { t->hits = *r->resultCount; t->state = Idle; + t->session->total_hits += t->hits; } else { /*"FAILED"*/ @@ -277,10 +282,15 @@ const char *find_field(const char *rec, const char *field) while (*line) { + const char *eol; + if (!strncmp(line, field, 3) && line[3] == ' ') return line; - while (*(line++) != '\n') - ; + while (*line && *line != '\n') + line++; + if (!(eol = strchr(line, '\n'))) + return 0; + line = eol + 1; } return 0; } @@ -306,6 +316,59 @@ const char *find_subfield(const char *field, char subfield) } // Extract 245 $a $b 100 $a +char *extract_title(struct session *s, const char *rec) +{ + const char *field, *subfield; + char *e, *ef; + unsigned char *obuf, *p; + + wrbuf_rewind(s->wrbuf); + + if (!(field = find_field(rec, "245"))) + return 0; + if (!(subfield = find_subfield(field, 'a'))) + return 0; + ef = index(subfield, '\n'); + if ((e = index(subfield, '\t')) && e < ef) + ef = e; + if (ef) + { + wrbuf_write(s->wrbuf, subfield, ef - subfield); + if ((subfield = find_subfield(field, 'b'))) + { + ef = index(subfield, '\n'); + if ((e = index(subfield, '\t')) && e < ef) + ef = e; + if (ef) + { + wrbuf_putc(s->wrbuf, ' '); + wrbuf_write(s->wrbuf, subfield, ef - subfield); + } + } + } + if ((field = find_field(rec, "100"))) + { + if ((subfield = find_subfield(field, 'a'))) + { + ef = index(subfield, '\n'); + if ((e = index(subfield, '\t')) && e < ef) + ef = e; + if (ef) + { + wrbuf_puts(s->wrbuf, ", by "); + wrbuf_write(s->wrbuf, subfield, ef - subfield); + } + } + } + wrbuf_putc(s->wrbuf, '\0'); + obuf = nmem_strdup(s->nmem, wrbuf_buf(s->wrbuf)); + for (p = obuf; *p; p++) + if (*p == '&' || *p == '<' || *p > 122 || *p < ' ') + *p = ' '; + return obuf; +} + +// Extract 245 $a $b 100 $a char *extract_mergekey(struct session *s, const char *rec) { const char *field, *subfield; @@ -502,6 +565,8 @@ static void extract_subject(struct session *s, const char *rec) int len; ef = index(subfield, '\n'); + if (!ef) + return; if ((e = index(subfield, '\t')) && e < ef) ef = e; while (ef > subfield && !isalpha(*(ef - 1)) && *(ef - 1) != ')') @@ -515,10 +580,32 @@ static void extract_subject(struct session *s, const char *rec) } } +static void pull_relevance_field(struct session *s, struct record *head, const char *rec, + char *field, int mult) +{ + const char *fb; + while ((fb = find_field(rec, field))) + { + char *ffield = strchr(fb, '\t'); + if (!ffield) + return; + char *eol = strchr(ffield, '\n'); + if (!eol) + return; + relevance_countwords(s->relevance, head, ffield, eol - ffield, mult); + rec = field + 1; // Crude way to cause a loop through repeating fields + } +} + static void pull_relevance_keys(struct session *s, struct record *head, struct record *rec) { relevance_newrec(s->relevance, head); - relevance_countwords(s->relevance, head, rec->merge_key, strlen(rec->merge_key)); + pull_relevance_field(s, head, rec->buf, "100", 2); + pull_relevance_field(s, head, rec->buf, "245", 4); + //pull_relevance_field(s, head, rec->buf, "530", 1); + pull_relevance_field(s, head, rec->buf, "630", 1); + pull_relevance_field(s, head, rec->buf, "650", 1); + pull_relevance_field(s, head, rec->buf, "700", 1); relevance_donerecord(s->relevance, head); } @@ -540,13 +627,14 @@ struct record *ingest_record(struct target *t, char *buf, int len) recbuf = wrbuf_buf(s->wrbuf); res = nmem_malloc(s->nmem, sizeof(struct record)); + res->buf = nmem_strdup(s->nmem, recbuf); - extract_subject(s, recbuf); + extract_subject(s, res->buf); - res->merge_key = extract_mergekey(s, recbuf); + res->title = extract_title(s, res->buf); + res->merge_key = extract_mergekey(s, res->buf); if (!res->merge_key) return 0; - res->buf = nmem_strdupn(s->nmem, recbuf, wrbuf_len(s->wrbuf)); res->target = t; res->next_cluster = 0; res->target_offset = -1; @@ -556,6 +644,8 @@ struct record *ingest_record(struct target *t, char *buf, int len) pull_relevance_keys(s, head, res); + s->total_records++; + return res; } @@ -643,9 +733,8 @@ static void handler(IOCHAN i, int event) else { yaz_log(YLOG_WARN|YLOG_ERRNO, "ERROR %s connect\n", t->hostport); - cs_close(t->link); - t->state = Failed; - iochan_destroy(i); + target_destroy(i); + return; } } @@ -657,9 +746,7 @@ static void handler(IOCHAN i, int event) if (getsockopt(cs_fileno(t->link), SOL_SOCKET, SO_ERROR, &errcode, &errlen) < 0 || errcode != 0) { - cs_close(t->link); - iochan_destroy(i); - t->state = Failed; + target_destroy(i); return; } else @@ -675,16 +762,12 @@ static void handler(IOCHAN i, int event) if (len < 0) { - cs_close(t->link); - iochan_destroy(i); - t->state = Failed; + target_destroy(i); return; } if (len == 0) { - cs_close(t->link); - iochan_destroy(i); - t->state = Failed; + target_destroy(i); return; } else if (len > 1) @@ -697,9 +780,7 @@ static void handler(IOCHAN i, int event) odr_setbuf(t->odr_in, t->ibuf, len, 0); if (!z_APDU(t->odr_in, &a, 0, 0)) { - cs_close(t->link); - iochan_destroy(i); - t->state = Failed; + target_destroy(i); return; } switch (a->which) @@ -715,16 +796,17 @@ static void handler(IOCHAN i, int event) break; default: yaz_log(YLOG_WARN, "Unexpected result from server"); - cs_close(t->link); - iochan_destroy(i); - t->state = Failed; + target_destroy(i); return; } // if (cs_more(t->link)) // iochan_setevent(i, EVENT_INPUT); } else // we throw away response and go to idle mode + { + yaz_log(YLOG_DEBUG, "Ignoring result to previous operation"); t->state = Idle; + } } /* if len==1 we do nothing but wait for more input */ } @@ -735,7 +817,7 @@ static void handler(IOCHAN i, int event) if (t->state == Idle) { - if (t->requestid != s->requestid) { + if (t->requestid != s->requestid && *s->query) { send_search(i); } else if (t->hits > 0 && t->records < global_parameters.toget && @@ -745,6 +827,32 @@ static void handler(IOCHAN i, int event) } } +static void target_destroy(IOCHAN i) +{ + struct target *t = iochan_getdata(i); + struct session *s = t->session; + struct target **p; + assert(iochan_getfun(i) == handler); + + yaz_log(YLOG_DEBUG, "Destroying target"); + + if (t->ibuf) + xfree(t->ibuf); + cs_close(t->link); + if (t->odr_in) + odr_destroy(t->odr_in); + if (t->odr_out) + odr_destroy(t->odr_out); + for (p = &s->targets; *p; p = &(*p)->next) + if (*p == t) + { + *p = (*p)->next; + break; + } + xfree(t); + iochan_destroy(i); +} + int load_targets(struct session *s, const char *fn) { FILE *f = fopen(fn, "r"); @@ -757,6 +865,10 @@ int load_targets(struct session *s, const char *fn) return -1; } + while (s->targets) + target_destroy(s->targets->iochan); + + s->query[0] = '\0'; target_p = &s->targets; while (fgets(line, 255, f)) { @@ -812,7 +924,8 @@ int load_targets(struct session *s, const char *fn) target->state = Failed; continue; } - new = iochan_create(cs_fileno(target->link), handler, 0); + target->iochan = new = iochan_create(cs_fileno(target->link), handler, 0); + assert(new); iochan_setdata(new, target); iochan_setevent(new, EVENT_EXCEPT); new->next = channel_list; @@ -823,7 +936,42 @@ int load_targets(struct session *s, const char *fn) return 0; } -void search(struct session *s, char *query) +static void pull_terms(NMEM nmem, struct ccl_rpn_node *n, char **termlist, int *num) +{ + switch (n->kind) + { + case CCL_RPN_AND: + case CCL_RPN_OR: + case CCL_RPN_NOT: + case CCL_RPN_PROX: + pull_terms(nmem, n->u.p[0], termlist, num); + pull_terms(nmem, n->u.p[1], termlist, num); + break; + case CCL_RPN_TERM: + termlist[(*num)++] = nmem_strdup(nmem, n->u.t.term); + break; + default: // NOOP + break; + } +} + +// Extract terms from query into null-terminated termlist +static int extract_terms(NMEM nmem, char *query, char **termlist) +{ + int error, pos; + struct ccl_rpn_node *n; + int num = 0; + + n = ccl_find_str(global_parameters.ccl_filter, query, &error, &pos); + if (!n) + return -1; + pull_terms(nmem, n, termlist, &num); + termlist[num] = 0; + ccl_rpn_delete(n); + return 0; +} + +char *search(struct session *s, char *query) { IOCHAN c; int live_channels = 0; @@ -860,12 +1008,18 @@ void search(struct session *s, char *query) } if (live_channels) { - const char *p[] = { query, 0 }; + char *p[512]; int maxrecs = live_channels * global_parameters.toget; s->termlist = termlist_create(s->nmem, maxrecs, 15); s->reclist = reclist_create(s->nmem, maxrecs); - s->relevance = relevance_create(s->nmem, p, maxrecs); + extract_terms(s->nmem, query, p); + s->relevance = relevance_create(s->nmem, (const char **) p, maxrecs); + s->total_records = s->total_hits = 0; } + else + return "NOTARGETS"; + + return 0; } struct session *new_session() @@ -874,6 +1028,8 @@ struct session *new_session() yaz_log(YLOG_DEBUG, "New pazpar2 session"); + session->total_hits = 0; + session->total_records = 0; session->termlist = 0; session->reclist = 0; session->requestid = -1; @@ -939,7 +1095,6 @@ struct record **show(struct session *s, int start, int *num) break; } recs[i] = r; - yaz_log(YLOG_DEBUG, "%d: %s%s", r->relevance, r->merge_key, r->next_cluster ? " (cluster)": ""); } return recs; } @@ -969,6 +1124,8 @@ void statistics(struct session *s, struct statistics *stat) default: break; } } + stat->num_hits = s->total_hits; + stat->num_records = s->total_records; stat->num_connections = i; } @@ -1019,7 +1176,7 @@ int main(int argc, char **argv) } if (!global_parameters.ccl_filter) - load_cclfile("default.bib"); + global_parameters.ccl_filter = load_cclfile("default.bib"); event_loop(&channel_list); diff --git a/pazpar2.h b/pazpar2.h index cb4e70f..b993327 100644 --- a/pazpar2.h +++ b/pazpar2.h @@ -12,6 +12,7 @@ struct record { int target_offset; char *buf; char *merge_key; + char *title; int relevance; int *term_frequency_vec; struct record *next_cluster; @@ -27,6 +28,8 @@ struct session { struct termlist *termlist; struct relevance *relevance; struct reclist *reclist; + int total_hits; + int total_records; yaz_marc_t yaz_marc; }; @@ -40,6 +43,8 @@ struct statistics { int num_idle; int num_failed; int num_error; + int num_hits; + int num_records; }; struct hitsbytarget { @@ -55,7 +60,7 @@ struct session *new_session(); void session_destroy(struct session *s); int load_targets(struct session *s, const char *fn); void statistics(struct session *s, struct statistics *stat); -void search(struct session *s, char *query); +char *search(struct session *s, char *query); struct record **show(struct session *s, int start, int *num); struct termlist_score **termlist(struct session *s, int *num); diff --git a/reclists.c b/reclists.c index 3a7ac5c..330affe 100644 --- a/reclists.c +++ b/reclists.c @@ -1,5 +1,5 @@ /* - * $Id: reclists.c,v 1.2 2006-11-26 05:15:43 quinn Exp $ + * $Id: reclists.c,v 1.3 2006-11-27 14:35:15 quinn Exp $ */ #include @@ -80,7 +80,6 @@ struct record *reclist_insert(struct reclist *l, struct record *record) if (!strcmp(record->merge_key, (*p)->record->merge_key)) { struct record *existing = (*p)->record; - yaz_log(YLOG_LOG, "Found a matching record: %s", record->merge_key); record->next_cluster = existing->next_cluster; existing->next_cluster = record; head = existing; @@ -89,7 +88,6 @@ struct record *reclist_insert(struct reclist *l, struct record *record) } if (!*p) // We made it to the end of the bucket without finding match { - yaz_log(YLOG_DEBUG, "Added a new record: %s", record->merge_key); struct reclist_bucket *new = nmem_malloc(l->nmem, sizeof(struct reclist_bucket)); new->record = record; diff --git a/relevance.c b/relevance.c index 221b709..c627a98 100644 --- a/relevance.c +++ b/relevance.c @@ -1,5 +1,5 @@ /* - * $Id: relevance.c,v 1.2 2006-11-26 05:15:43 quinn Exp $ + * $Id: relevance.c,v 1.3 2006-11-27 14:35:15 quinn Exp $ */ #include @@ -136,7 +136,7 @@ void relevance_newrec(struct relevance *r, struct record *rec) // FIXME. The definition of a word is crude here.. should support // some form of localization mechanism? void relevance_countwords(struct relevance *r, struct record *head, - const char *words, int len) + const char *words, int len, int multiplier) { while (len) { @@ -155,7 +155,7 @@ void relevance_countwords(struct relevance *r, struct record *head, { words += skipped; len -= skipped; - head->term_frequency_vec[res]++; + head->term_frequency_vec[res] += multiplier; } else { diff --git a/relevance.h b/relevance.h index 02d661e..38c3d9c 100644 --- a/relevance.h +++ b/relevance.h @@ -11,7 +11,7 @@ struct relevance; struct relevance *relevance_create(NMEM nmem, const char **terms, int numrecs); void relevance_newrec(struct relevance *r, struct record *rec); void relevance_countwords(struct relevance *r, struct record *rec, - const char *words, int len); + const char *words, int len, int multiplier); void relevance_donerecord(struct relevance *r, struct record *rec); void relevance_prepare_read(struct relevance *rel, struct reclist *rec); diff --git a/termlists.c b/termlists.c index fbf346e..f663a87 100644 --- a/termlists.c +++ b/termlists.c @@ -1,5 +1,5 @@ /* - * $Id: termlists.c,v 1.1 2006-11-24 20:29:07 quinn Exp $ + * $Id: termlists.c,v 1.2 2006-11-27 14:35:15 quinn Exp $ */ #include @@ -124,7 +124,6 @@ void termlist_insert(struct termlist *tl, const char *term) { if (!strcmp(term, (*p)->term.term)) { - yaz_log(YLOG_LOG, "Found a matching term: %s", term); (*p)->term.frequency++; update_highscore(tl, &((*p)->term)); break; @@ -132,7 +131,6 @@ void termlist_insert(struct termlist *tl, const char *term) } if (!*p) // We made it to the end of the bucket without finding match { - yaz_log(YLOG_DEBUG, "Added a new term: %s", term); struct termlist_bucket *new = nmem_malloc(tl->nmem, sizeof(struct termlist_bucket)); new->term.term = nmem_strdup(tl->nmem, term);