Updated source file headers with new year and no CVS Id.
[pazpar2-moved-to-github.git] / src / client.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2008 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 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/time.h>
28 #include <unistd.h>
29 #include <sys/socket.h>
30 #include <netdb.h>
31 #include <signal.h>
32 #include <ctype.h>
33 #include <assert.h>
34
35 #include <yaz/marcdisp.h>
36 #include <yaz/comstack.h>
37 #include <yaz/tcpip.h>
38 #include <yaz/proto.h>
39 #include <yaz/readconf.h>
40 #include <yaz/pquery.h>
41 #include <yaz/otherinfo.h>
42 #include <yaz/yaz-util.h>
43 #include <yaz/nmem.h>
44 #include <yaz/query-charset.h>
45 #include <yaz/querytowrbuf.h>
46 #include <yaz/oid_db.h>
47 #include <yaz/diagbib1.h>
48
49 #if HAVE_CONFIG_H
50 #include "cconfig.h"
51 #endif
52
53 #define USE_TIMING 0
54 #if USE_TIMING
55 #include <yaz/timing.h>
56 #endif
57
58 #include <netinet/in.h>
59
60 #include "pazpar2.h"
61
62 #include "client.h"
63 #include "connection.h"
64 #include "settings.h"
65
66 /** \brief Represents client state for a connection to one search target */
67 struct client {
68     struct session_database *database;
69     struct connection *connection;
70     struct session *session;
71     char *pquery; // Current search
72     int hits;
73     int records;
74     int setno;
75     int requestid;            // ID of current outstanding request
76     int diagnostic;
77     enum client_state state;
78     struct show_raw *show_raw;
79     struct client *next;     // next client in session or next in free list
80 };
81
82 struct show_raw {
83     int active; // whether this request has been sent to the server
84     int position;
85     int binary;
86     char *syntax;
87     char *esn;
88     void (*error_handler)(void *data, const char *addinfo);
89     void (*record_handler)(void *data, const char *buf, size_t sz);
90     void *data;
91     struct show_raw *next;
92 };
93
94 static const char *client_states[] = {
95     "Client_Connecting",
96     "Client_Connected",
97     "Client_Idle",
98     "Client_Initializing",
99     "Client_Searching",
100     "Client_Presenting",
101     "Client_Error",
102     "Client_Failed",
103     "Client_Disconnected",
104     "Client_Stopped",
105     "Client_Continue"
106 };
107
108 static struct client *client_freelist = 0;
109
110 static int send_apdu(struct client *c, Z_APDU *a)
111 {
112     struct session_database *sdb = client_get_database(c);
113     const char *apdulog = session_setting_oneval(sdb, PZ_APDULOG);
114     if (apdulog && *apdulog && *apdulog != '0')
115     {
116         ODR p = odr_createmem(ODR_PRINT);
117         yaz_log(YLOG_LOG, "send APDU %s", client_get_url(c));
118
119         odr_setprint(p, yaz_log_file());
120         z_APDU(p, &a, 0, 0);
121         odr_setprint(p, stderr);
122         odr_destroy(p);
123     }
124     return connection_send_apdu(client_get_connection(c), a);
125 }
126
127
128 const char *client_get_state_str(struct client *cl)
129 {
130     return client_states[cl->state];
131 }
132
133 enum client_state client_get_state(struct client *cl)
134 {
135     return cl->state;
136 }
137
138 void client_set_state(struct client *cl, enum client_state st)
139 {
140     cl->state = st;
141     if (cl->session)
142     {
143         int no_active = session_active_clients(cl->session);
144         if (no_active == 0)
145             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
146     }
147 }
148
149 static void client_show_raw_error(struct client *cl, const char *addinfo);
150
151 // Close connection and set state to error
152 void client_fatal(struct client *cl)
153 {
154     client_show_raw_error(cl, "client connection failure");
155     yaz_log(YLOG_WARN, "Fatal error from %s", client_get_url(cl));
156     connection_destroy(cl->connection);
157     client_set_state(cl, Client_Error);
158 }
159
160
161 static int diag_to_wrbuf(Z_DiagRec **pp, int num, WRBUF w)
162 {
163     int code = 0;
164     int i;
165     for (i = 0; i<num; i++)
166     {
167         Z_DiagRec *p = pp[i];
168         if (i)
169             wrbuf_puts(w, "; ");
170         if (p->which != Z_DiagRec_defaultFormat)
171         {
172             wrbuf_puts(w, "? Not in default format");
173         }
174         else
175         {
176             Z_DefaultDiagFormat *r = p->u.defaultFormat;
177             
178             if (!r->diagnosticSetId)
179                 wrbuf_puts(w, "? Missing diagset");
180             else
181             {
182                 oid_class oclass;
183                 char diag_name_buf[OID_STR_MAX];
184                 const char *diag_name = 0;
185                 diag_name = yaz_oid_to_string_buf
186                     (r->diagnosticSetId, &oclass, diag_name_buf);
187                 wrbuf_puts(w, diag_name);
188             }
189             if (!code)
190                 code = *r->condition;
191             wrbuf_printf(w, " %d %s", *r->condition,
192                          diagbib1_str(*r->condition));
193             switch (r->which)
194             {
195             case Z_DefaultDiagFormat_v2Addinfo:
196                 wrbuf_printf(w, " -- v2 addinfo '%s'", r->u.v2Addinfo);
197                 break;
198             case Z_DefaultDiagFormat_v3Addinfo:
199                 wrbuf_printf(w, " -- v3 addinfo '%s'", r->u.v3Addinfo);
200                 break;
201             }
202         }
203     }
204     return code;
205 }
206
207
208
209 struct connection *client_get_connection(struct client *cl)
210 {
211     return cl->connection;
212 }
213
214 struct session_database *client_get_database(struct client *cl)
215 {
216     return cl->database;
217 }
218
219 struct session *client_get_session(struct client *cl)
220 {
221     return cl->session;
222 }
223
224 const char *client_get_pquery(struct client *cl)
225 {
226     return cl->pquery;
227 }
228
229 void client_set_requestid(struct client *cl, int id)
230 {
231     cl->requestid = id;
232 }
233
234 int client_show_raw_begin(struct client *cl, int position,
235                           const char *syntax, const char *esn,
236                           void *data,
237                           void (*error_handler)(void *data, const char *addinfo),
238                           void (*record_handler)(void *data, const char *buf,
239                                                  size_t sz),
240                           void **data2,
241                           int binary)
242 {
243     struct show_raw *rr, **rrp;
244     if (!cl->connection)
245     {   /* the client has no connection */
246         return -1;
247     }
248     rr = xmalloc(sizeof(*rr));
249     *data2 = rr;
250     rr->position = position;
251     rr->active = 0;
252     rr->data = data;
253     rr->error_handler = error_handler;
254     rr->record_handler = record_handler;
255     rr->binary = binary;
256     if (syntax)
257         rr->syntax = xstrdup(syntax);
258     else
259         rr->syntax = 0;
260     if (esn)
261         rr->esn = xstrdup(esn);
262     else
263         rr->esn = 0;
264     rr->next = 0;
265     
266     for (rrp = &cl->show_raw; *rrp; rrp = &(*rrp)->next)
267         ;
268     *rrp = rr;
269     
270     if (cl->state == Client_Failed)
271     {
272         client_show_raw_error(cl, "client failed");
273     }
274     else if (cl->state == Client_Disconnected)
275     {
276         client_show_raw_error(cl, "client disconnected");
277     }
278     else
279     {
280         client_continue(cl);
281     }
282     return 0;
283 }
284
285 void client_show_raw_remove(struct client *cl, void *data)
286 {
287     struct show_raw *rr = data;
288     struct show_raw **rrp = &cl->show_raw;
289     while (*rrp != rr)
290         rrp = &(*rrp)->next;
291     if (*rrp)
292     {
293         *rrp = rr->next;
294         xfree(rr);
295     }
296 }
297
298 void client_show_raw_dequeue(struct client *cl)
299 {
300     struct show_raw *rr = cl->show_raw;
301
302     cl->show_raw = rr->next;
303     xfree(rr);
304 }
305
306 static void client_show_raw_error(struct client *cl, const char *addinfo)
307 {
308     while (cl->show_raw)
309     {
310         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
311         client_show_raw_dequeue(cl);
312     }
313 }
314
315 static void client_show_raw_cancel(struct client *cl)
316 {
317     while (cl->show_raw)
318     {
319         cl->show_raw->error_handler(cl->show_raw->data, "cancel");
320         client_show_raw_dequeue(cl);
321     }
322 }
323
324 static void client_present_syntax(Z_APDU *a, const char *syntax)
325 {
326     // empty string for syntax OMITS preferredRecordSyntax (OPTIONAL)
327     if (syntax && *syntax)
328         a->u.presentRequest->preferredRecordSyntax =
329             yaz_string_to_oid_odr(yaz_oid_std(),
330                                   CLASS_RECSYN, syntax,
331                                   global_parameters.odr_out);
332 }
333
334 static void client_present_elements(Z_APDU *a, const char *elements)
335 {
336     if (elements && *elements)  // element set is optional
337     {
338         Z_ElementSetNames *elementSetNames =
339             odr_malloc(global_parameters.odr_out, sizeof(*elementSetNames));
340         Z_RecordComposition *compo = 
341             odr_malloc(global_parameters.odr_out, sizeof(*compo));
342         a->u.presentRequest->recordComposition = compo;
343
344         compo->which = Z_RecordComp_simple;
345         compo->u.simple = elementSetNames;
346
347         elementSetNames->which = Z_ElementSetNames_generic;
348         elementSetNames->u.generic = 
349             odr_strdup(global_parameters.odr_out, elements);
350     }
351 }
352
353 void client_send_raw_present(struct client *cl)
354 {
355     struct session_database *sdb = client_get_database(cl);
356     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
357     int toget = 1;
358     int start = cl->show_raw->position;
359     const char *syntax = 0;
360     const char *elements = 0;
361
362     assert(cl->show_raw);
363
364     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
365             client_get_url(cl), toget, start);
366
367     a->u.presentRequest->resultSetStartPoint = &start;
368     a->u.presentRequest->numberOfRecordsRequested = &toget;
369
370     if (cl->show_raw->syntax)
371         syntax = cl->show_raw->syntax;
372     else
373         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
374
375     client_present_syntax(a, syntax);
376     if (cl->show_raw->esn)
377         elements = cl->show_raw->esn;
378     else
379         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
380     client_present_elements(a, elements);
381
382     if (send_apdu(cl, a) >= 0)
383     {
384         cl->show_raw->active = 1;
385         cl->state = Client_Presenting;
386     }
387     else
388     {
389         client_show_raw_error(cl, "send_apdu failed");
390         cl->state = Client_Error;
391     }
392     odr_reset(global_parameters.odr_out);
393 }
394
395 void client_send_present(struct client *cl)
396 {
397     struct session_database *sdb = client_get_database(cl);
398     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
399     int toget;
400     int start = cl->records + 1;
401     const char *syntax = 0;
402     const char *elements = 0;
403
404     toget = global_parameters.chunk;
405     if (toget > global_parameters.toget - cl->records)
406         toget = global_parameters.toget - cl->records;
407     if (toget > cl->hits - cl->records)
408         toget = cl->hits - cl->records;
409
410     yaz_log(YLOG_DEBUG, "Trying to present %d record(s) from %d",
411             toget, start);
412
413     a->u.presentRequest->resultSetStartPoint = &start;
414     a->u.presentRequest->numberOfRecordsRequested = &toget;
415
416     syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
417     client_present_syntax(a, syntax);
418
419     elements = session_setting_oneval(sdb, PZ_ELEMENTS);
420     client_present_elements(a, elements);
421
422     if (send_apdu(cl, a) >= 0)
423         cl->state = Client_Presenting;
424     else
425         cl->state = Client_Error;
426     odr_reset(global_parameters.odr_out);
427 }
428
429
430 void client_send_search(struct client *cl)
431 {
432     struct session *se = client_get_session(cl);
433     struct session_database *sdb = client_get_database(cl);
434     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_searchRequest);
435     int ndb;
436     char **databaselist;
437     Z_Query *zquery;
438     int ssub = 0, lslb = 100000, mspn = 10;
439     const char *piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK);
440     const char *queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING);
441
442     yaz_log(YLOG_DEBUG, "Sending search to %s", sdb->database->url);
443
444     
445     // constructing RPN query
446     a->u.searchRequest->query = zquery = odr_malloc(global_parameters.odr_out,
447                                                     sizeof(Z_Query));
448     zquery->which = Z_Query_type_1;
449     zquery->u.type_1 = p_query_rpn(global_parameters.odr_out, 
450                                    client_get_pquery(cl));
451
452     // converting to target encoding
453     if (queryenc && *queryenc)
454     {
455         yaz_iconv_t iconv = yaz_iconv_open(queryenc, "UTF-8");
456         if (iconv){
457             yaz_query_charset_convert_rpnquery(zquery->u.type_1, 
458                                                global_parameters.odr_out, 
459                                                iconv);
460             yaz_iconv_close(iconv);
461         } else
462             yaz_log(YLOG_WARN, "Query encoding failed %s %s", 
463                     client_get_database(cl)->database->url, queryenc);
464     }
465
466     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
467         ;
468     databaselist = odr_malloc(global_parameters.odr_out, sizeof(char*) * ndb);
469     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
470         databaselist[ndb] = sdb->database->databases[ndb];
471
472     if (!piggyback || *piggyback == '1')
473     {
474         const char *elements = session_setting_oneval(sdb, PZ_ELEMENTS);
475         const char *recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
476         if (recsyn && *recsyn)
477         {
478             a->u.searchRequest->preferredRecordSyntax =
479                 yaz_string_to_oid_odr(yaz_oid_std(),
480                                       CLASS_RECSYN, recsyn,
481                                       global_parameters.odr_out);
482         }
483         if (elements && *elements)
484         {
485             Z_ElementSetNames *esn =
486                 odr_malloc(global_parameters.odr_out, sizeof(*esn));
487             esn->which = Z_ElementSetNames_generic;
488             esn->u.generic = odr_strdup(global_parameters.odr_out, elements);
489
490             a->u.searchRequest->smallSetElementSetNames = esn;
491             a->u.searchRequest->mediumSetElementSetNames = esn;
492         }
493         a->u.searchRequest->smallSetUpperBound = &ssub;
494         a->u.searchRequest->largeSetLowerBound = &lslb;
495         a->u.searchRequest->mediumSetPresentNumber = &mspn;
496     }
497     a->u.searchRequest->databaseNames = databaselist;
498     a->u.searchRequest->num_databaseNames = ndb;
499
500     
501     {  //scope for sending and logging queries 
502         WRBUF wbquery = wrbuf_alloc();
503         yaz_query_to_wrbuf(wbquery, a->u.searchRequest->query);
504
505
506         if (send_apdu(cl, a) >= 0)
507         {
508             client_set_state(cl, Client_Searching);
509             client_set_requestid(cl, se->requestid);
510             yaz_log(YLOG_LOG, "SearchRequest %s %s %s", 
511                     client_get_database(cl)->database->url,
512                     queryenc ? queryenc : "UTF-8",
513                     wrbuf_cstr(wbquery));
514         }
515         else {
516             client_set_state(cl, Client_Error);
517             yaz_log(YLOG_WARN, "Failed SearchRequest %s  %s %s", 
518                     client_get_database(cl)->database->url, 
519                     queryenc ? queryenc : "UTF-8",
520                     wrbuf_cstr(wbquery));
521         }
522         
523         wrbuf_destroy(wbquery);
524     }    
525
526     odr_reset(global_parameters.odr_out);
527 }
528
529 void client_init_response(struct client *cl, Z_APDU *a)
530 {
531     Z_InitResponse *r = a->u.initResponse;
532
533     yaz_log(YLOG_DEBUG, "Init response %s", cl->database->database->url);
534
535     if (*r->result)
536         cl->state = Client_Continue;
537     else
538         cl->state = Client_Failed; // FIXME need to do something to the connection
539 }
540
541
542 static void ingest_raw_records(struct client *cl, Z_Records *r)
543 {
544     Z_NamePlusRecordList *rlist;
545     Z_NamePlusRecord *npr;
546     xmlDoc *doc;
547     xmlChar *buf_out;
548     int len_out;
549     if (r->which != Z_Records_DBOSD)
550     {
551         client_show_raw_error(cl, "non-surrogate diagnostics");
552         return;
553     }
554
555     rlist = r->u.databaseOrSurDiagnostics;
556     if (rlist->num_records != 1 || !rlist->records || !rlist->records[0])
557     {
558         client_show_raw_error(cl, "no records");
559         return;
560     }
561     npr = rlist->records[0];
562     if (npr->which != Z_NamePlusRecord_databaseRecord)
563     {
564         client_show_raw_error(cl, "surrogate diagnostic");
565         return;
566     }
567
568     if (cl->show_raw && cl->show_raw->binary)
569     {
570         Z_External *rec = npr->u.databaseRecord;
571         if (rec->which == Z_External_octet)
572         {
573             cl->show_raw->record_handler(cl->show_raw->data,
574                                          (const char *)
575                                          rec->u.octet_aligned->buf,
576                                          rec->u.octet_aligned->len);
577             client_show_raw_dequeue(cl);
578         }
579         else
580             client_show_raw_error(cl, "no records");
581     }
582
583     doc = record_to_xml(client_get_database(cl), npr->u.databaseRecord);
584     if (!doc)
585     {
586         client_show_raw_error(cl, "unable to convert record to xml");
587         return;
588     }
589
590     xmlDocDumpMemory(doc, &buf_out, &len_out);
591     xmlFreeDoc(doc);
592
593     if (cl->show_raw)
594     {
595         cl->show_raw->record_handler(cl->show_raw->data,
596                                      (const char *) buf_out, len_out);
597         client_show_raw_dequeue(cl);
598     }
599     xmlFree(buf_out);
600 }
601
602 static void ingest_records(struct client *cl, Z_Records *r)
603 {
604 #if USE_TIMING
605     yaz_timing_t t = yaz_timing_create();
606 #endif
607     struct record *rec;
608     struct session *s = client_get_session(cl);
609     Z_NamePlusRecordList *rlist;
610     int i;
611
612     if (r->which != Z_Records_DBOSD)
613         return;
614     rlist = r->u.databaseOrSurDiagnostics;
615     for (i = 0; i < rlist->num_records; i++)
616     {
617         Z_NamePlusRecord *npr = rlist->records[i];
618
619         cl->records++;
620         if (npr->which != Z_NamePlusRecord_databaseRecord)
621         {
622             yaz_log(YLOG_WARN, 
623                     "Unexpected record type, probably diagnostic %s",
624                     cl->database->database->url);
625             continue;
626         }
627
628         rec = ingest_record(cl, npr->u.databaseRecord, cl->records);
629         if (!rec)
630             continue;
631     }
632     if (rlist->num_records)
633         session_alert_watch(s, SESSION_WATCH_SHOW);
634     if (rlist->num_records)
635         session_alert_watch(s, SESSION_WATCH_RECORD);
636
637 #if USE_TIMING
638     yaz_timing_stop(t);
639     yaz_log(YLOG_LOG, "ingest_records %6.5f %3.2f %3.2f", 
640             yaz_timing_get_real(t), yaz_timing_get_user(t),
641             yaz_timing_get_sys(t));
642     yaz_timing_destroy(&t);
643 #endif
644 }
645
646
647 void client_search_response(struct client *cl, Z_APDU *a)
648 {
649     struct session *se = cl->session;
650     Z_SearchResponse *r = a->u.searchResponse;
651
652     yaz_log(YLOG_DEBUG, "Search response %s (status=%d)", 
653             cl->database->database->url, *r->searchStatus);
654
655     if (*r->searchStatus)
656     {
657         cl->hits = *r->resultCount;
658         if (cl->hits < 0)
659         {
660             yaz_log(YLOG_WARN, "Target %s returns hit count %d",
661                     cl->database->database->url, cl->hits);
662         }
663         else
664             se->total_hits += cl->hits;
665         if (r->presentStatus && !*r->presentStatus && r->records)
666         {
667             yaz_log(YLOG_DEBUG, "Records in search response %s", 
668                     cl->database->database->url);
669             ingest_records(cl, r->records);
670         }
671         cl->state = Client_Continue;
672     }
673     else
674     {          /*"FAILED"*/
675         Z_Records *recs = r->records;
676         cl->hits = 0;
677         cl->state = Client_Error;
678         if (recs && recs->which == Z_Records_NSD)
679         {
680             WRBUF w = wrbuf_alloc();
681
682             Z_DiagRec dr, *dr_p = &dr;
683             dr.which = Z_DiagRec_defaultFormat;
684             dr.u.defaultFormat = recs->u.nonSurrogateDiagnostic;
685             
686             wrbuf_printf(w, "Search response NSD %s: ",
687                          cl->database->database->url);
688             
689             cl->diagnostic = diag_to_wrbuf(&dr_p, 1, w);
690
691             yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
692
693             cl->state = Client_Error;
694             wrbuf_destroy(w);
695         }
696         else if (recs && recs->which == Z_Records_multipleNSD)
697         {
698             WRBUF w = wrbuf_alloc();
699
700             wrbuf_printf(w, "Search response multipleNSD %s: ",
701                          cl->database->database->url);
702             cl->diagnostic = 
703                 diag_to_wrbuf(recs->u.multipleNonSurDiagnostics->diagRecs,
704                               recs->u.multipleNonSurDiagnostics->num_diagRecs,
705                               w);
706             yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
707             cl->state = Client_Error;
708             wrbuf_destroy(w);
709         }
710     }
711 }
712
713 void client_present_response(struct client *cl, Z_APDU *a)
714 {
715     Z_PresentResponse *r = a->u.presentResponse;
716     Z_Records *recs = r->records;
717         
718     if (recs && recs->which == Z_Records_NSD)
719     {
720         WRBUF w = wrbuf_alloc();
721         
722         Z_DiagRec dr, *dr_p = &dr;
723         dr.which = Z_DiagRec_defaultFormat;
724         dr.u.defaultFormat = recs->u.nonSurrogateDiagnostic;
725         
726         wrbuf_printf(w, "Present response NSD %s: ",
727                      cl->database->database->url);
728         
729         cl->diagnostic = diag_to_wrbuf(&dr_p, 1, w);
730         
731         yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
732         
733         cl->state = Client_Error;
734         wrbuf_destroy(w);
735
736         client_show_raw_error(cl, "non surrogate diagnostics");
737     }
738     else if (recs && recs->which == Z_Records_multipleNSD)
739     {
740         WRBUF w = wrbuf_alloc();
741         
742         wrbuf_printf(w, "Present response multipleNSD %s: ",
743                      cl->database->database->url);
744         cl->diagnostic = 
745             diag_to_wrbuf(recs->u.multipleNonSurDiagnostics->diagRecs,
746                           recs->u.multipleNonSurDiagnostics->num_diagRecs,
747                           w);
748         yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
749         cl->state = Client_Error;
750         wrbuf_destroy(w);
751     }
752     else if (recs && !*r->presentStatus && cl->state != Client_Error)
753     {
754         yaz_log(YLOG_DEBUG, "Good Present response %s",
755                 cl->database->database->url);
756
757         // we can mix show raw and normal show ..
758         if (cl->show_raw && cl->show_raw->active)
759         {
760             cl->show_raw->active = 0; // no longer active
761             ingest_raw_records(cl, recs);
762         }
763         else
764             ingest_records(cl, recs);
765         cl->state = Client_Continue;
766     }
767     else if (*r->presentStatus) 
768     {
769         yaz_log(YLOG_WARN, "Bad Present response %s",
770                 cl->database->database->url);
771         cl->state = Client_Error;
772         client_show_raw_error(cl, "bad present response");
773     }
774 }
775
776 void client_close_response(struct client *cl, Z_APDU *a)
777 {
778     struct connection *co = cl->connection;
779     /* Z_Close *r = a->u.close; */
780
781     yaz_log(YLOG_WARN, "Close response %s", cl->database->database->url);
782
783     cl->state = Client_Failed;
784     connection_destroy(co);
785 }
786
787 int client_is_our_response(struct client *cl)
788 {
789     struct session *se = client_get_session(cl);
790
791     if (cl && (cl->requestid == se->requestid || 
792                cl->state == Client_Initializing))
793         return 1;
794     return 0;
795 }
796
797 // Set authentication token in init if one is set for the client
798 // TODO: Extend this to handle other schemes than open (should be simple)
799 static void init_authentication(struct client *cl, Z_InitRequest *req)
800 {
801     struct session_database *sdb = client_get_database(cl);
802     const char *auth = session_setting_oneval(sdb, PZ_AUTHENTICATION);
803
804     if (*auth)
805     {
806         struct connection *co = client_get_connection(cl);
807         struct session *se = client_get_session(cl);
808         Z_IdAuthentication *idAuth = odr_malloc(global_parameters.odr_out,
809                 sizeof(*idAuth));
810         idAuth->which = Z_IdAuthentication_open;
811         idAuth->u.open = odr_strdup(global_parameters.odr_out, auth);
812         req->idAuthentication = idAuth;
813         connection_set_authentication(co, nmem_strdup(se->session_nmem, auth));
814     }
815 }
816
817 static void init_zproxy(struct client *cl, Z_InitRequest *req)
818 {
819     struct session_database *sdb = client_get_database(cl);
820     char *ztarget = sdb->database->url;
821     //char *ztarget = sdb->url;    
822     const char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
823     
824     if (*zproxy)
825         yaz_oi_set_string_oid(&req->otherInfo,
826                               global_parameters.odr_out,
827                               yaz_oid_userinfo_proxy,
828                               1, ztarget);
829 }
830
831
832 static void client_init_request(struct client *cl)
833 {
834     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_initRequest);
835
836     a->u.initRequest->implementationId = global_parameters.implementationId;
837     a->u.initRequest->implementationName = global_parameters.implementationName;
838     a->u.initRequest->implementationVersion =
839         global_parameters.implementationVersion;
840     ODR_MASK_SET(a->u.initRequest->options, Z_Options_search);
841     ODR_MASK_SET(a->u.initRequest->options, Z_Options_present);
842     ODR_MASK_SET(a->u.initRequest->options, Z_Options_namedResultSets);
843
844     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_1);
845     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_2);
846     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_3);
847
848     init_authentication(cl, a->u.initRequest);
849     init_zproxy(cl, a->u.initRequest);
850
851     if (send_apdu(cl, a) >= 0)
852         client_set_state(cl, Client_Initializing);
853     else
854         client_set_state(cl, Client_Error);
855     odr_reset(global_parameters.odr_out);
856 }
857
858 void client_continue(struct client *cl)
859 {
860     if (cl->state == Client_Connected) {
861         client_init_request(cl);
862     }
863     if (cl->state == Client_Continue || cl->state == Client_Idle)
864     {
865         struct session *se = client_get_session(cl);
866         if (cl->requestid != se->requestid && cl->pquery) {
867             // we'll have to abort this because result set is to be deleted
868             client_show_raw_cancel(cl);   
869             client_send_search(cl);
870         }
871         else if (cl->show_raw)
872         {
873             client_send_raw_present(cl);
874         }
875         else if (cl->hits > 0 && cl->records < global_parameters.toget &&
876             cl->records < cl->hits) {
877             client_send_present(cl);
878         }
879         else
880             client_set_state(cl, Client_Idle);
881     }
882 }
883
884 struct client *client_create(void)
885 {
886     struct client *r;
887     if (client_freelist)
888     {
889         r = client_freelist;
890         client_freelist = client_freelist->next;
891     }
892     else
893         r = xmalloc(sizeof(struct client));
894     r->pquery = 0;
895     r->database = 0;
896     r->connection = 0;
897     r->session = 0;
898     r->hits = 0;
899     r->records = 0;
900     r->setno = 0;
901     r->requestid = -1;
902     r->diagnostic = 0;
903     r->state = Client_Disconnected;
904     r->show_raw = 0;
905     r->next = 0;
906     return r;
907 }
908
909 void client_destroy(struct client *c)
910 {
911     struct session *se = c->session;
912     if (c == se->clients)
913         se->clients = c->next;
914     else
915     {
916         struct client *cc;
917         for (cc = se->clients; cc && cc->next != c; cc = cc->next)
918             ;
919         if (cc)
920             cc->next = c->next;
921     }
922     xfree(c->pquery);
923
924     if (c->connection)
925         connection_release(c->connection);
926     c->next = client_freelist;
927     client_freelist = c;
928 }
929
930 void client_set_connection(struct client *cl, struct connection *con)
931 {
932     cl->connection = con;
933 }
934
935 void client_disconnect(struct client *cl)
936 {
937     if (cl->state != Client_Idle)
938         client_set_state(cl, Client_Disconnected);
939     client_set_connection(cl, 0);
940 }
941
942 // Extract terms from query into null-terminated termlist
943 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
944 {
945     int num = 0;
946
947     pull_terms(nmem, query, termlist, &num);
948     termlist[num] = 0;
949 }
950
951 // Initialize CCL map for a target
952 static CCL_bibset prepare_cclmap(struct client *cl)
953 {
954     struct session_database *sdb = client_get_database(cl);
955     struct setting *s;
956     CCL_bibset res;
957
958     if (!sdb->settings)
959         return 0;
960     res = ccl_qual_mk();
961     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
962     {
963         char *p = strchr(s->name + 3, ':');
964         if (!p)
965         {
966             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
967             ccl_qual_rm(&res);
968             return 0;
969         }
970         p++;
971         ccl_qual_fitem(res, s->value, p);
972     }
973     return res;
974 }
975
976 // Parse the query given the settings specific to this client
977 int client_parse_query(struct client *cl, const char *query)
978 {
979     struct session *se = client_get_session(cl);
980     struct ccl_rpn_node *cn;
981     int cerror, cpos;
982     CCL_bibset ccl_map = prepare_cclmap(cl);
983
984     if (!ccl_map)
985         return -1;
986
987     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
988     ccl_qual_rm(&ccl_map);
989     if (!cn)
990     {
991         cl->state = Client_Error;
992         yaz_log(YLOG_WARN, "Failed to parse query for %s",
993                          client_get_database(cl)->database->url);
994         return -1;
995     }
996     wrbuf_rewind(se->wrbuf);
997     ccl_pquery(se->wrbuf, cn);
998     xfree(cl->pquery);
999     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
1000
1001     if (!se->relevance)
1002     {
1003         // Initialize relevance structure with query terms
1004         char *p[512];
1005         extract_terms(se->nmem, cn, p);
1006         se->relevance = relevance_create(
1007             global_parameters.server->relevance_pct,
1008             se->nmem, (const char **) p,
1009             se->expected_maxrecs);
1010     }
1011
1012     ccl_rpn_delete(cn);
1013     return 0;
1014 }
1015
1016 void client_set_session(struct client *cl, struct session *se)
1017 {
1018     cl->session = se;
1019     cl->next = se->clients;
1020     se->clients = cl;
1021 }
1022
1023 int client_is_active(struct client *cl)
1024 {
1025     if (cl->connection && (cl->state == Client_Continue ||
1026                            cl->state == Client_Connecting ||
1027                            cl->state == Client_Connected ||
1028                            cl->state == Client_Initializing ||
1029                            cl->state == Client_Searching ||
1030                            cl->state == Client_Presenting))
1031         return 1;
1032     return 0;
1033 }
1034
1035 struct client *client_next_in_session(struct client *cl)
1036 {
1037     if (cl)
1038         return cl->next;
1039     return 0;
1040
1041 }
1042
1043 int client_get_hits(struct client *cl)
1044 {
1045     return cl->hits;
1046 }
1047
1048 int client_get_num_records(struct client *cl)
1049 {
1050     return cl->records;
1051 }
1052
1053 int client_get_diagnostic(struct client *cl)
1054 {
1055     return cl->diagnostic;
1056 }
1057
1058 void client_set_database(struct client *cl, struct session_database *db)
1059 {
1060     cl->database = db;
1061 }
1062
1063 struct host *client_get_host(struct client *cl)
1064 {
1065     return client_get_database(cl)->database->host;
1066 }
1067
1068 const char *client_get_url(struct client *cl)
1069 {
1070     return client_get_database(cl)->database->url;
1071 }
1072
1073 /*
1074  * Local variables:
1075  * c-basic-offset: 4
1076  * indent-tabs-mode: nil
1077  * End:
1078  * vim: shiftwidth=4 tabstop=8 expandtab
1079  */