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