Added support for turbo xml (txml)
[pazpar2-moved-to-github.git] / src / client.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2010 Index Data
3
4 Pazpar2 is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 /** \file client.c
21     \brief Z39.50 client 
22 */
23
24 #if HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 #include <pthread.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #if HAVE_SYS_TIME_H
32 #include <sys/time.h>
33 #endif
34 #if HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37 #include <signal.h>
38 #include <assert.h>
39
40 #include <yaz/marcdisp.h>
41 #include <yaz/comstack.h>
42 #include <yaz/tcpip.h>
43 #include <yaz/proto.h>
44 #include <yaz/readconf.h>
45 #include <yaz/pquery.h>
46 #include <yaz/otherinfo.h>
47 #include <yaz/yaz-util.h>
48 #include <yaz/nmem.h>
49 #include <yaz/query-charset.h>
50 #include <yaz/querytowrbuf.h>
51 #include <yaz/oid_db.h>
52 #include <yaz/diagbib1.h>
53 #include <yaz/snprintf.h>
54 #include <yaz/rpn2cql.h>
55
56 #define USE_TIMING 0
57 #if USE_TIMING
58 #include <yaz/timing.h>
59 #endif
60
61 #include "ppmutex.h"
62 #include "session.h"
63 #include "parameters.h"
64 #include "client.h"
65 #include "connection.h"
66 #include "settings.h"
67 #include "relevance.h"
68 #include "incref.h"
69
70 /** \brief Represents client state for a connection to one search target */
71 struct client {
72     struct session_database *database;
73     struct connection *connection;
74     struct session *session;
75     char *pquery; // Current search
76     char *cqlquery; // used for SRU targets only
77     Odr_int hits;
78     int record_offset;
79     int maxrecs;
80     int startrecs;
81     int diagnostic;
82     enum client_state state;
83     struct show_raw *show_raw;
84     struct client *next;     // next client in session or next in free list
85     ZOOM_resultset resultset;
86     YAZ_MUTEX mutex;
87     int ref_count;
88 };
89
90 struct show_raw {
91     int active; // whether this request has been sent to the server
92     int position;
93     int binary;
94     char *syntax;
95     char *esn;
96     void (*error_handler)(void *data, const char *addinfo);
97     void (*record_handler)(void *data, const char *buf, size_t sz);
98     void *data;
99     struct show_raw *next;
100 };
101
102 static const char *client_states[] = {
103     "Client_Connecting",
104     "Client_Idle",
105     "Client_Working",
106     "Client_Error",
107     "Client_Failed",
108     "Client_Disconnected"
109 };
110
111 const char *client_get_state_str(struct client *cl)
112 {
113     return client_states[cl->state];
114 }
115
116 enum client_state client_get_state(struct client *cl)
117 {
118     return cl->state;
119 }
120
121 void client_set_state(struct client *cl, enum client_state st)
122 {
123     cl->state = st;
124     /* no need to check for all client being non-active if this one
125        already is. Note that session_active_clients also LOCKS session */
126 #if 0
127     if (!client_is_active(cl) && cl->session)
128     {
129         int no_active = session_active_clients(cl->session);
130         if (no_active == 0)
131             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
132     }
133 #endif
134 }
135
136 static void client_show_raw_error(struct client *cl, const char *addinfo);
137
138 // Close connection and set state to error
139 void client_fatal(struct client *cl)
140 {
141     yaz_log(YLOG_WARN, "Fatal error from %s", client_get_url(cl));
142     connection_destroy(cl->connection);
143     client_set_state(cl, Client_Error);
144 }
145
146 struct connection *client_get_connection(struct client *cl)
147 {
148     return cl->connection;
149 }
150
151 struct session_database *client_get_database(struct client *cl)
152 {
153     return cl->database;
154 }
155
156 struct session *client_get_session(struct client *cl)
157 {
158     return cl->session;
159 }
160
161 const char *client_get_pquery(struct client *cl)
162 {
163     return cl->pquery;
164 }
165
166 static void client_send_raw_present(struct client *cl);
167 static int nativesyntax_to_type(struct session_database *sdb, char *type,
168                                 ZOOM_record rec);
169
170 static void client_show_immediate(
171     ZOOM_resultset resultset, struct session_database *sdb, int position,
172     void *data,
173     void (*error_handler)(void *data, const char *addinfo),
174     void (*record_handler)(void *data, const char *buf, size_t sz),
175     int binary)
176 {
177     ZOOM_record rec = 0;
178     char type[80];
179     const char *buf;
180     int len;
181
182     if (!resultset)
183     {
184         error_handler(data, "no resultset");
185         return;
186     }
187     rec = ZOOM_resultset_record(resultset, position-1);
188     if (!rec)
189     {
190         error_handler(data, "no record");
191         return;
192     }
193     if (binary)
194         strcpy(type, "raw");
195     else
196         nativesyntax_to_type(sdb, type, rec);
197     buf = ZOOM_record_get(rec, type, &len);
198     if (!buf)
199     {
200         error_handler(data, "no record");
201         return;
202     }
203     record_handler(data, buf, len);
204 }
205
206
207 int client_show_raw_begin(struct client *cl, int position,
208                           const char *syntax, const char *esn,
209                           void *data,
210                           void (*error_handler)(void *data, const char *addinfo),
211                           void (*record_handler)(void *data, const char *buf,
212                                                  size_t sz),
213                           int binary)
214 {
215     if (syntax == 0 && esn == 0)
216         client_show_immediate(cl->resultset, client_get_database(cl),
217                               position, data,
218                               error_handler, record_handler,
219                               binary);
220     else
221     {
222         struct show_raw *rr, **rrp;
223
224         if (!cl->connection)
225             return -1;
226     
227
228         rr = xmalloc(sizeof(*rr));
229         rr->position = position;
230         rr->active = 0;
231         rr->data = data;
232         rr->error_handler = error_handler;
233         rr->record_handler = record_handler;
234         rr->binary = binary;
235         if (syntax)
236             rr->syntax = xstrdup(syntax);
237         else
238             rr->syntax = 0;
239         if (esn)
240             rr->esn = xstrdup(esn);
241         else
242             rr->esn = 0;
243         rr->next = 0;
244         
245         for (rrp = &cl->show_raw; *rrp; rrp = &(*rrp)->next)
246             ;
247         *rrp = rr;
248         
249         if (cl->state == Client_Failed)
250         {
251             client_show_raw_error(cl, "client failed");
252         }
253         else if (cl->state == Client_Disconnected)
254         {
255             client_show_raw_error(cl, "client disconnected");
256         }
257         else
258         {
259             client_send_raw_present(cl);
260         }
261     }
262     return 0;
263 }
264
265 static void client_show_raw_delete(struct show_raw *r)
266 {
267     xfree(r->syntax);
268     xfree(r->esn);
269     xfree(r);
270 }
271
272 void client_show_raw_remove(struct client *cl, void *data)
273 {
274     struct show_raw *rr = data;
275     struct show_raw **rrp = &cl->show_raw;
276     while (*rrp != rr)
277         rrp = &(*rrp)->next;
278     if (*rrp)
279     {
280         *rrp = rr->next;
281         client_show_raw_delete(rr);
282     }
283 }
284
285 void client_show_raw_dequeue(struct client *cl)
286 {
287     struct show_raw *rr = cl->show_raw;
288
289     cl->show_raw = rr->next;
290     client_show_raw_delete(rr);
291 }
292
293 static void client_show_raw_error(struct client *cl, const char *addinfo)
294 {
295     while (cl->show_raw)
296     {
297         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
298         client_show_raw_dequeue(cl);
299     }
300 }
301
302 static void client_send_raw_present(struct client *cl)
303 {
304     struct session_database *sdb = client_get_database(cl);
305     struct connection *co = client_get_connection(cl);
306     ZOOM_resultset set = cl->resultset;
307
308     int offset = cl->show_raw->position;
309     const char *syntax = 0;
310     const char *elements = 0;
311
312     assert(cl->show_raw);
313     assert(set);
314
315     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
316             client_get_url(cl), 1, offset);
317
318     if (cl->show_raw->syntax)
319         syntax = cl->show_raw->syntax;
320     else
321         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
322     ZOOM_resultset_option_set(set, "preferredRecordSyntax", syntax);
323
324     if (cl->show_raw->esn)
325         elements = cl->show_raw->esn;
326     else
327         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
328     if (elements && *elements)
329         ZOOM_resultset_option_set(set, "elementSetName", elements);
330
331     ZOOM_resultset_records(set, 0, offset-1, 1);
332     cl->show_raw->active = 1;
333
334     connection_continue(co);
335 }
336
337 static int nativesyntax_to_type(struct session_database *sdb, char *type,
338                                 ZOOM_record rec)
339 {
340     const char *s = session_setting_oneval(sdb, PZ_NATIVESYNTAX);
341
342     if (s && *s)
343     {
344         if (!strncmp(s, "iso2709", 7))
345         {
346             const char *cp = strchr(s, ';');
347             yaz_snprintf(type, 80, "xml; charset=%s", cp ? cp+1 : "marc-8s");
348         }
349         else if (!strncmp(s, "xml", 3))
350         {
351             strcpy(type, "xml");
352         }
353         else if (!strncmp(s, "txml", 4))
354         {
355             const char *cp = strchr(s, ';');
356             yaz_snprintf(type, 80, "txml; charset=%s", cp ? cp+1 : "marc-8s");
357         }
358         else
359             return -1;
360         return 0;
361     }
362     else  /* attempt to deduce structure */
363     {
364         const char *syntax = ZOOM_record_get(rec, "syntax", NULL);
365         if (syntax)
366         {
367             if (!strcmp(syntax, "XML"))
368             {
369                 strcpy(type, "xml");
370                 return 0;
371             }
372             else if (!strcmp(syntax, "TXML"))
373                 {
374                     strcpy(type, "txml");
375                     return 0;
376                 }
377             else if (!strcmp(syntax, "USmarc") || !strcmp(syntax, "MARC21"))
378             {
379                 strcpy(type, "xml; charset=marc8-s");
380                 return 0;
381             }
382             else return -1;
383         }
384         else return -1;
385     }
386 }
387
388 static void ingest_raw_record(struct client *cl, ZOOM_record rec)
389 {
390     const char *buf;
391     int len;
392     char type[80];
393
394     if (cl->show_raw->binary)
395         strcpy(type, "raw");
396     else
397     {
398         struct session_database *sdb = client_get_database(cl);
399         nativesyntax_to_type(sdb, type, rec);
400     }
401
402     buf = ZOOM_record_get(rec, type, &len);
403     cl->show_raw->record_handler(cl->show_raw->data,  buf, len);
404     client_show_raw_dequeue(cl);
405 }
406
407 void client_search_response(struct client *cl)
408 {
409     struct connection *co = cl->connection;
410     struct session *se = cl->session;
411     ZOOM_connection link = connection_get_link(co);
412     ZOOM_resultset resultset = cl->resultset;
413     const char *error, *addinfo = 0;
414     
415     if (ZOOM_connection_error(link, &error, &addinfo))
416     {
417         cl->hits = 0;
418         client_set_state(cl, Client_Error);
419         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
420                 error, addinfo, client_get_url(cl));
421     }
422     else
423     {
424         cl->record_offset = cl->startrecs;
425         cl->hits = ZOOM_resultset_size(resultset);
426         if (se)
427             se->total_hits += cl->hits;
428     }
429 }
430
431 void client_got_records(struct client *cl)
432 {
433     if (cl->session)
434     {
435         session_alert_watch(cl->session, SESSION_WATCH_SHOW);
436         session_alert_watch(cl->session, SESSION_WATCH_RECORD);
437     }
438 }
439
440 void client_record_response(struct client *cl)
441 {
442     static pthread_mutex_t ingest_mutex = PTHREAD_MUTEX_INITIALIZER;
443     static int ingest_counter = 0, ingest_max = 0;
444     struct connection *co = cl->connection;
445     ZOOM_connection link = connection_get_link(co);
446     ZOOM_resultset resultset = cl->resultset;
447     const char *error, *addinfo;
448
449     if (ZOOM_connection_error(link, &error, &addinfo))
450     {
451         client_set_state(cl, Client_Error);
452         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
453             error, addinfo, client_get_url(cl));
454     }
455     else
456     {
457         ZOOM_record rec = 0;
458         const char *msg, *addinfo;
459         
460         if (cl->show_raw && cl->show_raw->active)
461         {
462             if ((rec = ZOOM_resultset_record(resultset,
463                                              cl->show_raw->position-1)))
464             {
465                 cl->show_raw->active = 0;
466                 ingest_raw_record(cl, rec);
467             }
468             else
469             {
470                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
471                         cl->show_raw->position-1);
472             }
473         }
474         else
475         {
476             int offset = cl->record_offset;
477             if ((rec = ZOOM_resultset_record(resultset, offset)))
478             {
479                 cl->record_offset++;
480                 if (cl->session == 0)
481                     ;
482                 else if (ZOOM_record_error(rec, &msg, &addinfo, 0))
483                     yaz_log(YLOG_WARN, "Record error %s (%s): %s (rec #%d)",
484                             error, addinfo, client_get_url(cl),
485                             cl->record_offset);
486                 else
487                 {
488                     struct session_database *sdb = client_get_database(cl);
489                     NMEM nmem = nmem_create();
490                     const char *xmlrec;
491                     int new_max = 0;
492                     char type[80];
493                     yaz_log(YLOG_LOG, "Record ingest begin client=%p session=%p", cl, cl->session);
494                     pthread_mutex_lock(&ingest_mutex);
495                     ++ingest_counter;
496                     if (ingest_counter > ingest_max)
497                     {
498                         ingest_max = ingest_counter;
499                         new_max = ingest_max;
500                     }
501                     pthread_mutex_unlock(&ingest_mutex);
502                     if (new_max)
503                         yaz_log(YLOG_LOG, "New max client=%p new_max=%d", cl, new_max);
504                     if (nativesyntax_to_type(sdb, type, rec))
505                         yaz_log(YLOG_WARN, "Failed to determine record type");
506                     xmlrec = ZOOM_record_get(rec, type, NULL);
507                     if (!xmlrec)
508                         yaz_log(YLOG_WARN, "ZOOM_record_get failed from %s",
509                                 client_get_url(cl));
510                     else
511                     {
512                         if (ingest_record(cl, xmlrec, cl->record_offset, nmem))
513                             yaz_log(YLOG_WARN, "Failed to ingest from %s",
514                                     client_get_url(cl));
515                     }
516                     pthread_mutex_lock(&ingest_mutex);
517                     --ingest_counter;
518                     pthread_mutex_unlock(&ingest_mutex);
519                     nmem_destroy(nmem);
520                     yaz_log(YLOG_LOG, "Record ingest end client=%p session=%p max=%d", cl, cl->session, ingest_max);
521                 }
522             }
523             else
524             {
525                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
526                         offset);
527             }
528         }
529     }
530 }
531
532 void client_start_search(struct client *cl)
533 {
534     struct session_database *sdb = client_get_database(cl);
535     struct connection *co = client_get_connection(cl);
536     ZOOM_connection link = connection_get_link(co);
537     ZOOM_resultset rs;
538     char *databaseName = sdb->database->databases[0];
539     const char *opt_piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK);
540     const char *opt_queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING);
541     const char *opt_elements = session_setting_oneval(sdb, PZ_ELEMENTS);
542     const char *opt_requestsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
543     const char *opt_maxrecs = session_setting_oneval(sdb, PZ_MAXRECS);
544     const char *opt_sru = session_setting_oneval(sdb, PZ_SRU);
545     const char *opt_sort = session_setting_oneval(sdb, PZ_SORT);
546     char maxrecs_str[24], startrecs_str[24];
547
548     assert(link);
549
550     cl->hits = -1;
551     cl->record_offset = 0;
552     cl->diagnostic = 0;
553     client_set_state(cl, Client_Working);
554
555     if (*opt_piggyback)
556         ZOOM_connection_option_set(link, "piggyback", opt_piggyback);
557     else
558         ZOOM_connection_option_set(link, "piggyback", "1");
559     if (*opt_queryenc)
560         ZOOM_connection_option_set(link, "rpnCharset", opt_queryenc);
561     if (*opt_sru && *opt_elements)
562         ZOOM_connection_option_set(link, "schema", opt_elements);
563     else if (*opt_elements)
564         ZOOM_connection_option_set(link, "elementSetName", opt_elements);
565     if (*opt_requestsyn)
566         ZOOM_connection_option_set(link, "preferredRecordSyntax", opt_requestsyn);
567
568     if (!*opt_maxrecs)
569     {
570         sprintf(maxrecs_str, "%d", cl->maxrecs);
571         opt_maxrecs = maxrecs_str;
572     }
573     ZOOM_connection_option_set(link, "count", opt_maxrecs);
574
575
576     if (atoi(opt_maxrecs) > 20)
577         ZOOM_connection_option_set(link, "presentChunk", "20");
578     else
579         ZOOM_connection_option_set(link, "presentChunk", opt_maxrecs);
580
581     sprintf(startrecs_str, "%d", cl->startrecs);
582     ZOOM_connection_option_set(link, "start", startrecs_str);
583
584     if (databaseName)
585         ZOOM_connection_option_set(link, "databaseName", databaseName);
586
587     if (cl->cqlquery)
588     {
589         ZOOM_query q = ZOOM_query_create();
590         yaz_log(YLOG_LOG, "Search %s CQL: %s", sdb->database->url, cl->cqlquery);
591         ZOOM_query_cql(q, cl->cqlquery);
592         if (*opt_sort)
593             ZOOM_query_sortby(q, opt_sort);
594         rs = ZOOM_connection_search(link, q);
595         ZOOM_query_destroy(q);
596     }
597     else
598     {
599         yaz_log(YLOG_LOG, "Search %s PQF: %s", sdb->database->url, cl->pquery);
600         rs = ZOOM_connection_search_pqf(link, cl->pquery);
601     }
602     ZOOM_resultset_destroy(cl->resultset);
603     cl->resultset = rs;
604     connection_continue(co);
605 }
606
607 struct client *client_create(void)
608 {
609     struct client *r = xmalloc(sizeof(*r));
610     r->maxrecs = 100;
611     r->startrecs = 0;
612     r->pquery = 0;
613     r->cqlquery = 0;
614     r->database = 0;
615     r->connection = 0;
616     r->session = 0;
617     r->hits = 0;
618     r->record_offset = 0;
619     r->diagnostic = 0;
620     r->state = Client_Disconnected;
621     r->show_raw = 0;
622     r->resultset = 0;
623     r->next = 0;
624     r->mutex = 0;
625     pazpar2_mutex_create(&r->mutex, "client");
626
627     r->ref_count = 1;
628     
629     return r;
630 }
631
632 void client_incref(struct client *c)
633 {
634     pazpar2_incref(&c->ref_count, c->mutex);
635     yaz_log(YLOG_DEBUG, "client_incref %s %d", client_get_url(c), c->ref_count);
636 }
637
638 int client_destroy(struct client *c)
639 {
640     if (c)
641     {
642         yaz_log(YLOG_DEBUG, "client_destroy %s %d",
643                 client_get_url(c), c->ref_count);
644         if (!pazpar2_decref(&c->ref_count, c->mutex))
645         {
646             c->next = 0;
647             xfree(c->pquery);
648             c->pquery = 0;
649             xfree(c->cqlquery);
650             c->cqlquery = 0;
651
652             ZOOM_resultset_destroy(c->resultset);
653             yaz_mutex_destroy(&c->mutex);
654             xfree(c);
655             return 1;
656         }
657     }
658     return 0;
659 }
660
661 void client_set_connection(struct client *cl, struct connection *con)
662 {
663     if (con)
664     {
665         assert(cl->connection == 0);
666         cl->connection = con;
667         client_incref(cl);
668     }
669     else
670     {
671         cl->connection = con;
672         client_destroy(cl);
673     }
674 }
675
676 void client_disconnect(struct client *cl)
677 {
678     if (cl->state != Client_Idle)
679         client_set_state(cl, Client_Disconnected);
680     client_set_connection(cl, 0);
681 }
682
683 // Extract terms from query into null-terminated termlist
684 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
685 {
686     int num = 0;
687
688     pull_terms(nmem, query, termlist, &num);
689     termlist[num] = 0;
690 }
691
692 // Initialize CCL map for a target
693 static CCL_bibset prepare_cclmap(struct client *cl)
694 {
695     struct session_database *sdb = client_get_database(cl);
696     struct setting *s;
697     CCL_bibset res;
698
699     if (!sdb->settings)
700         return 0;
701     res = ccl_qual_mk();
702     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
703     {
704         char *p = strchr(s->name + 3, ':');
705         if (!p)
706         {
707             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
708             ccl_qual_rm(&res);
709             return 0;
710         }
711         p++;
712         ccl_qual_fitem(res, s->value, p);
713     }
714     return res;
715 }
716
717 // returns a xmalloced CQL query corresponding to the pquery in client
718 static char *make_cqlquery(struct client *cl)
719 {
720     cql_transform_t cqlt = cql_transform_create();
721     Z_RPNQuery *zquery;
722     char *r;
723     WRBUF wrb = wrbuf_alloc();
724     int status;
725     ODR odr_out = odr_createmem(ODR_ENCODE);
726
727     zquery = p_query_rpn(odr_out, cl->pquery);
728     yaz_log(YLOG_LOG, "PQF: %s", cl->pquery);
729     if ((status = cql_transform_rpn2cql_wrbuf(cqlt, wrb, zquery)))
730     {
731         yaz_log(YLOG_WARN, "Failed to generate CQL query, code=%d", status);
732         r = 0;
733     }
734     else
735     {
736         r = xstrdup(wrbuf_cstr(wrb));
737     }     
738     wrbuf_destroy(wrb);
739     odr_destroy(odr_out);
740     cql_transform_close(cqlt);
741     return r;
742 }
743
744 // Parse the query given the settings specific to this client
745 int client_parse_query(struct client *cl, const char *query)
746 {
747     struct session *se = client_get_session(cl);
748     struct session_database *sdb = client_get_database(cl);
749     struct ccl_rpn_node *cn;
750     int cerror, cpos;
751     CCL_bibset ccl_map = prepare_cclmap(cl);
752     const char *sru = session_setting_oneval(sdb, PZ_SRU);
753     const char *pqf_prefix = session_setting_oneval(sdb, PZ_PQF_PREFIX);
754     const char *pqf_strftime = session_setting_oneval(sdb, PZ_PQF_STRFTIME);
755
756     if (!ccl_map)
757         return -1;
758
759     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
760     ccl_qual_rm(&ccl_map);
761     if (!cn)
762     {
763         client_set_state(cl, Client_Error);
764         yaz_log(YLOG_WARN, "Failed to parse CCL query %s for %s",
765                 query,
766                 client_get_database(cl)->database->url);
767         return -1;
768     }
769     wrbuf_rewind(se->wrbuf);
770     if (*pqf_prefix)
771     {
772         wrbuf_puts(se->wrbuf, pqf_prefix);
773         wrbuf_puts(se->wrbuf, " ");
774     }
775     if (!pqf_strftime || !*pqf_strftime)
776         ccl_pquery(se->wrbuf, cn);
777     else
778     {
779         time_t cur_time = time(0);
780         struct tm *tm =  localtime(&cur_time);
781         char tmp_str[300];
782         const char *cp = tmp_str;
783
784         /* see man strftime(3) for things .. In particular %% gets converted
785          to %.. And That's our original query .. */
786         strftime(tmp_str, sizeof(tmp_str)-1, pqf_strftime, tm);
787         for (; *cp; cp++)
788         {
789             if (cp[0] == '%')
790                 ccl_pquery(se->wrbuf, cn);
791             else
792                 wrbuf_putc(se->wrbuf, cp[0]);
793         }
794     }
795     xfree(cl->pquery);
796     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
797
798     xfree(cl->cqlquery);
799     if (*sru)
800     {
801         if (!(cl->cqlquery = make_cqlquery(cl)))
802             return -1;
803     }
804     else
805         cl->cqlquery = 0;
806
807     if (!se->relevance)
808     {
809         // Initialize relevance structure with query terms
810         char *p[512];
811         extract_terms(se->nmem, cn, p);
812         se->relevance = relevance_create(
813             se->service->relevance_pct,
814             se->nmem, (const char **) p);
815     }
816
817     ccl_rpn_delete(cn);
818     return 0;
819 }
820
821
822 void client_remove_from_session(struct client *c)
823 {
824     struct session *se;
825     client_incref(c);
826
827     se = c->session;
828     assert(se);
829     if (se)
830     {
831         struct client **ccp = &se->clients;
832         
833         while (*ccp && *ccp != c)
834             ccp = &(*ccp)->next;
835         assert(*ccp == c);
836         *ccp = c->next;
837         
838         c->database = 0;
839         c->session = 0;
840         c->next = 0;
841     }
842     client_destroy(c);
843 }
844
845 void client_set_session(struct client *cl, struct session *se)
846 {
847     cl->session = se;
848     cl->next = se->clients;
849     se->clients = cl;
850 }
851
852 int client_is_active(struct client *cl)
853 {
854     if (cl->connection && (cl->state == Client_Connecting ||
855                            cl->state == Client_Working))
856         return 1;
857     return 0;
858 }
859
860 struct client *client_next_in_session(struct client *cl)
861 {
862     if (cl)
863         return cl->next;
864     return 0;
865
866 }
867
868 Odr_int client_get_hits(struct client *cl)
869 {
870     return cl->hits;
871 }
872
873 int client_get_num_records(struct client *cl)
874 {
875     return cl->record_offset;
876 }
877
878 void client_set_diagnostic(struct client *cl, int diagnostic)
879 {
880     cl->diagnostic = diagnostic;
881 }
882
883 int client_get_diagnostic(struct client *cl)
884 {
885     return cl->diagnostic;
886 }
887
888 void client_set_database(struct client *cl, struct session_database *db)
889 {
890     cl->database = db;
891 }
892
893 struct host *client_get_host(struct client *cl)
894 {
895     return client_get_database(cl)->database->host;
896 }
897
898 const char *client_get_url(struct client *cl)
899 {
900     if (cl->database)
901         return client_get_database(cl)->database->url;
902     else
903         return "NOURL";
904 }
905
906 void client_set_maxrecs(struct client *cl, int v)
907 {
908     cl->maxrecs = v;
909 }
910
911 void client_set_startrecs(struct client *cl, int v)
912 {
913     cl->startrecs = v;
914 }
915
916 /*
917  * Local variables:
918  * c-basic-offset: 4
919  * c-file-style: "Stroustrup"
920  * indent-tabs-mode: nil
921  * End:
922  * vim: shiftwidth=4 tabstop=8 expandtab
923  */
924