Work on bug #1192: block=1, session_watch problems. The http_channel
[pazpar2-moved-to-github.git] / src / client.c
1 /* $Id: client.c,v 1.11 2007-06-15 19:35:17 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
50 #if HAVE_CONFIG_H
51 #include "cconfig.h"
52 #endif
53
54 #define USE_TIMING 0
55 #if USE_TIMING
56 #include <yaz/timing.h>
57 #endif
58
59 #include <netinet/in.h>
60
61 #include "pazpar2.h"
62
63 #include "client.h"
64 #include "connection.h"
65 #include "settings.h"
66
67 /** \brief Represents client state for a connection to one search target */
68 struct client {
69     struct session_database *database;
70     struct connection *connection;
71     struct session *session;
72     char *pquery; // Current search
73     int hits;
74     int records;
75     int setno;
76     int requestid;            // ID of current outstanding request
77     int diagnostic;
78     enum client_state state;
79     struct show_raw *show_raw;
80     struct client *next;     // next client in session or next in free list
81 };
82
83 struct show_raw {
84     int active; // whether this request has been sent to the server
85     int position;
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 };
92
93 static const char *client_states[] = {
94     "Client_Connecting",
95     "Client_Connected",
96     "Client_Idle",
97     "Client_Initializing",
98     "Client_Searching",
99     "Client_Presenting",
100     "Client_Error",
101     "Client_Failed",
102     "Client_Disconnected",
103     "Client_Stopped"
104 };
105
106 static struct client *client_freelist = 0;
107
108 static int send_apdu(struct client *c, Z_APDU *a)
109 {
110     return connection_send_apdu(client_get_connection(c), a);
111 }
112
113
114 const char *client_get_state_str(struct client *cl)
115 {
116     return client_states[cl->state];
117 }
118
119 enum client_state client_get_state(struct client *cl)
120 {
121     return cl->state;
122 }
123
124 void client_set_state(struct client *cl, enum client_state st)
125 {
126     cl->state = st;
127 }
128
129 static void client_show_raw_error(struct client *cl, const char *addinfo);
130
131 // Close connection and set state to error
132 void client_fatal(struct client *cl)
133 {
134     client_show_raw_error(cl, "client connection failure");
135     yaz_log(YLOG_WARN, "Fatal error from %s", client_get_url(cl));
136     connection_destroy(cl->connection);
137     cl->state = Client_Error;
138 }
139
140 struct connection *client_get_connection(struct client *cl)
141 {
142     return cl->connection;
143 }
144
145 struct session_database *client_get_database(struct client *cl)
146 {
147     return cl->database;
148 }
149
150 struct session *client_get_session(struct client *cl)
151 {
152     return cl->session;
153 }
154
155 const char *client_get_pquery(struct client *cl)
156 {
157     return cl->pquery;
158 }
159
160 void client_set_requestid(struct client *cl, int id)
161 {
162     cl->requestid = id;
163 }
164
165 int client_show_raw_begin(struct client *cl, int position,
166                           const char *syntax, const char *esn,
167                           void *data,
168                           void (*error_handler)(void *data, const char *addinfo),
169                           void (*record_handler)(void *data, const char *buf,
170                                                  size_t sz))
171 {
172     if (cl->show_raw)
173         return -1;
174     cl->show_raw = xmalloc(sizeof(*cl->show_raw));
175     cl->show_raw->position = position;
176     cl->show_raw->active = 0;
177     cl->show_raw->data = data;
178     cl->show_raw->error_handler = error_handler;
179     cl->show_raw->record_handler = record_handler;
180     if (syntax)
181         cl->show_raw->syntax = xstrdup(syntax);
182     else
183         cl->show_raw->syntax = 0;
184     if (esn)
185         cl->show_raw->esn = xstrdup(esn);
186     else
187         cl->show_raw->esn = 0;
188     client_continue(cl);
189     return 0;
190 }
191
192 void client_show_raw_reset(struct client *cl)
193 {
194     xfree(cl->show_raw);
195     cl->show_raw = 0;
196 }
197
198 static void client_show_raw_error(struct client *cl, const char *addinfo)
199 {
200     if (cl->show_raw)
201     {
202         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
203         client_show_raw_reset(cl);
204     }
205 }
206
207 static void client_show_raw_cancel(struct client *cl)
208 {
209     if (cl->show_raw)
210     {
211         cl->show_raw->error_handler(cl->show_raw->data, "cancel");
212         client_show_raw_reset(cl);
213     }
214 }
215
216 void client_send_raw_present(struct client *cl)
217 {
218     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
219     int toget = 1;
220     int start = cl->show_raw->position;
221
222     assert(cl->show_raw);
223
224     yaz_log(YLOG_DEBUG, "Trying to present %d record(s) from %d",
225             toget, start);
226
227     a->u.presentRequest->resultSetStartPoint = &start;
228     a->u.presentRequest->numberOfRecordsRequested = &toget;
229
230     if (cl->show_raw->syntax)  // syntax is optional
231         a->u.presentRequest->preferredRecordSyntax =
232             yaz_string_to_oid_odr(yaz_oid_std(),
233                                   CLASS_RECSYN, cl->show_raw->syntax,
234                                   global_parameters.odr_out);
235     if (cl->show_raw->esn)  // element set is optional
236     {
237         Z_ElementSetNames *elementSetNames =
238             odr_malloc(global_parameters.odr_out, sizeof(*elementSetNames));
239         Z_RecordComposition *compo = 
240             odr_malloc(global_parameters.odr_out, sizeof(*compo));
241         a->u.presentRequest->recordComposition = compo;
242
243         compo->which = Z_RecordComp_simple;
244         compo->u.simple = elementSetNames;
245
246         elementSetNames->which = Z_ElementSetNames_generic;
247         elementSetNames->u.generic = 
248             odr_strdup(global_parameters.odr_out, cl->show_raw->esn);
249     }
250     if (send_apdu(cl, a) >= 0)
251     {
252         cl->show_raw->active = 1;
253         cl->state = Client_Presenting;
254     }
255     else
256     {
257         client_show_raw_error(cl, "send_apdu failed");
258         cl->state = Client_Error;
259     }
260     odr_reset(global_parameters.odr_out);
261 }
262
263 void client_send_present(struct client *cl)
264 {
265     struct session_database *sdb = client_get_database(cl);
266     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
267     int toget;
268     int start = cl->records + 1;
269     char *recsyn;
270
271     toget = global_parameters.chunk;
272     if (toget > global_parameters.toget - cl->records)
273         toget = global_parameters.toget - cl->records;
274     if (toget > cl->hits - cl->records)
275         toget = cl->hits - cl->records;
276
277     yaz_log(YLOG_DEBUG, "Trying to present %d record(s) from %d",
278             toget, start);
279
280     a->u.presentRequest->resultSetStartPoint = &start;
281     a->u.presentRequest->numberOfRecordsRequested = &toget;
282
283     if ((recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX)))
284     {
285         a->u.presentRequest->preferredRecordSyntax =
286             yaz_string_to_oid_odr(yaz_oid_std(),
287                                   CLASS_RECSYN, recsyn,
288                                   global_parameters.odr_out);
289     }
290
291     if (send_apdu(cl, a) >= 0)
292         cl->state = Client_Presenting;
293     else
294         cl->state = Client_Error;
295     odr_reset(global_parameters.odr_out);
296 }
297
298
299 void client_send_search(struct client *cl)
300 {
301     struct session *se = client_get_session(cl);
302     struct session_database *sdb = client_get_database(cl);
303     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_searchRequest);
304     int ndb;
305     char **databaselist;
306     Z_Query *zquery;
307     int ssub = 0, lslb = 100000, mspn = 10;
308     char *recsyn = 0;
309     char *piggyback = 0;
310     char *queryenc = 0;
311     yaz_iconv_t iconv = 0;
312
313     yaz_log(YLOG_DEBUG, "Sending search to %s", sdb->database->url);
314
315     
316     // constructing RPN query
317     a->u.searchRequest->query = zquery = odr_malloc(global_parameters.odr_out,
318                                                     sizeof(Z_Query));
319     zquery->which = Z_Query_type_1;
320     zquery->u.type_1 = p_query_rpn(global_parameters.odr_out, 
321                                    client_get_pquery(cl));
322
323     // converting to target encoding
324     if ((queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING))){
325         iconv = yaz_iconv_open(queryenc, "UTF-8");
326         if (iconv){
327             yaz_query_charset_convert_rpnquery(zquery->u.type_1, 
328                                                global_parameters.odr_out, 
329                                                iconv);
330             yaz_iconv_close(iconv);
331         } else
332             yaz_log(YLOG_WARN, "Query encoding failed %s %s", 
333                     client_get_database(cl)->database->url, queryenc);
334     }
335
336     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
337         ;
338     databaselist = odr_malloc(global_parameters.odr_out, sizeof(char*) * ndb);
339     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
340         databaselist[ndb] = sdb->database->databases[ndb];
341
342     if (!(piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK)) 
343         || *piggyback == '1')
344     {
345         if ((recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX)))
346         {
347             a->u.searchRequest->preferredRecordSyntax =
348                 yaz_string_to_oid_odr(yaz_oid_std(),
349                                       CLASS_RECSYN, recsyn,
350                                       global_parameters.odr_out);
351         }
352         a->u.searchRequest->smallSetUpperBound = &ssub;
353         a->u.searchRequest->largeSetLowerBound = &lslb;
354         a->u.searchRequest->mediumSetPresentNumber = &mspn;
355     }
356     a->u.searchRequest->databaseNames = databaselist;
357     a->u.searchRequest->num_databaseNames = ndb;
358
359     
360     {  //scope for sending and logging queries 
361         WRBUF wbquery = wrbuf_alloc();
362         yaz_query_to_wrbuf(wbquery, a->u.searchRequest->query);
363
364
365         if (send_apdu(cl, a) >= 0)
366         {
367             client_set_state(cl, Client_Searching);
368             client_set_requestid(cl, se->requestid);
369             yaz_log(YLOG_LOG, "SearchRequest %s %s %s", 
370                     client_get_database(cl)->database->url,
371                     queryenc ? queryenc : "UTF-8",
372                     wrbuf_cstr(wbquery));
373         }
374         else {
375             client_set_state(cl, Client_Error);
376             yaz_log(YLOG_WARN, "Failed SearchRequest %s  %s %s", 
377                     client_get_database(cl)->database->url, 
378                     queryenc ? queryenc : "UTF-8",
379                     wrbuf_cstr(wbquery));
380         }
381         
382         wrbuf_destroy(wbquery);
383     }    
384
385     odr_reset(global_parameters.odr_out);
386 }
387
388 void client_init_response(struct client *cl, Z_APDU *a)
389 {
390     Z_InitResponse *r = a->u.initResponse;
391
392     yaz_log(YLOG_DEBUG, "Init response %s", cl->database->database->url);
393
394     if (*r->result)
395     {
396         cl->state = Client_Idle;
397     }
398     else
399         cl->state = Client_Failed; // FIXME need to do something to the connection
400 }
401
402
403 static void ingest_raw_records(struct client *cl, Z_Records *r)
404 {
405     Z_NamePlusRecordList *rlist;
406     Z_NamePlusRecord *npr;
407     xmlDoc *doc;
408     xmlChar *buf_out;
409     int len_out;
410     if (r->which != Z_Records_DBOSD)
411     {
412         client_show_raw_error(cl, "non-surrogate diagnostics");
413         return;
414     }
415
416     rlist = r->u.databaseOrSurDiagnostics;
417     if (rlist->num_records != 1 || !rlist->records || !rlist->records[0])
418     {
419         client_show_raw_error(cl, "no records");
420         return;
421     }
422     npr = rlist->records[0];
423     if (npr->which != Z_NamePlusRecord_databaseRecord)
424     {
425         client_show_raw_error(cl, "surrogate diagnostic");
426         return;
427     }
428
429     doc = record_to_xml(client_get_database(cl), npr->u.databaseRecord);
430     if (!doc)
431     {
432         client_show_raw_error(cl, "unable to convert record to xml");
433         return;
434     }
435
436     xmlDocDumpMemory(doc, &buf_out, &len_out);
437     xmlFreeDoc(doc);
438
439     cl->show_raw->record_handler(cl->show_raw->data,
440                                  (const char *) buf_out, len_out);
441     
442     xmlFree(buf_out);
443     xfree(cl->show_raw);
444     cl->show_raw = 0;
445 }
446
447 static void ingest_records(struct client *cl, Z_Records *r)
448 {
449 #if USE_TIMING
450     yaz_timing_t t = yaz_timing_create();
451 #endif
452     struct record *rec;
453     struct session *s = client_get_session(cl);
454     Z_NamePlusRecordList *rlist;
455     int i;
456
457     if (r->which != Z_Records_DBOSD)
458         return;
459     rlist = r->u.databaseOrSurDiagnostics;
460     for (i = 0; i < rlist->num_records; i++)
461     {
462         Z_NamePlusRecord *npr = rlist->records[i];
463
464         cl->records++;
465         if (npr->which != Z_NamePlusRecord_databaseRecord)
466         {
467             yaz_log(YLOG_WARN, 
468                     "Unexpected record type, probably diagnostic %s",
469                     cl->database->database->url);
470             continue;
471         }
472
473         rec = ingest_record(cl, npr->u.databaseRecord, cl->records);
474         if (!rec)
475             continue;
476     }
477     if (rlist->num_records)
478         session_alert_watch(s, SESSION_WATCH_RECORDS);
479
480 #if USE_TIMING
481     yaz_timing_stop(t);
482     yaz_log(YLOG_LOG, "ingest_records %6.5f %3.2f %3.2f", 
483             yaz_timing_get_real(t), yaz_timing_get_user(t),
484             yaz_timing_get_sys(t));
485     yaz_timing_destroy(&t);
486 #endif
487 }
488
489
490 void client_search_response(struct client *cl, Z_APDU *a)
491 {
492     struct session *se = cl->session;
493     Z_SearchResponse *r = a->u.searchResponse;
494
495     yaz_log(YLOG_DEBUG, "Search response %s (status=%d)", 
496             cl->database->database->url, *r->searchStatus);
497
498     if (*r->searchStatus)
499     {
500         cl->hits = *r->resultCount;
501         se->total_hits += cl->hits;
502         if (r->presentStatus && !*r->presentStatus && r->records)
503         {
504             yaz_log(YLOG_DEBUG, "Records in search response %s", 
505                     cl->database->database->url);
506             ingest_records(cl, r->records);
507         }
508         cl->state = Client_Idle;
509     }
510     else
511     {          /*"FAILED"*/
512         cl->hits = 0;
513         cl->state = Client_Error;
514         if (r->records) {
515             Z_Records *recs = r->records;
516             if (recs->which == Z_Records_NSD)
517             {
518                 yaz_log(YLOG_WARN,  
519                     "Search response: Non-surrogate diagnostic %s (%d)", 
520                     cl->database->database->url, 
521                     *recs->u.nonSurrogateDiagnostic->condition); 
522                 cl->diagnostic = *recs->u.nonSurrogateDiagnostic->condition;
523                 cl->state = Client_Error;
524             }
525         }
526     }
527 }
528
529 void client_present_response(struct client *cl, Z_APDU *a)
530 {
531     Z_PresentResponse *r = a->u.presentResponse;
532
533     if (r->records) {
534         Z_Records *recs = r->records;
535         if (recs->which == Z_Records_NSD)
536         {
537             yaz_log(YLOG_WARN, "Non-surrogate diagnostic %s",
538                     cl->database->database->url);
539             cl->diagnostic = *recs->u.nonSurrogateDiagnostic->condition;
540             cl->state = Client_Error;
541             client_show_raw_error(cl, "non surrogate diagnostics");
542         }
543     }
544
545     if (!*r->presentStatus && cl->state != Client_Error)
546     {
547         yaz_log(YLOG_DEBUG, "Good Present response %s",
548                 cl->database->database->url);
549
550         // we can mix show raw and normal show ..
551         if (cl->show_raw && cl->show_raw->active)
552         {
553             cl->show_raw->active = 0; // no longer active
554             ingest_raw_records(cl, r->records);
555         }
556         else
557             ingest_records(cl, r->records);
558         cl->state = Client_Idle;
559     }
560     else if (*r->presentStatus) 
561     {
562         yaz_log(YLOG_WARN, "Bad Present response %s",
563                 cl->database->database->url);
564         cl->state = Client_Error;
565         client_show_raw_error(cl, "bad present response");
566     }
567 }
568
569 void client_close_response(struct client *cl, Z_APDU *a)
570 {
571     struct connection *co = cl->connection;
572     /* Z_Close *r = a->u.close; */
573
574     yaz_log(YLOG_WARN, "Close response %s", cl->database->database->url);
575
576     cl->state = Client_Failed;
577     connection_destroy(co);
578 }
579
580 int client_is_our_response(struct client *cl)
581 {
582     struct session *se = client_get_session(cl);
583
584     if (cl && (cl->requestid == se->requestid || 
585                cl->state == Client_Initializing))
586         return 1;
587     return 0;
588 }
589
590 // Set authentication token in init if one is set for the client
591 // TODO: Extend this to handle other schemes than open (should be simple)
592 static void init_authentication(struct client *cl, Z_InitRequest *req)
593 {
594     struct session_database *sdb = client_get_database(cl);
595     char *auth = session_setting_oneval(sdb, PZ_AUTHENTICATION);
596
597     if (*auth)
598     {
599         struct connection *co = client_get_connection(cl);
600         struct session *se = client_get_session(cl);
601         Z_IdAuthentication *idAuth = odr_malloc(global_parameters.odr_out,
602                 sizeof(*idAuth));
603         idAuth->which = Z_IdAuthentication_open;
604         idAuth->u.open = auth;
605         req->idAuthentication = idAuth;
606         connection_set_authentication(co, nmem_strdup(se->session_nmem, auth));
607     }
608 }
609
610 static void init_zproxy(struct client *cl, Z_InitRequest *req)
611 {
612     struct session_database *sdb = client_get_database(cl);
613     char *ztarget = sdb->database->url;
614     //char *ztarget = sdb->url;    
615     char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
616
617     if (*zproxy)
618         yaz_oi_set_string_oid(&req->otherInfo,
619                               global_parameters.odr_out,
620                               yaz_oid_userinfo_proxy,
621                               1, ztarget);
622 }
623
624
625 static void client_init_request(struct client *cl)
626 {
627     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_initRequest);
628
629     a->u.initRequest->implementationId = global_parameters.implementationId;
630     a->u.initRequest->implementationName = global_parameters.implementationName;
631     a->u.initRequest->implementationVersion =
632         global_parameters.implementationVersion;
633     ODR_MASK_SET(a->u.initRequest->options, Z_Options_search);
634     ODR_MASK_SET(a->u.initRequest->options, Z_Options_present);
635     ODR_MASK_SET(a->u.initRequest->options, Z_Options_namedResultSets);
636
637     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_1);
638     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_2);
639     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_3);
640
641     init_authentication(cl, a->u.initRequest);
642     init_zproxy(cl, a->u.initRequest);
643
644     if (send_apdu(cl, a) >= 0)
645         client_set_state(cl, Client_Initializing);
646     else
647         client_set_state(cl, Client_Error);
648     odr_reset(global_parameters.odr_out);
649 }
650
651 void client_continue(struct client *cl)
652 {
653     if (cl->state == Client_Connected) {
654         client_init_request(cl);
655     }
656
657     if (cl->state == Client_Idle)
658     {
659         struct session *se = client_get_session(cl);
660         if (cl->requestid != se->requestid && cl->pquery) {
661             // we'll have to abort this because result set is to be deleted
662             client_show_raw_cancel(cl);   
663             client_send_search(cl);
664         }
665         else if (cl->show_raw)
666         {
667             client_send_raw_present(cl);
668         }
669         else if (cl->hits > 0 && cl->records < global_parameters.toget &&
670             cl->records < cl->hits) {
671             client_send_present(cl);
672         }
673     }
674 }
675
676 struct client *client_create(void)
677 {
678     struct client *r;
679     if (client_freelist)
680     {
681         r = client_freelist;
682         client_freelist = client_freelist->next;
683     }
684     else
685         r = xmalloc(sizeof(struct client));
686     r->pquery = 0;
687     r->database = 0;
688     r->connection = 0;
689     r->session = 0;
690     r->hits = 0;
691     r->records = 0;
692     r->setno = 0;
693     r->requestid = -1;
694     r->diagnostic = 0;
695     r->state = Client_Disconnected;
696     r->show_raw = 0;
697     r->next = 0;
698     return r;
699 }
700
701 void client_destroy(struct client *c)
702 {
703     struct session *se = c->session;
704     if (c == se->clients)
705         se->clients = c->next;
706     else
707     {
708         struct client *cc;
709         for (cc = se->clients; cc && cc->next != c; cc = cc->next)
710             ;
711         if (cc)
712             cc->next = c->next;
713     }
714     xfree(c->pquery);
715
716     if (c->connection)
717         connection_release(c->connection);
718     c->next = client_freelist;
719     client_freelist = c;
720 }
721
722 void client_set_connection(struct client *cl, struct connection *con)
723 {
724     cl->connection = con;
725 }
726
727 void client_disconnect(struct client *cl)
728 {
729     if (cl->state != Client_Idle)
730         cl->state = Client_Disconnected;
731     client_set_connection(cl, 0);
732 }
733
734 // Extract terms from query into null-terminated termlist
735 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
736 {
737     int num = 0;
738
739     pull_terms(nmem, query, termlist, &num);
740     termlist[num] = 0;
741 }
742
743 // Initialize CCL map for a target
744 static CCL_bibset prepare_cclmap(struct client *cl)
745 {
746     struct session_database *sdb = client_get_database(cl);
747     struct setting *s;
748     CCL_bibset res;
749
750     if (!sdb->settings)
751         return 0;
752     res = ccl_qual_mk();
753     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
754     {
755         char *p = strchr(s->name + 3, ':');
756         if (!p)
757         {
758             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
759             ccl_qual_rm(&res);
760             return 0;
761         }
762         p++;
763         ccl_qual_fitem(res, s->value, p);
764     }
765     return res;
766 }
767
768 // Parse the query given the settings specific to this client
769 int client_parse_query(struct client *cl, const char *query)
770 {
771     struct session *se = client_get_session(cl);
772     struct ccl_rpn_node *cn;
773     int cerror, cpos;
774     CCL_bibset ccl_map = prepare_cclmap(cl);
775
776     if (!ccl_map)
777         return -1;
778     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
779     ccl_qual_rm(&ccl_map);
780     if (!cn)
781     {
782         cl->state = Client_Error;
783         yaz_log(YLOG_WARN, "Failed to parse query for %s",
784                          client_get_database(cl)->database->url);
785         return -1;
786     }
787     wrbuf_rewind(se->wrbuf);
788     ccl_pquery(se->wrbuf, cn);
789     xfree(cl->pquery);
790     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
791
792     if (!se->relevance)
793     {
794         // Initialize relevance structure with query terms
795         char *p[512];
796         extract_terms(se->nmem, cn, p);
797         se->relevance = relevance_create(client_get_database(cl)->pct,
798                                          se->nmem, (const char **) p,
799                                          se->expected_maxrecs);
800     }
801
802     ccl_rpn_delete(cn);
803     return 0;
804 }
805
806 void client_set_session(struct client *cl, struct session *se)
807 {
808     cl->session = se;
809     cl->next = se->clients;
810     se->clients = cl;
811 }
812
813 int client_is_active(struct client *cl)
814 {
815     if (cl->connection && (cl->state == Client_Connecting ||
816                            cl->state == Client_Initializing ||
817                            cl->state == Client_Searching ||
818                            cl->state == Client_Presenting))
819         return 1;
820     return 0;
821 }
822
823 struct client *client_next_in_session(struct client *cl)
824 {
825     if (cl)
826         return cl->next;
827     return 0;
828
829 }
830
831 int client_get_hits(struct client *cl)
832 {
833     return cl->hits;
834 }
835
836 int client_get_num_records(struct client *cl)
837 {
838     return cl->records;
839 }
840
841 int client_get_diagnostic(struct client *cl)
842 {
843     return cl->diagnostic;
844 }
845
846 void client_set_database(struct client *cl, struct session_database *db)
847 {
848     cl->database = db;
849 }
850
851 struct host *client_get_host(struct client *cl)
852 {
853     return client_get_database(cl)->database->host;
854 }
855
856 const char *client_get_url(struct client *cl)
857 {
858     return client_get_database(cl)->database->url;
859 }
860
861 /*
862  * Local variables:
863  * c-basic-offset: 4
864  * indent-tabs-mode: nil
865  * End:
866  * vim: shiftwidth=4 tabstop=8 expandtab
867  */