More active trickery. Setting state to Client_Continue instead Client_Idle
[pazpar2-moved-to-github.git] / src / client.c
1 /* $Id: client.c,v 1.24 2007-09-20 08:34:50 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     char *syntax;
88     char *esn;
89     void (*error_handler)(void *data, const char *addinfo);
90     void (*record_handler)(void *data, const char *buf, size_t sz);
91     void *data;
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 {
241     if (cl->show_raw)
242         return -1;
243     cl->show_raw = xmalloc(sizeof(*cl->show_raw));
244     cl->show_raw->position = position;
245     cl->show_raw->active = 0;
246     cl->show_raw->data = data;
247     cl->show_raw->error_handler = error_handler;
248     cl->show_raw->record_handler = record_handler;
249     if (syntax)
250         cl->show_raw->syntax = xstrdup(syntax);
251     else
252         cl->show_raw->syntax = 0;
253     if (esn)
254         cl->show_raw->esn = xstrdup(esn);
255     else
256         cl->show_raw->esn = 0;
257     
258
259     if (cl->state == Client_Failed)
260     {
261         client_show_raw_error(cl, "client failed");
262     }
263     else if (cl->state == Client_Disconnected)
264     {
265         client_show_raw_error(cl, "client disconnected");
266     }
267     else
268     {
269         client_continue(cl);
270     }
271     return 0;
272 }
273
274 void client_show_raw_reset(struct client *cl)
275 {
276     xfree(cl->show_raw);
277     cl->show_raw = 0;
278 }
279
280 static void client_show_raw_error(struct client *cl, const char *addinfo)
281 {
282     if (cl->show_raw)
283     {
284         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
285         client_show_raw_reset(cl);
286     }
287 }
288
289 static void client_show_raw_cancel(struct client *cl)
290 {
291     if (cl->show_raw)
292     {
293         cl->show_raw->error_handler(cl->show_raw->data, "cancel");
294         client_show_raw_reset(cl);
295     }
296 }
297
298 static void client_present_syntax(Z_APDU *a, const char *syntax)
299 {
300     // empty string for syntax OMITS preferredRecordSyntax (OPTIONAL)
301     if (syntax && *syntax)
302         a->u.presentRequest->preferredRecordSyntax =
303             yaz_string_to_oid_odr(yaz_oid_std(),
304                                   CLASS_RECSYN, syntax,
305                                   global_parameters.odr_out);
306 }
307
308 static void client_present_elements(Z_APDU *a, const char *elements)
309 {
310     if (elements && *elements)  // element set is optional
311     {
312         Z_ElementSetNames *elementSetNames =
313             odr_malloc(global_parameters.odr_out, sizeof(*elementSetNames));
314         Z_RecordComposition *compo = 
315             odr_malloc(global_parameters.odr_out, sizeof(*compo));
316         a->u.presentRequest->recordComposition = compo;
317
318         compo->which = Z_RecordComp_simple;
319         compo->u.simple = elementSetNames;
320
321         elementSetNames->which = Z_ElementSetNames_generic;
322         elementSetNames->u.generic = 
323             odr_strdup(global_parameters.odr_out, elements);
324     }
325 }
326
327 void client_send_raw_present(struct client *cl)
328 {
329     struct session_database *sdb = client_get_database(cl);
330     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
331     int toget = 1;
332     int start = cl->show_raw->position;
333     const char *syntax = 0;
334     const char *elements = 0;
335
336     assert(cl->show_raw);
337
338     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
339             client_get_url(cl), toget, start);
340
341     a->u.presentRequest->resultSetStartPoint = &start;
342     a->u.presentRequest->numberOfRecordsRequested = &toget;
343
344     if (cl->show_raw->syntax)
345         syntax = cl->show_raw->syntax;
346     else
347         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
348
349     client_present_syntax(a, syntax);
350     if (cl->show_raw->esn)
351         elements = cl->show_raw->esn;
352     else
353         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
354     client_present_elements(a, elements);
355
356     if (send_apdu(cl, a) >= 0)
357     {
358         cl->show_raw->active = 1;
359         cl->state = Client_Presenting;
360     }
361     else
362     {
363         client_show_raw_error(cl, "send_apdu failed");
364         cl->state = Client_Error;
365     }
366     odr_reset(global_parameters.odr_out);
367 }
368
369 void client_send_present(struct client *cl)
370 {
371     struct session_database *sdb = client_get_database(cl);
372     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
373     int toget;
374     int start = cl->records + 1;
375     const char *syntax = 0;
376     const char *elements = 0;
377
378     toget = global_parameters.chunk;
379     if (toget > global_parameters.toget - cl->records)
380         toget = global_parameters.toget - cl->records;
381     if (toget > cl->hits - cl->records)
382         toget = cl->hits - cl->records;
383
384     yaz_log(YLOG_DEBUG, "Trying to present %d record(s) from %d",
385             toget, start);
386
387     a->u.presentRequest->resultSetStartPoint = &start;
388     a->u.presentRequest->numberOfRecordsRequested = &toget;
389
390     syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
391     client_present_syntax(a, syntax);
392
393     elements = session_setting_oneval(sdb, PZ_ELEMENTS);
394     client_present_elements(a, elements);
395
396     if (send_apdu(cl, a) >= 0)
397         cl->state = Client_Presenting;
398     else
399         cl->state = Client_Error;
400     odr_reset(global_parameters.odr_out);
401 }
402
403
404 void client_send_search(struct client *cl)
405 {
406     struct session *se = client_get_session(cl);
407     struct session_database *sdb = client_get_database(cl);
408     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_searchRequest);
409     int ndb;
410     char **databaselist;
411     Z_Query *zquery;
412     int ssub = 0, lslb = 100000, mspn = 10;
413     const char *piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK);
414     const char *queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING);
415
416     yaz_log(YLOG_DEBUG, "Sending search to %s", sdb->database->url);
417
418     
419     // constructing RPN query
420     a->u.searchRequest->query = zquery = odr_malloc(global_parameters.odr_out,
421                                                     sizeof(Z_Query));
422     zquery->which = Z_Query_type_1;
423     zquery->u.type_1 = p_query_rpn(global_parameters.odr_out, 
424                                    client_get_pquery(cl));
425
426     // converting to target encoding
427     if (queryenc && *queryenc)
428     {
429         yaz_iconv_t iconv = yaz_iconv_open(queryenc, "UTF-8");
430         if (iconv){
431             yaz_query_charset_convert_rpnquery(zquery->u.type_1, 
432                                                global_parameters.odr_out, 
433                                                iconv);
434             yaz_iconv_close(iconv);
435         } else
436             yaz_log(YLOG_WARN, "Query encoding failed %s %s", 
437                     client_get_database(cl)->database->url, queryenc);
438     }
439
440     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
441         ;
442     databaselist = odr_malloc(global_parameters.odr_out, sizeof(char*) * ndb);
443     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
444         databaselist[ndb] = sdb->database->databases[ndb];
445
446     if (!piggyback || *piggyback == '1')
447     {
448         const char *elements = session_setting_oneval(sdb, PZ_ELEMENTS);
449         const char *recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
450         if (recsyn && *recsyn)
451         {
452             a->u.searchRequest->preferredRecordSyntax =
453                 yaz_string_to_oid_odr(yaz_oid_std(),
454                                       CLASS_RECSYN, recsyn,
455                                       global_parameters.odr_out);
456         }
457         if (elements && *elements)
458         {
459             Z_ElementSetNames *esn =
460                 odr_malloc(global_parameters.odr_out, sizeof(*esn));
461             esn->which = Z_ElementSetNames_generic;
462             esn->u.generic = odr_strdup(global_parameters.odr_out, elements);
463
464             a->u.searchRequest->smallSetElementSetNames = esn;
465             a->u.searchRequest->mediumSetElementSetNames = esn;
466         }
467         a->u.searchRequest->smallSetUpperBound = &ssub;
468         a->u.searchRequest->largeSetLowerBound = &lslb;
469         a->u.searchRequest->mediumSetPresentNumber = &mspn;
470     }
471     a->u.searchRequest->databaseNames = databaselist;
472     a->u.searchRequest->num_databaseNames = ndb;
473
474     
475     {  //scope for sending and logging queries 
476         WRBUF wbquery = wrbuf_alloc();
477         yaz_query_to_wrbuf(wbquery, a->u.searchRequest->query);
478
479
480         if (send_apdu(cl, a) >= 0)
481         {
482             client_set_state(cl, Client_Searching);
483             client_set_requestid(cl, se->requestid);
484             yaz_log(YLOG_LOG, "SearchRequest %s %s %s", 
485                     client_get_database(cl)->database->url,
486                     queryenc ? queryenc : "UTF-8",
487                     wrbuf_cstr(wbquery));
488         }
489         else {
490             client_set_state(cl, Client_Error);
491             yaz_log(YLOG_WARN, "Failed SearchRequest %s  %s %s", 
492                     client_get_database(cl)->database->url, 
493                     queryenc ? queryenc : "UTF-8",
494                     wrbuf_cstr(wbquery));
495         }
496         
497         wrbuf_destroy(wbquery);
498     }    
499
500     odr_reset(global_parameters.odr_out);
501 }
502
503 void client_init_response(struct client *cl, Z_APDU *a)
504 {
505     Z_InitResponse *r = a->u.initResponse;
506
507     yaz_log(YLOG_DEBUG, "Init response %s", cl->database->database->url);
508
509     if (*r->result)
510         cl->state = Client_Continue;
511     else
512         cl->state = Client_Failed; // FIXME need to do something to the connection
513 }
514
515
516 static void ingest_raw_records(struct client *cl, Z_Records *r)
517 {
518     Z_NamePlusRecordList *rlist;
519     Z_NamePlusRecord *npr;
520     xmlDoc *doc;
521     xmlChar *buf_out;
522     int len_out;
523     if (r->which != Z_Records_DBOSD)
524     {
525         client_show_raw_error(cl, "non-surrogate diagnostics");
526         return;
527     }
528
529     rlist = r->u.databaseOrSurDiagnostics;
530     if (rlist->num_records != 1 || !rlist->records || !rlist->records[0])
531     {
532         client_show_raw_error(cl, "no records");
533         return;
534     }
535     npr = rlist->records[0];
536     if (npr->which != Z_NamePlusRecord_databaseRecord)
537     {
538         client_show_raw_error(cl, "surrogate diagnostic");
539         return;
540     }
541
542     doc = record_to_xml(client_get_database(cl), npr->u.databaseRecord);
543     if (!doc)
544     {
545         client_show_raw_error(cl, "unable to convert record to xml");
546         return;
547     }
548
549     xmlDocDumpMemory(doc, &buf_out, &len_out);
550     xmlFreeDoc(doc);
551
552     cl->show_raw->record_handler(cl->show_raw->data,
553                                  (const char *) buf_out, len_out);
554     
555     xmlFree(buf_out);
556     xfree(cl->show_raw);
557     cl->show_raw = 0;
558 }
559
560 static void ingest_records(struct client *cl, Z_Records *r)
561 {
562 #if USE_TIMING
563     yaz_timing_t t = yaz_timing_create();
564 #endif
565     struct record *rec;
566     struct session *s = client_get_session(cl);
567     Z_NamePlusRecordList *rlist;
568     int i;
569
570     if (r->which != Z_Records_DBOSD)
571         return;
572     rlist = r->u.databaseOrSurDiagnostics;
573     for (i = 0; i < rlist->num_records; i++)
574     {
575         Z_NamePlusRecord *npr = rlist->records[i];
576
577         cl->records++;
578         if (npr->which != Z_NamePlusRecord_databaseRecord)
579         {
580             yaz_log(YLOG_WARN, 
581                     "Unexpected record type, probably diagnostic %s",
582                     cl->database->database->url);
583             continue;
584         }
585
586         rec = ingest_record(cl, npr->u.databaseRecord, cl->records);
587         if (!rec)
588             continue;
589     }
590     if (rlist->num_records)
591         session_alert_watch(s, SESSION_WATCH_SHOW);
592     if (rlist->num_records)
593         session_alert_watch(s, SESSION_WATCH_RECORD);
594
595 #if USE_TIMING
596     yaz_timing_stop(t);
597     yaz_log(YLOG_LOG, "ingest_records %6.5f %3.2f %3.2f", 
598             yaz_timing_get_real(t), yaz_timing_get_user(t),
599             yaz_timing_get_sys(t));
600     yaz_timing_destroy(&t);
601 #endif
602 }
603
604
605 void client_search_response(struct client *cl, Z_APDU *a)
606 {
607     struct session *se = cl->session;
608     Z_SearchResponse *r = a->u.searchResponse;
609
610     yaz_log(YLOG_DEBUG, "Search response %s (status=%d)", 
611             cl->database->database->url, *r->searchStatus);
612
613     if (*r->searchStatus)
614     {
615         cl->hits = *r->resultCount;
616         if (cl->hits < 0)
617         {
618             yaz_log(YLOG_WARN, "Target %s returns hit count %d",
619                     cl->database->database->url, cl->hits);
620         }
621         else
622             se->total_hits += cl->hits;
623         if (r->presentStatus && !*r->presentStatus && r->records)
624         {
625             yaz_log(YLOG_DEBUG, "Records in search response %s", 
626                     cl->database->database->url);
627             ingest_records(cl, r->records);
628         }
629         cl->state = Client_Continue;
630     }
631     else
632     {          /*"FAILED"*/
633         Z_Records *recs = r->records;
634         cl->hits = 0;
635         cl->state = Client_Error;
636         if (recs && recs->which == Z_Records_NSD)
637         {
638             WRBUF w = wrbuf_alloc();
639
640             Z_DiagRec dr, *dr_p = &dr;
641             dr.which = Z_DiagRec_defaultFormat;
642             dr.u.defaultFormat = recs->u.nonSurrogateDiagnostic;
643             
644             wrbuf_printf(w, "Search response NSD %s: ",
645                          cl->database->database->url);
646             
647             cl->diagnostic = diag_to_wrbuf(&dr_p, 1, w);
648
649             yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
650
651             cl->state = Client_Error;
652             wrbuf_destroy(w);
653         }
654         else if (recs && recs->which == Z_Records_multipleNSD)
655         {
656             WRBUF w = wrbuf_alloc();
657
658             wrbuf_printf(w, "Search response multipleNSD %s: ",
659                          cl->database->database->url);
660             cl->diagnostic = 
661                 diag_to_wrbuf(recs->u.multipleNonSurDiagnostics->diagRecs,
662                               recs->u.multipleNonSurDiagnostics->num_diagRecs,
663                               w);
664             yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
665             cl->state = Client_Error;
666             wrbuf_destroy(w);
667         }
668     }
669 }
670
671 void client_present_response(struct client *cl, Z_APDU *a)
672 {
673     Z_PresentResponse *r = a->u.presentResponse;
674     Z_Records *recs = r->records;
675         
676     if (recs && recs->which == Z_Records_NSD)
677     {
678         WRBUF w = wrbuf_alloc();
679         
680         Z_DiagRec dr, *dr_p = &dr;
681         dr.which = Z_DiagRec_defaultFormat;
682         dr.u.defaultFormat = recs->u.nonSurrogateDiagnostic;
683         
684         wrbuf_printf(w, "Present response NSD %s: ",
685                      cl->database->database->url);
686         
687         cl->diagnostic = diag_to_wrbuf(&dr_p, 1, w);
688         
689         yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
690         
691         cl->state = Client_Error;
692         wrbuf_destroy(w);
693
694         client_show_raw_error(cl, "non surrogate diagnostics");
695     }
696     else if (recs && recs->which == Z_Records_multipleNSD)
697     {
698         WRBUF w = wrbuf_alloc();
699         
700         wrbuf_printf(w, "Present 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     else if (recs && !*r->presentStatus && cl->state != Client_Error)
711     {
712         yaz_log(YLOG_DEBUG, "Good Present response %s",
713                 cl->database->database->url);
714
715         // we can mix show raw and normal show ..
716         if (cl->show_raw && cl->show_raw->active)
717         {
718             cl->show_raw->active = 0; // no longer active
719             ingest_raw_records(cl, recs);
720         }
721         else
722             ingest_records(cl, recs);
723         cl->state = Client_Continue;
724     }
725     else if (*r->presentStatus) 
726     {
727         yaz_log(YLOG_WARN, "Bad Present response %s",
728                 cl->database->database->url);
729         cl->state = Client_Error;
730         client_show_raw_error(cl, "bad present response");
731     }
732 }
733
734 void client_close_response(struct client *cl, Z_APDU *a)
735 {
736     struct connection *co = cl->connection;
737     /* Z_Close *r = a->u.close; */
738
739     yaz_log(YLOG_WARN, "Close response %s", cl->database->database->url);
740
741     cl->state = Client_Failed;
742     connection_destroy(co);
743 }
744
745 int client_is_our_response(struct client *cl)
746 {
747     struct session *se = client_get_session(cl);
748
749     if (cl && (cl->requestid == se->requestid || 
750                cl->state == Client_Initializing))
751         return 1;
752     return 0;
753 }
754
755 // Set authentication token in init if one is set for the client
756 // TODO: Extend this to handle other schemes than open (should be simple)
757 static void init_authentication(struct client *cl, Z_InitRequest *req)
758 {
759     struct session_database *sdb = client_get_database(cl);
760     const char *auth = session_setting_oneval(sdb, PZ_AUTHENTICATION);
761
762     if (*auth)
763     {
764         struct connection *co = client_get_connection(cl);
765         struct session *se = client_get_session(cl);
766         Z_IdAuthentication *idAuth = odr_malloc(global_parameters.odr_out,
767                 sizeof(*idAuth));
768         idAuth->which = Z_IdAuthentication_open;
769         idAuth->u.open = odr_strdup(global_parameters.odr_out, auth);
770         req->idAuthentication = idAuth;
771         connection_set_authentication(co, nmem_strdup(se->session_nmem, auth));
772     }
773 }
774
775 static void init_zproxy(struct client *cl, Z_InitRequest *req)
776 {
777     struct session_database *sdb = client_get_database(cl);
778     char *ztarget = sdb->database->url;
779     //char *ztarget = sdb->url;    
780     const char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
781     
782     if (*zproxy)
783         yaz_oi_set_string_oid(&req->otherInfo,
784                               global_parameters.odr_out,
785                               yaz_oid_userinfo_proxy,
786                               1, ztarget);
787 }
788
789
790 static void client_init_request(struct client *cl)
791 {
792     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_initRequest);
793
794     a->u.initRequest->implementationId = global_parameters.implementationId;
795     a->u.initRequest->implementationName = global_parameters.implementationName;
796     a->u.initRequest->implementationVersion =
797         global_parameters.implementationVersion;
798     ODR_MASK_SET(a->u.initRequest->options, Z_Options_search);
799     ODR_MASK_SET(a->u.initRequest->options, Z_Options_present);
800     ODR_MASK_SET(a->u.initRequest->options, Z_Options_namedResultSets);
801
802     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_1);
803     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_2);
804     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_3);
805
806     init_authentication(cl, a->u.initRequest);
807     init_zproxy(cl, a->u.initRequest);
808
809     if (send_apdu(cl, a) >= 0)
810         client_set_state(cl, Client_Initializing);
811     else
812         client_set_state(cl, Client_Error);
813     odr_reset(global_parameters.odr_out);
814 }
815
816 void client_continue(struct client *cl)
817 {
818     if (cl->state == Client_Connected) {
819         client_init_request(cl);
820     }
821     if (cl->state == Client_Continue || cl->state == Client_Idle)
822     {
823         struct session *se = client_get_session(cl);
824         if (cl->requestid != se->requestid && cl->pquery) {
825             // we'll have to abort this because result set is to be deleted
826             client_show_raw_cancel(cl);   
827             client_send_search(cl);
828         }
829         else if (cl->show_raw)
830         {
831             client_send_raw_present(cl);
832         }
833         else if (cl->hits > 0 && cl->records < global_parameters.toget &&
834             cl->records < cl->hits) {
835             client_send_present(cl);
836         }
837         else
838             client_set_state(cl, Client_Idle);
839     }
840 }
841
842 struct client *client_create(void)
843 {
844     struct client *r;
845     if (client_freelist)
846     {
847         r = client_freelist;
848         client_freelist = client_freelist->next;
849     }
850     else
851         r = xmalloc(sizeof(struct client));
852     r->pquery = 0;
853     r->database = 0;
854     r->connection = 0;
855     r->session = 0;
856     r->hits = 0;
857     r->records = 0;
858     r->setno = 0;
859     r->requestid = -1;
860     r->diagnostic = 0;
861     r->state = Client_Disconnected;
862     r->show_raw = 0;
863     r->next = 0;
864     return r;
865 }
866
867 void client_destroy(struct client *c)
868 {
869     struct session *se = c->session;
870     if (c == se->clients)
871         se->clients = c->next;
872     else
873     {
874         struct client *cc;
875         for (cc = se->clients; cc && cc->next != c; cc = cc->next)
876             ;
877         if (cc)
878             cc->next = c->next;
879     }
880     xfree(c->pquery);
881
882     if (c->connection)
883         connection_release(c->connection);
884     c->next = client_freelist;
885     client_freelist = c;
886 }
887
888 void client_set_connection(struct client *cl, struct connection *con)
889 {
890     cl->connection = con;
891 }
892
893 void client_disconnect(struct client *cl)
894 {
895     if (cl->state != Client_Idle)
896         cl->state = Client_Disconnected;
897     client_set_connection(cl, 0);
898 }
899
900 // Extract terms from query into null-terminated termlist
901 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
902 {
903     int num = 0;
904
905     pull_terms(nmem, query, termlist, &num);
906     termlist[num] = 0;
907 }
908
909 // Initialize CCL map for a target
910 static CCL_bibset prepare_cclmap(struct client *cl)
911 {
912     struct session_database *sdb = client_get_database(cl);
913     struct setting *s;
914     CCL_bibset res;
915
916     if (!sdb->settings)
917         return 0;
918     res = ccl_qual_mk();
919     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
920     {
921         char *p = strchr(s->name + 3, ':');
922         if (!p)
923         {
924             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
925             ccl_qual_rm(&res);
926             return 0;
927         }
928         p++;
929         ccl_qual_fitem(res, s->value, p);
930     }
931     return res;
932 }
933
934 // Parse the query given the settings specific to this client
935 int client_parse_query(struct client *cl, const char *query)
936 {
937     struct session *se = client_get_session(cl);
938     struct ccl_rpn_node *cn;
939     int cerror, cpos;
940     CCL_bibset ccl_map = prepare_cclmap(cl);
941
942     if (!ccl_map)
943         return -1;
944
945     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
946     ccl_qual_rm(&ccl_map);
947     if (!cn)
948     {
949         cl->state = Client_Error;
950         yaz_log(YLOG_WARN, "Failed to parse query for %s",
951                          client_get_database(cl)->database->url);
952         return -1;
953     }
954     wrbuf_rewind(se->wrbuf);
955     ccl_pquery(se->wrbuf, cn);
956     xfree(cl->pquery);
957     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
958
959     if (!se->relevance)
960     {
961         // Initialize relevance structure with query terms
962         char *p[512];
963         extract_terms(se->nmem, cn, p);
964         se->relevance = relevance_create(
965             global_parameters.server->relevance_pct,
966             se->nmem, (const char **) p,
967             se->expected_maxrecs);
968     }
969
970     ccl_rpn_delete(cn);
971     return 0;
972 }
973
974 void client_set_session(struct client *cl, struct session *se)
975 {
976     cl->session = se;
977     cl->next = se->clients;
978     se->clients = cl;
979 }
980
981 int client_is_active(struct client *cl)
982 {
983     if (cl->connection && (cl->state == Client_Continue ||
984                            cl->state == Client_Connecting ||
985                            cl->state == Client_Initializing ||
986                            cl->state == Client_Searching ||
987                            cl->state == Client_Presenting))
988         return 1;
989     return 0;
990 }
991
992 struct client *client_next_in_session(struct client *cl)
993 {
994     if (cl)
995         return cl->next;
996     return 0;
997
998 }
999
1000 int client_get_hits(struct client *cl)
1001 {
1002     return cl->hits;
1003 }
1004
1005 int client_get_num_records(struct client *cl)
1006 {
1007     return cl->records;
1008 }
1009
1010 int client_get_diagnostic(struct client *cl)
1011 {
1012     return cl->diagnostic;
1013 }
1014
1015 void client_set_database(struct client *cl, struct session_database *db)
1016 {
1017     cl->database = db;
1018 }
1019
1020 struct host *client_get_host(struct client *cl)
1021 {
1022     return client_get_database(cl)->database->host;
1023 }
1024
1025 const char *client_get_url(struct client *cl)
1026 {
1027     return client_get_database(cl)->database->url;
1028 }
1029
1030 /*
1031  * Local variables:
1032  * c-basic-offset: 4
1033  * indent-tabs-mode: nil
1034  * End:
1035  * vim: shiftwidth=4 tabstop=8 expandtab
1036  */