Refactor code out to stand-alone functions. This is to support one code path
[pazpar2-moved-to-github.git] / src / client.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2012 Index Data
3
4 Pazpar2 is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 /** \file client.c
21     \brief Z39.50 client
22 */
23
24 #if HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #if HAVE_SYS_TIME_H
31 #include <sys/time.h>
32 #endif
33 #if HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #include <signal.h>
37 #include <assert.h>
38
39 #include <yaz/marcdisp.h>
40 #include <yaz/comstack.h>
41 #include <yaz/tcpip.h>
42 #include <yaz/proto.h>
43 #include <yaz/readconf.h>
44 #include <yaz/pquery.h>
45 #include <yaz/otherinfo.h>
46 #include <yaz/yaz-util.h>
47 #include <yaz/nmem.h>
48 #include <yaz/query-charset.h>
49 #include <yaz/querytowrbuf.h>
50 #include <yaz/oid_db.h>
51 #include <yaz/diagbib1.h>
52 #include <yaz/snprintf.h>
53 #include <yaz/rpn2cql.h>
54 #include <yaz/rpn2solr.h>
55 #include <yaz/gettimeofday.h>
56
57 #define USE_TIMING 0
58 #if USE_TIMING
59 #include <yaz/timing.h>
60 #endif
61
62 #include "ppmutex.h"
63 #include "session.h"
64 #include "parameters.h"
65 #include "client.h"
66 #include "connection.h"
67 #include "settings.h"
68 #include "relevance.h"
69 #include "incref.h"
70
71 static YAZ_MUTEX g_mutex = 0;
72 static int no_clients = 0;
73 static int no_clients_total = 0;
74
75 static int client_use(int delta)
76 {
77     int clients;
78     if (!g_mutex)
79         yaz_mutex_create(&g_mutex);
80     yaz_mutex_enter(g_mutex);
81     no_clients += delta;
82     if (delta > 0)
83         no_clients_total += delta;
84     clients = no_clients;
85     yaz_mutex_leave(g_mutex);
86     yaz_log(YLOG_DEBUG, "%s clients=%d", delta == 0 ? "" : (delta > 0 ? "INC" : "DEC"), clients);
87     return clients;
88 }
89
90 int  clients_count(void) {
91     return client_use(0);
92 }
93
94 int  clients_count_total(void) {
95     int total = 0;
96     if (!g_mutex)
97         return 0;
98     yaz_mutex_enter(g_mutex);
99     total = no_clients_total;
100     yaz_mutex_leave(g_mutex);
101     return total;
102 }
103
104
105 /** \brief Represents client state for a connection to one search target */
106 struct client {
107     struct session_database *database;
108     struct connection *connection;
109     struct session *session;
110     char *pquery; // Current search
111     char *cqlquery; // used for SRU targets only
112     char *addinfo; // diagnostic info for most resent error
113     Odr_int hits;
114     int record_offset;
115     int filtered; // When using local:, this will count the number of filtered records.
116     int maxrecs;
117     int startrecs;
118     int diagnostic;
119     int preferred;
120     struct suggestions *suggestions;
121     enum client_state state;
122     struct show_raw *show_raw;
123     ZOOM_resultset resultset;
124     YAZ_MUTEX mutex;
125     int ref_count;
126     char *id;
127     facet_limits_t facet_limits;
128     int same_search;
129     char *sort_spec;
130 };
131
132 struct suggestions {
133     NMEM nmem;
134     int num;
135     char **misspelled;
136     char **suggest;
137     char *passthrough;
138 };
139
140 struct show_raw {
141     int active; // whether this request has been sent to the server
142     int position;
143     int binary;
144     char *syntax;
145     char *esn;
146     char *nativesyntax;
147     void (*error_handler)(void *data, const char *addinfo);
148     void (*record_handler)(void *data, const char *buf, size_t sz);
149     void *data;
150     struct show_raw *next;
151 };
152
153 static const char *client_states[] = {
154     "Client_Connecting",
155     "Client_Idle",
156     "Client_Working",
157     "Client_Error",
158     "Client_Failed",
159     "Client_Disconnected"
160 };
161
162 const char *client_get_state_str(struct client *cl)
163 {
164     return client_states[cl->state];
165 }
166
167 enum client_state client_get_state(struct client *cl)
168 {
169     return cl->state;
170 }
171
172 void client_set_state_nb(struct client *cl, enum client_state st)
173 {
174     cl->state = st;
175 }
176
177 void client_set_state(struct client *cl, enum client_state st)
178 {
179     int was_active = 0;
180     if (client_is_active(cl))
181         was_active = 1;
182     cl->state = st;
183     /* If client is going from being active to inactive and all clients
184        are now idle we fire a watch for the session . The assumption is
185        that session is not mutex locked if client is already active */
186     if (was_active && !client_is_active(cl) && cl->session)
187     {
188
189         int no_active = session_active_clients(cl->session);
190         yaz_log(YLOG_DEBUG, "%s: releasing watches on zero active: %d",
191                 client_get_id(cl), no_active);
192         if (no_active == 0) {
193             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
194             session_alert_watch(cl->session, SESSION_WATCH_BYTARGET);
195             session_alert_watch(cl->session, SESSION_WATCH_TERMLIST);
196             session_alert_watch(cl->session, SESSION_WATCH_SHOW_PREF);
197         }
198     }
199 }
200
201 static void client_show_raw_error(struct client *cl, const char *addinfo);
202
203 struct connection *client_get_connection(struct client *cl)
204 {
205     return cl->connection;
206 }
207
208 struct session_database *client_get_database(struct client *cl)
209 {
210     return cl->database;
211 }
212
213 struct session *client_get_session(struct client *cl)
214 {
215     return cl->session;
216 }
217
218 const char *client_get_pquery(struct client *cl)
219 {
220     return cl->pquery;
221 }
222
223 static void client_send_raw_present(struct client *cl);
224 static int nativesyntax_to_type(const char *s, char *type, ZOOM_record rec);
225
226 static void client_show_immediate(
227     ZOOM_resultset resultset, struct session_database *sdb, int position,
228     void *data,
229     void (*error_handler)(void *data, const char *addinfo),
230     void (*record_handler)(void *data, const char *buf, size_t sz),
231     int binary,
232     const char *nativesyntax)
233 {
234     ZOOM_record rec = 0;
235     char type[80];
236     const char *buf;
237     int len;
238
239     if (!resultset)
240     {
241         error_handler(data, "no resultset");
242         return;
243     }
244     rec = ZOOM_resultset_record_immediate(resultset, position-1);
245     if (!rec)
246     {
247         error_handler(data, "no record");
248         return;
249     }
250     nativesyntax_to_type(nativesyntax, type, rec);
251     buf = ZOOM_record_get(rec, type, &len);
252     if (!buf)
253     {
254         error_handler(data, "no record");
255         return;
256     }
257     record_handler(data, buf, len);
258 }
259
260
261 int client_show_raw_begin(struct client *cl, int position,
262                           const char *syntax, const char *esn,
263                           void *data,
264                           void (*error_handler)(void *data, const char *addinfo),
265                           void (*record_handler)(void *data, const char *buf,
266                                                  size_t sz),
267                           int binary,
268                           const char *nativesyntax)
269 {
270     if (!nativesyntax)
271     {
272         if (binary)
273             nativesyntax = "raw";
274         else
275         {
276             struct session_database *sdb = client_get_database(cl);
277             nativesyntax = session_setting_oneval(sdb, PZ_NATIVESYNTAX);
278         }
279     }
280
281     if (syntax == 0 && esn == 0)
282         client_show_immediate(cl->resultset, client_get_database(cl),
283                               position, data,
284                               error_handler, record_handler,
285                               binary, nativesyntax);
286     else
287     {
288         struct show_raw *rr, **rrp;
289
290         if (!cl->connection)
291             return -1;
292
293
294         rr = xmalloc(sizeof(*rr));
295         rr->position = position;
296         rr->active = 0;
297         rr->data = data;
298         rr->error_handler = error_handler;
299         rr->record_handler = record_handler;
300         rr->binary = binary;
301         if (syntax)
302             rr->syntax = xstrdup(syntax);
303         else
304             rr->syntax = 0;
305         if (esn)
306             rr->esn = xstrdup(esn);
307         else
308             rr->esn = 0;
309
310         assert(nativesyntax);
311         rr->nativesyntax = xstrdup(nativesyntax);
312
313         rr->next = 0;
314
315         for (rrp = &cl->show_raw; *rrp; rrp = &(*rrp)->next)
316             ;
317         *rrp = rr;
318
319         if (cl->state == Client_Failed)
320         {
321             client_show_raw_error(cl, "client failed");
322         }
323         else if (cl->state == Client_Disconnected)
324         {
325             client_show_raw_error(cl, "client disconnected");
326         }
327         else
328         {
329             client_send_raw_present(cl);
330         }
331     }
332     return 0;
333 }
334
335 static void client_show_raw_delete(struct show_raw *r)
336 {
337     xfree(r->syntax);
338     xfree(r->esn);
339     xfree(r->nativesyntax);
340     xfree(r);
341 }
342
343 void client_show_raw_remove(struct client *cl, void *data)
344 {
345     struct show_raw *rr = data;
346     struct show_raw **rrp = &cl->show_raw;
347     while (*rrp != rr)
348         rrp = &(*rrp)->next;
349     if (*rrp)
350     {
351         *rrp = rr->next;
352         client_show_raw_delete(rr);
353     }
354 }
355
356 void client_show_raw_dequeue(struct client *cl)
357 {
358     struct show_raw *rr = cl->show_raw;
359
360     cl->show_raw = rr->next;
361     client_show_raw_delete(rr);
362 }
363
364 static void client_show_raw_error(struct client *cl, const char *addinfo)
365 {
366     while (cl->show_raw)
367     {
368         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
369         client_show_raw_dequeue(cl);
370     }
371 }
372
373 static void client_send_raw_present(struct client *cl)
374 {
375     struct session_database *sdb = client_get_database(cl);
376     struct connection *co = client_get_connection(cl);
377     ZOOM_resultset set = cl->resultset;
378
379     int offset = cl->show_raw->position;
380     const char *syntax = 0;
381     const char *elements = 0;
382
383     assert(cl->show_raw);
384     assert(set);
385
386     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
387             client_get_id(cl), 1, offset);
388
389     if (cl->show_raw->syntax)
390         syntax = cl->show_raw->syntax;
391     else
392         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
393     ZOOM_resultset_option_set(set, "preferredRecordSyntax", syntax);
394
395     if (cl->show_raw->esn)
396         elements = cl->show_raw->esn;
397     else
398         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
399     if (elements && *elements)
400         ZOOM_resultset_option_set(set, "elementSetName", elements);
401
402     ZOOM_resultset_records(set, 0, offset-1, 1);
403     cl->show_raw->active = 1;
404
405     connection_continue(co);
406 }
407
408 static int nativesyntax_to_type(const char *s, char *type,
409                                 ZOOM_record rec)
410 {
411     if (s && *s)
412     {
413         if (!strncmp(s, "iso2709", 7))
414         {
415             const char *cp = strchr(s, ';');
416             yaz_snprintf(type, 80, "xml; charset=%s", cp ? cp+1 : "marc-8s");
417         }
418         else if (!strncmp(s, "txml", 4))
419         {
420             const char *cp = strchr(s, ';');
421             yaz_snprintf(type, 80, "txml; charset=%s", cp ? cp+1 : "marc-8s");
422         }
423         else /* pass verbatim to ZOOM - including "xml" */
424             strcpy(type, s);
425         return 0;
426     }
427     else  /* attempt to deduce structure */
428     {
429         const char *syntax = ZOOM_record_get(rec, "syntax", NULL);
430         if (syntax)
431         {
432             if (!strcmp(syntax, "XML"))
433             {
434                 strcpy(type, "xml");
435                 return 0;
436             }
437             else if (!strcmp(syntax, "USmarc") || !strcmp(syntax, "MARC21"))
438             {
439                 strcpy(type, "xml; charset=marc8-s");
440                 return 0;
441             }
442             else return -1;
443         }
444         else return -1;
445     }
446 }
447
448 /**
449  * TODO Consider thread safety!!!
450  *
451  */
452 void client_report_facets(struct client *cl, ZOOM_resultset rs)
453 {
454     struct session_database *sdb = client_get_database(cl);
455     ZOOM_facet_field *facets = ZOOM_resultset_facets(rs);
456
457     if (sdb && facets)
458     {
459         struct session *se = client_get_session(cl);
460         int facet_num = ZOOM_resultset_facets_size(rs);
461         struct setting *s;
462
463         for (s = sdb->settings[PZ_FACETMAP]; s; s = s->next)
464         {
465             const char *p = strchr(s->name + 3, ':');
466             if (p && p[1] && s->value && s->value[0])
467             {
468                 int facet_idx;
469                 p++; /* p now holds logical facet name */
470                 for (facet_idx = 0; facet_idx < facet_num; facet_idx++)
471                 {
472                     const char *native_name =
473                         ZOOM_facet_field_name(facets[facet_idx]);
474                     if (native_name && !strcmp(s->value, native_name))
475                     {
476                         size_t term_idx;
477                         size_t term_num =
478                             ZOOM_facet_field_term_count(facets[facet_idx]);
479                         for (term_idx = 0; term_idx < term_num; term_idx++ )
480                         {
481                             int freq;
482                             const char *term =
483                                 ZOOM_facet_field_get_term(facets[facet_idx],
484                                                           term_idx, &freq);
485                             if (term)
486                                 add_facet(se, p, term, freq);
487                         }
488                         break;
489                     }
490                 }
491             }
492         }
493     }
494 }
495
496 static void ingest_raw_record(struct client *cl, ZOOM_record rec)
497 {
498     const char *buf;
499     int len;
500     char type[80];
501
502     nativesyntax_to_type(cl->show_raw->nativesyntax, type, rec);
503     buf = ZOOM_record_get(rec, type, &len);
504     cl->show_raw->record_handler(cl->show_raw->data,  buf, len);
505     client_show_raw_dequeue(cl);
506 }
507
508 void client_check_preferred_watch(struct client *cl)
509 {
510     struct session *se = cl->session;
511     yaz_log(YLOG_DEBUG, "client_check_preferred_watch: %s ", client_get_id(cl));
512     if (se)
513     {
514         client_unlock(cl);
515         /* TODO possible threading issue. Session can have been destroyed */
516         if (session_is_preferred_clients_ready(se)) {
517             session_alert_watch(se, SESSION_WATCH_SHOW_PREF);
518         }
519         else
520             yaz_log(YLOG_DEBUG, "client_check_preferred_watch: Still locked on preferred targets.");
521
522         client_lock(cl);
523     }
524     else
525         yaz_log(YLOG_WARN, "client_check_preferred_watch: %s. No session!", client_get_id(cl));
526
527 }
528
529 struct suggestions* client_suggestions_create(const char* suggestions_string);
530 static void client_suggestions_destroy(struct client *cl);
531
532 void client_search_response(struct client *cl)
533 {
534     struct connection *co = cl->connection;
535     ZOOM_connection link = connection_get_link(co);
536     ZOOM_resultset resultset = cl->resultset;
537
538     const char *error, *addinfo = 0;
539
540     if (ZOOM_connection_error(link, &error, &addinfo))
541     {
542         cl->hits = 0;
543         client_set_state(cl, Client_Error);
544         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
545                 error, addinfo, client_get_id(cl));
546     }
547     else
548     {
549         client_report_facets(cl, resultset);
550         cl->record_offset = cl->startrecs;
551         cl->hits = ZOOM_resultset_size(resultset);
552         yaz_log(YLOG_DEBUG, "client_search_response: hits " ODR_INT_PRINTF, cl->hits);
553         if (cl->suggestions)
554             client_suggestions_destroy(cl);
555         cl->suggestions = client_suggestions_create(ZOOM_resultset_option_get(resultset, "suggestions"));
556     }
557 }
558
559 void client_got_records(struct client *cl)
560 {
561     struct session *se = cl->session;
562     if (se)
563     {
564         if (reclist_get_num_records(se->reclist) > 0)
565         {
566             client_unlock(cl);
567             session_alert_watch(se, SESSION_WATCH_SHOW);
568             session_alert_watch(se, SESSION_WATCH_BYTARGET);
569             session_alert_watch(se, SESSION_WATCH_TERMLIST);
570             session_alert_watch(se, SESSION_WATCH_RECORD);
571             client_lock(cl);
572         }
573     }
574 }
575
576 static void client_record_ingest(struct client *cl)
577 {
578     const char *msg, *addinfo;
579     ZOOM_record rec = 0;
580     ZOOM_resultset resultset = cl->resultset;
581     int offset = cl->record_offset;
582     if ((rec = ZOOM_resultset_record_immediate(resultset, offset)))
583     {
584         cl->record_offset++;
585         if (cl->session == 0) {
586             /* no operation */
587         }
588         else if (ZOOM_record_error(rec, &msg, &addinfo, 0))
589         {
590             yaz_log(YLOG_WARN, "Record error %s (%s): %s (rec #%d)",
591                     msg, addinfo, client_get_id(cl), cl->record_offset);
592         }
593         else
594         {
595             struct session_database *sdb = client_get_database(cl);
596             NMEM nmem = nmem_create();
597             const char *xmlrec;
598             char type[80];
599
600             const char *s = session_setting_oneval(sdb, PZ_NATIVESYNTAX);
601             if (nativesyntax_to_type(s, type, rec))
602                 yaz_log(YLOG_WARN, "Failed to determine record type");
603             xmlrec = ZOOM_record_get(rec, type, NULL);
604             if (!xmlrec)
605                 yaz_log(YLOG_WARN, "ZOOM_record_get failed from %s",
606                         client_get_id(cl));
607             else
608             {
609                 /* OK = 0, -1 = failure, -2 = Filtered */
610                 int rc = ingest_record(cl, xmlrec, cl->record_offset, nmem);
611                 if (rc == -1)
612                     yaz_log(YLOG_WARN, "Failed to ingest from %s", client_get_id(cl));
613                 if (rc == -2)
614                     cl->filtered += 1;
615             }
616             nmem_destroy(nmem);
617         }
618     }
619     else
620     {
621         yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d", offset);
622     }
623 }
624
625 void client_record_response(struct client *cl)
626 {
627     struct connection *co = cl->connection;
628     ZOOM_connection link = connection_get_link(co);
629     ZOOM_resultset resultset = cl->resultset;
630     const char *error, *addinfo;
631
632     if (ZOOM_connection_error(link, &error, &addinfo))
633     {
634         client_set_state(cl, Client_Error);
635         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
636             error, addinfo, client_get_id(cl));
637     }
638     else
639     {
640         if (cl->show_raw && cl->show_raw->active)
641         {
642             ZOOM_record rec = 0;
643             if ((rec = ZOOM_resultset_record_immediate(
644                      resultset, cl->show_raw->position-1)))
645             {
646                 cl->show_raw->active = 0;
647                 ingest_raw_record(cl, rec);
648             }
649             else
650             {
651                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
652                         cl->show_raw->position-1);
653             }
654         }
655         else
656         {
657             client_record_ingest(cl);
658         }
659     }
660 }
661
662 int client_reingest(struct client *cl)
663 {
664     int i = cl->startrecs;
665     int to = cl->record_offset;
666     cl->filtered = 0;
667
668     cl->record_offset = i;
669     for (; i < to; i++)
670         client_record_ingest(cl);
671     return 0;
672 }
673
674 static void client_set_facets_request(struct client *cl, ZOOM_connection link)
675 {
676     struct session_database *sdb = client_get_database(cl);
677
678     WRBUF w = wrbuf_alloc();
679
680     struct setting *s;
681
682     for (s = sdb->settings[PZ_FACETMAP]; s; s = s->next)
683     {
684         const char *p = strchr(s->name + 3, ':');
685         if (!p)
686         {
687             yaz_log(YLOG_WARN, "Malformed facetmap name: %s", s->name);
688         }
689         else if (s->value && s->value[0])
690         {
691             wrbuf_puts(w, "@attr 1=");
692             yaz_encode_pqf_term(w, s->value, strlen(s->value));
693             if (s->next)
694                 wrbuf_puts(w, ",");
695         }
696     }
697     yaz_log(YLOG_DEBUG, "using facets str: %s", wrbuf_cstr(w));
698     ZOOM_connection_option_set(link, "facets",
699                                wrbuf_len(w) ? wrbuf_cstr(w) : 0);
700     wrbuf_destroy(w);
701 }
702
703 int client_has_facet(struct client *cl, const char *name)
704 {
705     struct session_database *sdb = client_get_database(cl);
706     struct setting *s;
707
708     for (s = sdb->settings[PZ_FACETMAP]; s; s = s->next)
709     {
710         const char *p = strchr(s->name + 3, ':');
711         if (p && !strcmp(name, p + 1))
712             return 1;
713     }
714     return 0;
715 }
716
717 static const char *get_strategy_plus_sort(struct client *l, const char *field)
718 {
719     struct session_database *sdb = client_get_database(l);
720     struct setting *s;
721
722     const char *strategy_plus_sort = 0;
723
724     for (s = sdb->settings[PZ_SORTMAP]; s; s = s->next)
725     {
726         char *p = strchr(s->name + 3, ':');
727         if (!p)
728         {
729             yaz_log(YLOG_WARN, "Malformed sortmap name: %s", s->name);
730             continue;
731         }
732         p++;
733         if (!strcmp(p, field))
734         {
735             strategy_plus_sort = s->value;
736             break;
737         }
738     }
739     return strategy_plus_sort;
740 }
741
742 int client_parse_init(struct client *cl, int same_search)
743 {
744     cl->same_search = same_search;
745     return 0;
746 }
747
748 int client_parse_range(struct client *cl, const char *startrecs, const char *maxrecs)
749 {
750     if (maxrecs && atoi(maxrecs) != cl->maxrecs)
751     {
752         cl->same_search = 0;
753         cl->maxrecs = atoi(maxrecs);
754     }
755
756     if (startrecs && atoi(startrecs) != cl->startrecs)
757     {
758         cl->same_search = 0;
759         cl->startrecs = atoi(startrecs);
760     }
761     return 0;
762 }
763
764 int client_start_search(struct client *cl)
765 {
766     struct session_database *sdb = client_get_database(cl);
767     struct connection *co = client_get_connection(cl);
768     ZOOM_connection link = connection_get_link(co);
769     struct session *se = client_get_session(cl);
770     ZOOM_resultset rs;
771     const char *opt_piggyback   = session_setting_oneval(sdb, PZ_PIGGYBACK);
772     const char *opt_queryenc    = session_setting_oneval(sdb, PZ_QUERYENCODING);
773     const char *opt_elements    = session_setting_oneval(sdb, PZ_ELEMENTS);
774     const char *opt_requestsyn  = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
775     const char *opt_maxrecs     = session_setting_oneval(sdb, PZ_MAXRECS);
776     const char *opt_sru         = session_setting_oneval(sdb, PZ_SRU);
777     const char *opt_sort        = session_setting_oneval(sdb, PZ_SORT);
778     const char *opt_preferred   = session_setting_oneval(sdb, PZ_PREFERRED);
779     const char *extra_args      = session_setting_oneval(sdb, PZ_EXTRA_ARGS);
780     const char *opt_present_chunk = session_setting_oneval(sdb, PZ_PRESENT_CHUNK);
781     ZOOM_query query;
782     char maxrecs_str[24], startrecs_str[24], present_chunk_str[24];
783     struct timeval tval;
784     int present_chunk = 20; // Default chunk size
785     int rc_prep_connection;
786
787
788     yaz_gettimeofday(&tval);
789     tval.tv_sec += 5;
790
791     if (opt_present_chunk && strcmp(opt_present_chunk,"")) {
792         present_chunk = atoi(opt_present_chunk);
793         yaz_log(YLOG_DEBUG, "Present chunk set to %d", present_chunk);
794     }
795     assert(link);
796     rc_prep_connection =
797         client_prep_connection(cl, se->service->z3950_operation_timeout,
798                                se->service->z3950_session_timeout,
799                                se->service->server->iochan_man,
800                                &tval);
801     /* Nothing has changed and we already have a result */
802     if (cl->same_search == 1 && rc_prep_connection == 2)
803     {
804         session_log(se, YLOG_LOG, "client %s REUSE result", client_get_id(cl));
805         return client_reingest(cl);
806     }
807     else if (!rc_prep_connection)
808     {
809         session_log(se, YLOG_LOG, "client %s FAILED to search: No connection.", client_get_id(cl));
810         return -1;
811     }
812     session_log(se, YLOG_LOG, "client %s NEW search", client_get_id(cl));
813
814     cl->diagnostic = 0;
815     cl->filtered = 0;
816
817     if (extra_args && *extra_args)
818         ZOOM_connection_option_set(link, "extraArgs", extra_args);
819
820     if (opt_preferred) {
821         cl->preferred = atoi(opt_preferred);
822         if (cl->preferred)
823             yaz_log(YLOG_LOG, "Target %s has preferred status: %d",
824                     client_get_id(cl), cl->preferred);
825     }
826
827     if (*opt_piggyback)
828         ZOOM_connection_option_set(link, "piggyback", opt_piggyback);
829     else
830         ZOOM_connection_option_set(link, "piggyback", "1");
831     if (*opt_queryenc)
832         ZOOM_connection_option_set(link, "rpnCharset", opt_queryenc);
833     if (*opt_sru && *opt_elements)
834         ZOOM_connection_option_set(link, "schema", opt_elements);
835     else if (*opt_elements)
836         ZOOM_connection_option_set(link, "elementSetName", opt_elements);
837     if (*opt_requestsyn)
838         ZOOM_connection_option_set(link, "preferredRecordSyntax", opt_requestsyn);
839
840     if (opt_maxrecs && *opt_maxrecs)
841     {
842         cl->maxrecs = atoi(opt_maxrecs);
843     }
844
845     /* convert back to string representation used in ZOOM API */
846     sprintf(maxrecs_str, "%d", cl->maxrecs);
847     ZOOM_connection_option_set(link, "count", maxrecs_str);
848
849     /* A present_chunk less than 1 will disable chunking. */
850     if (present_chunk > 0 && cl->maxrecs > present_chunk) {
851         sprintf(present_chunk_str, "%d", present_chunk);
852         ZOOM_connection_option_set(link, "presentChunk", present_chunk_str);
853         yaz_log(YLOG_DEBUG, "Present chunk set to %s", present_chunk_str);
854     }
855     else {
856         ZOOM_connection_option_set(link, "presentChunk", maxrecs_str);
857         yaz_log(YLOG_DEBUG, "Present chunk set to %s (maxrecs)", maxrecs_str);
858     }
859     sprintf(startrecs_str, "%d", cl->startrecs);
860     ZOOM_connection_option_set(link, "start", startrecs_str);
861
862     /* TODO Verify does it break something for CQL targets(non-SOLR) ? */
863     /* facets definition is in PQF */
864     client_set_facets_request(cl, link);
865
866     query = ZOOM_query_create();
867     if (cl->cqlquery)
868     {
869         yaz_log(YLOG_LOG, "Client %s: Search CQL: %s", client_get_id(cl), cl->cqlquery);
870         ZOOM_query_cql(query, cl->cqlquery);
871         if (*opt_sort)
872             ZOOM_query_sortby(query, opt_sort);
873     }
874     else
875     {
876         yaz_log(YLOG_LOG, "Client %s: Search PQF: %s", client_get_id(cl), cl->pquery);
877
878         ZOOM_query_prefix(query, cl->pquery);
879     }
880     // TODO check for re-ingest / re-search
881
882     yaz_log(YLOG_DEBUG,"Client %s: Starting search", client_get_id(cl));
883     client_set_state(cl, Client_Working);
884     cl->hits = 0;
885     cl->record_offset = 0;
886     rs = ZOOM_connection_search(link, query);
887     ZOOM_query_destroy(query);
888     ZOOM_resultset_destroy(cl->resultset);
889     cl->resultset = rs;
890     connection_continue(co);
891     return 0;
892 }
893
894 struct client *client_create(const char *id)
895 {
896     struct client *cl = xmalloc(sizeof(*cl));
897     cl->maxrecs = 100;
898     cl->startrecs = 0;
899     cl->pquery = 0;
900     cl->cqlquery = 0;
901     cl->addinfo = 0;
902     cl->database = 0;
903     cl->connection = 0;
904     cl->session = 0;
905     cl->hits = 0;
906     cl->record_offset = 0;
907     cl->filtered = 0;
908     cl->diagnostic = 0;
909     cl->state = Client_Disconnected;
910     cl->show_raw = 0;
911     cl->resultset = 0;
912     cl->suggestions = 0;
913     cl->mutex = 0;
914     pazpar2_mutex_create(&cl->mutex, "client");
915     cl->preferred = 0;
916     cl->ref_count = 1;
917     cl->facet_limits = 0;
918     assert(id);
919     cl->id = xstrdup(id);
920     client_use(1);
921
922     return cl;
923 }
924
925 void client_lock(struct client *c)
926 {
927     yaz_mutex_enter(c->mutex);
928 }
929
930 void client_unlock(struct client *c)
931 {
932     yaz_mutex_leave(c->mutex);
933 }
934
935 void client_incref(struct client *c)
936 {
937     pazpar2_incref(&c->ref_count, c->mutex);
938     yaz_log(YLOG_DEBUG, "client_incref c=%p %s cnt=%d",
939             c, client_get_id(c), c->ref_count);
940 }
941
942 int client_destroy(struct client *c)
943 {
944     if (c)
945     {
946         yaz_log(YLOG_DEBUG, "client_destroy c=%p %s cnt=%d",
947                 c, client_get_id(c), c->ref_count);
948         if (!pazpar2_decref(&c->ref_count, c->mutex))
949         {
950             xfree(c->pquery);
951             c->pquery = 0;
952             xfree(c->cqlquery);
953             c->cqlquery = 0;
954             xfree(c->addinfo);
955             c->addinfo = 0;
956             xfree(c->id);
957             assert(!c->connection);
958             facet_limits_destroy(c->facet_limits);
959
960             if (c->resultset)
961             {
962                 ZOOM_resultset_destroy(c->resultset);
963             }
964             yaz_mutex_destroy(&c->mutex);
965             xfree(c);
966             client_use(-1);
967             return 1;
968         }
969     }
970     return 0;
971 }
972
973 void client_set_connection(struct client *cl, struct connection *con)
974 {
975     if (cl->resultset)
976         ZOOM_resultset_release(cl->resultset);
977     if (con)
978     {
979         assert(cl->connection == 0);
980         cl->connection = con;
981         client_incref(cl);
982     }
983     else
984     {
985         cl->connection = con;
986         client_destroy(cl);
987     }
988 }
989
990 void client_disconnect(struct client *cl)
991 {
992     if (cl->state != Client_Idle)
993         client_set_state(cl, Client_Disconnected);
994     client_set_connection(cl, 0);
995 }
996
997
998 // Initialize CCL map for a target
999 static CCL_bibset prepare_cclmap(struct client *cl, CCL_bibset base_bibset)
1000 {
1001     struct session_database *sdb = client_get_database(cl);
1002     struct setting *s;
1003     CCL_bibset res;
1004
1005     if (!sdb->settings)
1006         return 0;
1007     if (base_bibset)
1008         res = ccl_qual_dup(base_bibset);
1009     else
1010         res = ccl_qual_mk();
1011     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
1012     {
1013         const char *addinfo = 0;
1014         char *p = strchr(s->name + 3, ':');
1015         if (!p)
1016         {
1017             WRBUF w = wrbuf_alloc();
1018             wrbuf_printf(w, "Malformed cclmap. name=%s", s->name);
1019             yaz_log(YLOG_WARN, "%s: %s", client_get_id(cl), wrbuf_cstr(w));
1020             client_set_diagnostic(cl, ZOOM_ERROR_CCL_CONFIG, wrbuf_cstr(w));
1021             client_set_state_nb(cl, Client_Error);
1022             ccl_qual_rm(&res);
1023             wrbuf_destroy(w);
1024             return 0;
1025         }
1026         p++;
1027         if (ccl_qual_fitem2(res, s->value, p, &addinfo))
1028         {
1029             WRBUF w = wrbuf_alloc();
1030
1031             wrbuf_printf(w, "Malformed cclmap. name=%s: value=%s (%s)",
1032                          s->name, p, addinfo);
1033             yaz_log(YLOG_WARN, "%s: %s", client_get_id(cl), wrbuf_cstr(w));
1034             client_set_diagnostic(cl, ZOOM_ERROR_CCL_CONFIG, wrbuf_cstr(w));
1035             client_set_state_nb(cl, Client_Error);
1036             ccl_qual_rm(&res);
1037             wrbuf_destroy(w);
1038             return 0;
1039         }
1040     }
1041     return res;
1042 }
1043
1044 // returns a xmalloced CQL query corresponding to the pquery in client
1045 static char *make_cqlquery(struct client *cl, Z_RPNQuery *zquery)
1046 {
1047     cql_transform_t cqlt = cql_transform_create();
1048     char *r = 0;
1049     WRBUF wrb = wrbuf_alloc();
1050     int status;
1051
1052     if ((status = cql_transform_rpn2cql_wrbuf(cqlt, wrb, zquery)))
1053     {
1054         yaz_log(YLOG_WARN, "Failed to generate CQL query, code=%d", status);
1055     }
1056     else
1057     {
1058         r = xstrdup(wrbuf_cstr(wrb));
1059     }
1060     wrbuf_destroy(wrb);
1061     cql_transform_close(cqlt);
1062     return r;
1063 }
1064
1065 // returns a xmalloced SOLR query corresponding to the pquery in client
1066 // TODO Could prob. be merge with the similar make_cqlquery
1067 static char *make_solrquery(struct client *cl, Z_RPNQuery *zquery)
1068 {
1069     solr_transform_t sqlt = solr_transform_create();
1070     char *r = 0;
1071     WRBUF wrb = wrbuf_alloc();
1072     int status;
1073
1074     if ((status = solr_transform_rpn2solr_wrbuf(sqlt, wrb, zquery)))
1075     {
1076         yaz_log(YLOG_WARN, "Failed to generate SOLR query, code=%d", status);
1077     }
1078     else
1079     {
1080         r = xstrdup(wrbuf_cstr(wrb));
1081     }
1082     wrbuf_destroy(wrb);
1083     solr_transform_close(sqlt);
1084     return r;
1085 }
1086
1087 const char *client_get_facet_limit_local(struct client *cl,
1088                                          struct session_database *sdb,
1089                                          int *l,
1090                                          NMEM nmem, int *num, char ***values)
1091 {
1092     const char *name = 0;
1093     const char *value = 0;
1094     for (; (name = facet_limits_get(cl->facet_limits, *l, &value)); (*l)++)
1095     {
1096         struct setting *s = 0;
1097
1098         for (s = sdb->settings[PZ_LIMITMAP]; s; s = s->next)
1099         {
1100             const char *p = strchr(s->name + 3, ':');
1101             if (p && !strcmp(p + 1, name) && s->value &&
1102                 !strncmp(s->value, "local:", 6))
1103             {
1104                 const char *cp = s->value + 6;
1105                 while (*cp == ' ')
1106                     cp++;
1107
1108                 nmem_strsplit_escape2(nmem, "|", value, values,
1109                                       num, 1, '\\', 1);
1110                 (*l)++;
1111                 return *cp ? cp : name;
1112             }
1113         }
1114     }
1115     return 0;
1116 }
1117
1118 static int apply_limit(struct session_database *sdb,
1119                        facet_limits_t facet_limits,
1120                        WRBUF w_pqf, CCL_bibset ccl_map)
1121 {
1122     int ret = 0;
1123     int i = 0;
1124     const char *name;
1125     const char *value;
1126
1127     NMEM nmem_tmp = nmem_create();
1128     for (i = 0; (name = facet_limits_get(facet_limits, i, &value)); i++)
1129     {
1130         struct setting *s = 0;
1131         nmem_reset(nmem_tmp);
1132         for (s = sdb->settings[PZ_LIMITMAP]; s; s = s->next)
1133         {
1134             const char *p = strchr(s->name + 3, ':');
1135             if (p && !strcmp(p + 1, name) && s->value)
1136             {
1137                 char **values = 0;
1138                 int i, num = 0;
1139                 nmem_strsplit_escape2(nmem_tmp, "|", value, &values,
1140                                       &num, 1, '\\', 1);
1141
1142                 if (!strncmp(s->value, "rpn:", 4))
1143                 {
1144                     const char *pqf = s->value + 4;
1145
1146                     wrbuf_puts(w_pqf, "@and ");
1147                     wrbuf_puts(w_pqf, pqf);
1148                     wrbuf_puts(w_pqf, " ");
1149                     for (i = 0; i < num; i++)
1150                     {
1151                         if (i < num - 1)
1152                             wrbuf_puts(w_pqf, "@or ");
1153                         yaz_encode_pqf_term(w_pqf, values[i],
1154                                             strlen(values[i]));
1155                     }
1156                 }
1157                 else if (!strncmp(s->value, "ccl:", 4))
1158                 {
1159                     const char *ccl = s->value + 4;
1160                     WRBUF ccl_w = wrbuf_alloc();
1161                     for (i = 0; i < num; i++)
1162                     {
1163                         int cerror, cpos;
1164                         struct ccl_rpn_node *cn;
1165
1166                         wrbuf_rewind(ccl_w);
1167                         wrbuf_puts(ccl_w, ccl);
1168                         wrbuf_puts(ccl_w, "=\"");
1169                         wrbuf_puts(ccl_w, values[i]);
1170                         wrbuf_puts(ccl_w, "\"");
1171
1172                         cn = ccl_find_str(ccl_map, wrbuf_cstr(ccl_w),
1173                                           &cerror, &cpos);
1174                         if (cn)
1175                         {
1176                             if (i == 0)
1177                                 wrbuf_printf(w_pqf, "@and ");
1178
1179                             /* or multiple values.. could be bad if last CCL
1180                                parse fails, but this is unlikely to happen */
1181                             if (i < num - 1)
1182                                 wrbuf_printf(w_pqf, "@or ");
1183                             ccl_pquery(w_pqf, cn);
1184                             ccl_rpn_delete(cn);
1185                         }
1186                     }
1187                     wrbuf_destroy(ccl_w);
1188                 }
1189                 else if (!strncmp(s->value, "local:", 6)) {
1190                     /* no operation */
1191                 }
1192                 else
1193                 {
1194                     yaz_log(YLOG_WARN, "Target %s: Bad limitmap '%s'",
1195                             sdb->database->id, s->value);
1196                     ret = -1; /* bad limitmap */
1197                 }
1198                 break;
1199             }
1200         }
1201         if (!s)
1202         {
1203             yaz_log(YLOG_WARN, "Target %s: limit %s used, but no limitmap defined",
1204                     (sdb->database ? sdb->database->id : "<no id>"), name);
1205         }
1206     }
1207     nmem_destroy(nmem_tmp);
1208     return ret;
1209 }
1210
1211 // Parse the query given the settings specific to this client
1212 // return 0 if query is OK but different from before
1213 // return 1 if query is OK but same as before
1214 // return -1 on query error
1215 // return -2 on limit error
1216 int client_parse_query(struct client *cl, const char *query,
1217                        facet_limits_t facet_limits,
1218                        CCL_bibset bibset)
1219 {
1220     struct session *se = client_get_session(cl);
1221     struct session_database *sdb = client_get_database(cl);
1222     struct ccl_rpn_node *cn;
1223     int cerror, cpos;
1224     ODR odr_out;
1225     CCL_bibset ccl_map = prepare_cclmap(cl, bibset);
1226     const char *sru = session_setting_oneval(sdb, PZ_SRU);
1227     const char *pqf_prefix = session_setting_oneval(sdb, PZ_PQF_PREFIX);
1228     const char *pqf_strftime = session_setting_oneval(sdb, PZ_PQF_STRFTIME);
1229     const char *query_syntax = session_setting_oneval(sdb, PZ_QUERY_SYNTAX);
1230     WRBUF w_ccl, w_pqf;
1231     int ret_value = 1;
1232     Z_RPNQuery *zquery;
1233
1234     if (!ccl_map)
1235         return -3;
1236
1237     w_ccl = wrbuf_alloc();
1238     wrbuf_puts(w_ccl, query);
1239
1240     w_pqf = wrbuf_alloc();
1241     if (*pqf_prefix)
1242     {
1243         wrbuf_puts(w_pqf, pqf_prefix);
1244         wrbuf_puts(w_pqf, " ");
1245     }
1246
1247     if (apply_limit(sdb, facet_limits, w_pqf, ccl_map))
1248     {
1249         ccl_qual_rm(&ccl_map);
1250         return -2;
1251     }
1252
1253     facet_limits_destroy(cl->facet_limits);
1254     cl->facet_limits = facet_limits_dup(facet_limits);
1255
1256     yaz_log(YLOG_LOG, "Client %s: CCL query: %s", client_get_id(cl), wrbuf_cstr(w_ccl));
1257     cn = ccl_find_str(ccl_map, wrbuf_cstr(w_ccl), &cerror, &cpos);
1258     ccl_qual_rm(&ccl_map);
1259     if (!cn)
1260     {
1261         client_set_state(cl, Client_Error);
1262         session_log(se, YLOG_WARN, "Client %s: Failed to parse CCL query '%s'",
1263                     client_get_id(cl),
1264                     wrbuf_cstr(w_ccl));
1265         wrbuf_destroy(w_ccl);
1266         wrbuf_destroy(w_pqf);
1267         return -1;
1268     }
1269     wrbuf_destroy(w_ccl);
1270
1271     if (!pqf_strftime || !*pqf_strftime)
1272         ccl_pquery(w_pqf, cn);
1273     else
1274     {
1275         time_t cur_time = time(0);
1276         struct tm *tm =  localtime(&cur_time);
1277         char tmp_str[300];
1278         const char *cp = tmp_str;
1279
1280         /* see man strftime(3) for things .. In particular %% gets converted
1281          to %.. And That's our original query .. */
1282         strftime(tmp_str, sizeof(tmp_str)-1, pqf_strftime, tm);
1283         for (; *cp; cp++)
1284         {
1285             if (cp[0] == '%')
1286                 ccl_pquery(w_pqf, cn);
1287             else
1288                 wrbuf_putc(w_pqf, cp[0]);
1289         }
1290     }
1291
1292     /* Compares query and limit with old one. If different we need to research */
1293     if (!cl->pquery || strcmp(cl->pquery, wrbuf_cstr(w_pqf)))
1294     {
1295         xfree(cl->pquery);
1296         cl->pquery = xstrdup(wrbuf_cstr(w_pqf));
1297         ret_value = 0;
1298     }
1299     wrbuf_destroy(w_pqf);
1300
1301     xfree(cl->cqlquery);
1302     cl->cqlquery = 0;
1303
1304     odr_out = odr_createmem(ODR_ENCODE);
1305     zquery = p_query_rpn(odr_out, cl->pquery);
1306     if (!zquery)
1307     {
1308
1309         session_log(se, YLOG_WARN, "Invalid PQF query for Client %s: %s",
1310                     client_get_id(cl), cl->pquery);
1311         ret_value = -1;
1312     }
1313     else
1314     {
1315         session_log(se, YLOG_LOG, "PQF for Client %s: %s",
1316                     client_get_id(cl), cl->pquery);
1317
1318         /* Support for PQF on SRU targets. */
1319         if (strcmp(query_syntax, "pqf") != 0 && *sru)
1320         {
1321             if (!strcmp(sru, "solr"))
1322                 cl->cqlquery = make_solrquery(cl, zquery);
1323             else
1324                 cl->cqlquery = make_cqlquery(cl, zquery);
1325             if (!cl->cqlquery)
1326                 ret_value = -1;
1327         }
1328     }
1329     odr_destroy(odr_out);
1330
1331     /* TODO FIX Not thread safe */
1332     if (!se->relevance)
1333     {
1334         // Initialize relevance structure with query terms
1335         se->relevance = relevance_create_ccl(se->service->charsets, cn,
1336                                              se->service->rank_cluster,
1337                                              se->service->rank_follow,
1338                                              se->service->rank_lead,
1339                                              se->service->rank_length);
1340     }
1341     ccl_rpn_delete(cn);
1342     return ret_value;
1343 }
1344
1345 int client_parse_sort(struct client *cl, struct reclist_sortparms *sp)
1346 {
1347     struct session *se = client_get_session(cl);
1348     if (sp)
1349     {   /* first entry is current sorting ! */
1350         const char *sort_strategy_and_spec =
1351             get_strategy_plus_sort(cl, se->sorted_results->name);
1352
1353         int increasing = se->sorted_results->increasing;
1354         // int type = se->sorted_results->type;
1355         if (sort_strategy_and_spec && strlen(sort_strategy_and_spec) < 40)
1356         {
1357             char spec[50], *p;
1358             strcpy(spec, sort_strategy_and_spec);
1359             p = strchr(spec, ':');
1360             if (p)
1361             {
1362                 *p++ = '\0'; /* cut the string in two */
1363                 while (*p == ' ')
1364                     p++;
1365                 if (increasing)
1366                     strcat(p, " <");
1367                 else
1368                     strcat(p, " >");
1369                 yaz_log(YLOG_LOG, "Client %s: applying sorting %s %s", client_get_id(cl), spec, p);
1370                 if (!cl->sort_spec || strcmp(cl->sort_spec, spec))
1371                     cl->same_search = 0;
1372                 cl->sort_spec = nmem_strdup(se->nmem, spec);
1373             }
1374         }
1375     }
1376     return 0;
1377 }
1378
1379 void client_set_session(struct client *cl, struct session *se)
1380 {
1381     cl->session = se;
1382 }
1383
1384 int client_is_active(struct client *cl)
1385 {
1386     if (cl->connection && (cl->state == Client_Connecting ||
1387                            cl->state == Client_Working))
1388         return 1;
1389     return 0;
1390 }
1391
1392 int client_is_active_preferred(struct client *cl)
1393 {
1394     /* only count if this is a preferred target. */
1395     if (!cl->preferred)
1396         return 0;
1397     /* TODO No sure this the condition that Seb wants */
1398     if (cl->connection && (cl->state == Client_Connecting ||
1399                            cl->state == Client_Working))
1400         return 1;
1401     return 0;
1402 }
1403
1404 Odr_int client_get_hits(struct client *cl)
1405 {
1406     return cl->hits;
1407 }
1408
1409 Odr_int client_get_approximation(struct client *cl)
1410 {
1411     if (cl->record_offset > 0) {
1412         Odr_int approx = ((10 * cl->hits * (cl->record_offset - cl->filtered)) / cl->record_offset + 5) /10;
1413         yaz_log(YLOG_DEBUG, "%s: Approx: %lld * %d / %d = %lld ", client_get_id(cl), cl->hits, cl->record_offset - cl->filtered, cl->record_offset, approx);
1414         return approx;
1415     }
1416     return cl->hits;
1417 }
1418
1419 int client_get_num_records(struct client *cl)
1420 {
1421     return cl->record_offset;
1422 }
1423
1424 int client_get_num_records_filtered(struct client *cl)
1425 {
1426     return cl->filtered;
1427 }
1428
1429 void client_set_diagnostic(struct client *cl, int diagnostic,
1430                            const char *addinfo)
1431 {
1432     cl->diagnostic = diagnostic;
1433     xfree(cl->addinfo);
1434     cl->addinfo = 0;
1435     if (addinfo)
1436         cl->addinfo = xstrdup(addinfo);
1437 }
1438
1439 int client_get_diagnostic(struct client *cl, const char **addinfo)
1440 {
1441     if (addinfo)
1442         *addinfo = cl->addinfo;
1443     return cl->diagnostic;
1444 }
1445
1446 const char * client_get_suggestions_xml(struct client *cl, WRBUF wrbuf)
1447 {
1448     /* int idx; */
1449     struct suggestions *suggestions = cl->suggestions;
1450
1451     if (!suggestions) {
1452         //yaz_log(YLOG_DEBUG, "No suggestions found");
1453         return "";
1454     }
1455     if (suggestions->passthrough) {
1456         yaz_log(YLOG_DEBUG, "Passthrough Suggestions: \n%s\n", suggestions->passthrough);
1457         return suggestions->passthrough;
1458     }
1459     if (suggestions->num == 0) {
1460         return "";
1461     }
1462     /*
1463     for (idx = 0; idx < suggestions->num; idx++) {
1464         wrbuf_printf(wrbuf, "<suggest term=\"%s\"", suggestions->suggest[idx]);
1465         if (suggestions->misspelled[idx] && suggestions->misspelled[idx]) {
1466             wrbuf_puts(wrbuf, suggestions->misspelled[idx]);
1467             wrbuf_puts(wrbuf, "</suggest>\n");
1468         }
1469         else
1470             wrbuf_puts(wrbuf, "/>\n");
1471     }
1472     */
1473     return wrbuf_cstr(wrbuf);
1474 }
1475
1476
1477 void client_set_database(struct client *cl, struct session_database *db)
1478 {
1479     cl->database = db;
1480 }
1481
1482 const char *client_get_id(struct client *cl)
1483 {
1484     return cl->id;
1485 }
1486
1487 int client_get_maxrecs(struct client *cl)
1488 {
1489     return cl->maxrecs;
1490 }
1491
1492 void client_set_preferred(struct client *cl, int v)
1493 {
1494     cl->preferred = v;
1495 }
1496
1497
1498 struct suggestions* client_suggestions_create(const char* suggestions_string)
1499 {
1500     int i;
1501     NMEM nmem;
1502     struct suggestions *suggestions;
1503     if (suggestions_string == 0 || suggestions_string[0] == 0 )
1504         return 0;
1505     nmem = nmem_create();
1506     suggestions = nmem_malloc(nmem, sizeof(*suggestions));
1507     yaz_log(YLOG_DEBUG, "client target suggestions: %s.", suggestions_string);
1508
1509     suggestions->nmem = nmem;
1510     suggestions->num = 0;
1511     suggestions->misspelled = 0;
1512     suggestions->suggest = 0;
1513     suggestions->passthrough = nmem_strdup_null(nmem, suggestions_string);
1514
1515     if (suggestions_string)
1516         nmem_strsplit_escape2(suggestions->nmem, "\n", suggestions_string, &suggestions->suggest,
1517                               &suggestions->num, 1, '\\', 0);
1518     /* Set up misspelled array */
1519     suggestions->misspelled = (char **) nmem_malloc(nmem, suggestions->num * sizeof(**suggestions->misspelled));
1520     /* replace = with \0 .. for each item */
1521     for (i = 0; i < suggestions->num; i++)
1522     {
1523         char *cp = strchr(suggestions->suggest[i], '=');
1524         if (cp) {
1525             *cp = '\0';
1526             suggestions->misspelled[i] = cp+1;
1527         }
1528     }
1529     return suggestions;
1530 }
1531
1532 static void client_suggestions_destroy(struct client *cl)
1533 {
1534     NMEM nmem = cl->suggestions->nmem;
1535     cl->suggestions = 0;
1536     nmem_destroy(nmem);
1537 }
1538
1539 int client_test_sort_order(struct client *cl, struct reclist_sortparms *sp)
1540 {
1541     //TODO implement correctly.
1542     return 1;
1543 }
1544 /*
1545  * Local variables:
1546  * c-basic-offset: 4
1547  * c-file-style: "Stroustrup"
1548  * indent-tabs-mode: nil
1549  * End:
1550  * vim: shiftwidth=4 tabstop=8 expandtab
1551  */
1552