Removed unneeded socket includes + minor reformat
[pazpar2-moved-to-github.git] / src / client.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2008 Index Data
3
4 Pazpar2 is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 /** \file client.c
21     \brief Z39.50 client 
22 */
23
24 #if HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
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 <ctype.h>
39 #include <assert.h>
40
41 #include <yaz/marcdisp.h>
42 #include <yaz/comstack.h>
43 #include <yaz/tcpip.h>
44 #include <yaz/proto.h>
45 #include <yaz/readconf.h>
46 #include <yaz/pquery.h>
47 #include <yaz/otherinfo.h>
48 #include <yaz/yaz-util.h>
49 #include <yaz/nmem.h>
50 #include <yaz/query-charset.h>
51 #include <yaz/querytowrbuf.h>
52 #include <yaz/oid_db.h>
53 #include <yaz/diagbib1.h>
54 #include <yaz/snprintf.h>
55 #include <yaz/rpn2cql.h>
56
57 #define USE_TIMING 0
58 #if USE_TIMING
59 #include <yaz/timing.h>
60 #endif
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     char *cqlquery; // used for SRU targets only
75     int hits;
76     int record_offset;
77     int setno;
78     int requestid;            // ID of current outstanding request
79     int diagnostic;
80     enum client_state state;
81     struct show_raw *show_raw;
82     struct client *next;     // next client in session or next in free list
83 };
84
85 struct show_raw {
86     int active; // whether this request has been sent to the server
87     int position;
88     int binary;
89     char *syntax;
90     char *esn;
91     void (*error_handler)(void *data, const char *addinfo);
92     void (*record_handler)(void *data, const char *buf, size_t sz);
93     void *data;
94     struct show_raw *next;
95 };
96
97 static const char *client_states[] = {
98     "Client_Connecting",
99     "Client_Idle",
100     "Client_Working",
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 const char *client_get_state_str(struct client *cl)
111 {
112     return client_states[cl->state];
113 }
114
115 enum client_state client_get_state(struct client *cl)
116 {
117     return cl->state;
118 }
119
120 void client_set_state(struct client *cl, enum client_state st)
121 {
122     cl->state = st;
123     if (cl->session)
124     {
125         int no_active = session_active_clients(cl->session);
126         if (no_active == 0)
127             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
128     }
129 }
130
131 static void client_show_raw_error(struct client *cl, const char *addinfo);
132
133 // Close connection and set state to error
134 void client_fatal(struct client *cl)
135 {
136     yaz_log(YLOG_WARN, "Fatal error from %s", client_get_url(cl));
137     connection_destroy(cl->connection);
138     client_set_state(cl, Client_Error);
139 }
140
141 struct connection *client_get_connection(struct client *cl)
142 {
143     return cl->connection;
144 }
145
146 struct session_database *client_get_database(struct client *cl)
147 {
148     return cl->database;
149 }
150
151 struct session *client_get_session(struct client *cl)
152 {
153     return cl->session;
154 }
155
156 const char *client_get_pquery(struct client *cl)
157 {
158     return cl->pquery;
159 }
160
161 void client_set_requestid(struct client *cl, int id)
162 {
163     cl->requestid = id;
164 }
165
166
167 static void client_send_raw_present(struct client *cl);
168
169 int client_show_raw_begin(struct client *cl, int position,
170                           const char *syntax, const char *esn,
171                           void *data,
172                           void (*error_handler)(void *data, const char *addinfo),
173                           void (*record_handler)(void *data, const char *buf,
174                                                  size_t sz),
175                           void **data2,
176                           int binary)
177 {
178     struct show_raw *rr, **rrp;
179     if (!cl->connection)
180     {   /* the client has no connection */
181         return -1;
182     }
183     rr = xmalloc(sizeof(*rr));
184     *data2 = rr;
185     rr->position = position;
186     rr->active = 0;
187     rr->data = data;
188     rr->error_handler = error_handler;
189     rr->record_handler = record_handler;
190     rr->binary = binary;
191     if (syntax)
192         rr->syntax = xstrdup(syntax);
193     else
194         rr->syntax = 0;
195     if (esn)
196         rr->esn = xstrdup(esn);
197     else
198         rr->esn = 0;
199     rr->next = 0;
200     
201     for (rrp = &cl->show_raw; *rrp; rrp = &(*rrp)->next)
202         ;
203     *rrp = rr;
204     
205     if (cl->state == Client_Failed)
206     {
207         client_show_raw_error(cl, "client failed");
208     }
209     else if (cl->state == Client_Disconnected)
210     {
211         client_show_raw_error(cl, "client disconnected");
212     }
213     else
214     {
215         client_send_raw_present(cl);
216     }
217     return 0;
218 }
219
220 void client_show_raw_remove(struct client *cl, void *data)
221 {
222     struct show_raw *rr = data;
223     struct show_raw **rrp = &cl->show_raw;
224     while (*rrp != rr)
225         rrp = &(*rrp)->next;
226     if (*rrp)
227     {
228         *rrp = rr->next;
229         xfree(rr);
230     }
231 }
232
233 void client_show_raw_dequeue(struct client *cl)
234 {
235     struct show_raw *rr = cl->show_raw;
236
237     cl->show_raw = rr->next;
238     xfree(rr);
239 }
240
241 static void client_show_raw_error(struct client *cl, const char *addinfo)
242 {
243     while (cl->show_raw)
244     {
245         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
246         client_show_raw_dequeue(cl);
247     }
248 }
249
250 static void client_send_raw_present(struct client *cl)
251 {
252     struct session_database *sdb = client_get_database(cl);
253     struct connection *co = client_get_connection(cl);
254     ZOOM_resultset set = connection_get_resultset(co);
255
256     int offset = cl->show_raw->position;
257     const char *syntax = 0;
258     const char *elements = 0;
259
260     assert(cl->show_raw);
261     assert(set);
262
263     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
264             client_get_url(cl), 1, offset);
265
266     if (cl->show_raw->syntax)
267         syntax = cl->show_raw->syntax;
268     else
269         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
270     ZOOM_resultset_option_set(set, "preferredRecordSyntax", syntax);
271
272     if (cl->show_raw->esn)
273         elements = cl->show_raw->esn;
274     else
275         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
276     if (elements && *elements)
277         ZOOM_resultset_option_set(set, "elementSetName", elements);
278
279     ZOOM_resultset_records(set, 0, offset-1, 1);
280     cl->show_raw->active = 1;
281
282     connection_continue(co);
283 }
284
285 static int nativesyntax_to_type(struct session_database *sdb, char *type)
286 {
287     const char *s = session_setting_oneval(sdb, PZ_NATIVESYNTAX);
288
289     if (s && *s)
290     {
291         if (!strncmp(s, "iso2709", 7))
292         {
293             const char *cp = strchr(s, ';');
294             yaz_snprintf(type, 80, "xml; charset=%s", cp ? cp+1 : "marc-8s");
295         }
296         else if (!strncmp(s, "xml", 3))
297         {
298             strcpy(type, "xml");
299         }
300         else
301             return -1;
302         return 0;
303     }
304     return -1;
305 }
306
307 static void ingest_raw_record(struct client *cl, ZOOM_record rec)
308 {
309     const char *buf;
310     int len;
311     char type[80];
312
313     if (cl->show_raw->binary)
314         strcpy(type, "raw");
315     else
316     {
317         struct session_database *sdb = client_get_database(cl);
318         nativesyntax_to_type(sdb, type);
319     }
320
321     buf = ZOOM_record_get(rec, type, &len);
322     cl->show_raw->record_handler(cl->show_raw->data,  buf, len);
323     client_show_raw_dequeue(cl);
324 }
325
326 void client_search_response(struct client *cl)
327 {
328     struct connection *co = cl->connection;
329     struct session *se = cl->session;
330     ZOOM_connection link = connection_get_link(co);
331     ZOOM_resultset resultset = connection_get_resultset(co);
332     const char *error, *addinfo;
333
334     if (ZOOM_connection_error(link, &error, &addinfo))
335     {
336         cl->hits = 0;
337         client_set_state(cl, Client_Error);
338         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
339             error, addinfo, client_get_url(cl));
340     }
341     else
342     {
343         cl->record_offset = 0;
344         cl->hits = ZOOM_resultset_size(resultset);
345         se->total_hits += cl->hits;
346     }
347 }
348
349
350 void client_record_response(struct client *cl)
351 {
352     struct connection *co = cl->connection;
353     ZOOM_connection link = connection_get_link(co);
354     ZOOM_resultset resultset = connection_get_resultset(co);
355     const char *error, *addinfo;
356
357     if (ZOOM_connection_error(link, &error, &addinfo))
358     {
359         client_set_state(cl, Client_Error);
360         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
361             error, addinfo, client_get_url(cl));
362     }
363     else
364     {
365         ZOOM_record rec = 0;
366         const char *msg, *addinfo;
367         
368         if (cl->show_raw && cl->show_raw->active)
369         {
370             if ((rec = ZOOM_resultset_record(resultset,
371                                              cl->show_raw->position-1)))
372             {
373                 cl->show_raw->active = 0;
374                 ingest_raw_record(cl, rec);
375             }
376             else
377             {
378                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
379                         cl->show_raw->position-1);
380             }
381         }
382         else
383         {
384             int offset = cl->record_offset;
385             if ((rec = ZOOM_resultset_record(resultset, offset)))
386             {
387                 cl->record_offset++;
388                 if (ZOOM_record_error(rec, &msg, &addinfo, 0))
389                     yaz_log(YLOG_WARN, "Record error %s (%s): %s (rec #%d)",
390                             error, addinfo, client_get_url(cl),
391                             cl->record_offset);
392                 else
393                 {
394                     struct session_database *sdb = client_get_database(cl);
395                     const char *xmlrec;
396                     char type[80];
397                     nativesyntax_to_type(sdb, type);
398                     if ((xmlrec = ZOOM_record_get(rec, type, NULL)))
399                     {
400                         if (ingest_record(cl, xmlrec, cl->record_offset))
401                         {
402                             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
403                             session_alert_watch(cl->session, SESSION_WATCH_RECORD);
404                         }
405                         else
406                             yaz_log(YLOG_WARN, "Failed to ingest");
407                     }
408                     else
409                         yaz_log(YLOG_WARN, "Failed to extract ZOOM record");
410                 }
411
412             }
413             else
414             {
415                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
416                         offset);
417             }
418         }
419     }
420 }
421
422 void client_start_search(struct client *cl)
423 {
424     struct session_database *sdb = client_get_database(cl);
425     struct connection *co = client_get_connection(cl);
426     ZOOM_connection link = connection_get_link(co);
427     ZOOM_resultset rs;
428     char *databaseName = sdb->database->databases[0];
429     const char *opt_piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK);
430     const char *opt_queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING);
431     const char *opt_elements = session_setting_oneval(sdb, PZ_ELEMENTS);
432     const char *opt_requestsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
433     const char *opt_maxrecs = session_setting_oneval(sdb, PZ_MAXRECS);
434     const char *opt_sru = session_setting_oneval(sdb, PZ_SRU);
435
436     assert(link);
437
438     cl->hits = -1;
439     cl->record_offset = 0;
440     cl->diagnostic = 0;
441     client_set_state(cl, Client_Working);
442
443     if (*opt_piggyback)
444         ZOOM_connection_option_set(link, "piggyback", opt_piggyback);
445     else
446         ZOOM_connection_option_set(link, "piggyback", "1");
447     if (*opt_queryenc)
448         ZOOM_connection_option_set(link, "rpnCharset", opt_queryenc);
449     if (*opt_sru && *opt_elements)
450         ZOOM_connection_option_set(link, "schema", opt_elements);
451     else if (*opt_elements)
452         ZOOM_connection_option_set(link, "elementSetName", opt_elements);
453     if (*opt_requestsyn)
454         ZOOM_connection_option_set(link, "preferredRecordSyntax", opt_requestsyn);
455     if (*opt_maxrecs)
456         ZOOM_connection_option_set(link, "count", opt_maxrecs);
457     else
458     {
459         char n[128];
460         sprintf(n, "%d", global_parameters.toget);
461         ZOOM_connection_option_set(link, "count", n);
462     }
463     if (databaseName)
464         ZOOM_connection_option_set(link, "databaseName", databaseName);
465
466     ZOOM_connection_option_set(link, "presentChunk", "20");
467
468     if (cl->cqlquery)
469     {
470         ZOOM_query q = ZOOM_query_create();
471         ZOOM_query_cql(q, cl->cqlquery);
472         rs = ZOOM_connection_search(link, q);
473         ZOOM_query_destroy(q);
474     }
475     else
476         rs = ZOOM_connection_search_pqf(link, cl->pquery);
477     connection_set_resultset(co, rs);
478     connection_continue(co);
479 }
480
481 struct client *client_create(void)
482 {
483     struct client *r;
484     if (client_freelist)
485     {
486         r = client_freelist;
487         client_freelist = client_freelist->next;
488     }
489     else
490         r = xmalloc(sizeof(struct client));
491     r->pquery = 0;
492     r->cqlquery = 0;
493     r->database = 0;
494     r->connection = 0;
495     r->session = 0;
496     r->hits = 0;
497     r->record_offset = 0;
498     r->setno = 0;
499     r->requestid = -1;
500     r->diagnostic = 0;
501     r->state = Client_Disconnected;
502     r->show_raw = 0;
503     r->next = 0;
504     return r;
505 }
506
507 void client_destroy(struct client *c)
508 {
509     struct session *se = c->session;
510     if (c == se->clients)
511         se->clients = c->next;
512     else
513     {
514         struct client *cc;
515         for (cc = se->clients; cc && cc->next != c; cc = cc->next)
516             ;
517         if (cc)
518             cc->next = c->next;
519     }
520     xfree(c->pquery);
521     xfree(c->cqlquery);
522
523     if (c->connection)
524         connection_release(c->connection);
525     c->next = client_freelist;
526     client_freelist = c;
527 }
528
529 void client_set_connection(struct client *cl, struct connection *con)
530 {
531     cl->connection = con;
532 }
533
534 void client_disconnect(struct client *cl)
535 {
536     if (cl->state != Client_Idle)
537         client_set_state(cl, Client_Disconnected);
538     client_set_connection(cl, 0);
539 }
540
541 // Extract terms from query into null-terminated termlist
542 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
543 {
544     int num = 0;
545
546     pull_terms(nmem, query, termlist, &num);
547     termlist[num] = 0;
548 }
549
550 // Initialize CCL map for a target
551 static CCL_bibset prepare_cclmap(struct client *cl)
552 {
553     struct session_database *sdb = client_get_database(cl);
554     struct setting *s;
555     CCL_bibset res;
556
557     if (!sdb->settings)
558         return 0;
559     res = ccl_qual_mk();
560     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
561     {
562         char *p = strchr(s->name + 3, ':');
563         if (!p)
564         {
565             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
566             ccl_qual_rm(&res);
567             return 0;
568         }
569         p++;
570         ccl_qual_fitem(res, s->value, p);
571     }
572     return res;
573 }
574
575 // returns a xmalloced CQL query corresponding to the pquery in client
576 static char *make_cqlquery(struct client *cl)
577 {
578     cql_transform_t cqlt = cql_transform_create();
579     Z_RPNQuery *zquery;
580     char *r;
581     WRBUF wrb = wrbuf_alloc();
582     int status;
583
584     zquery = p_query_rpn(global_parameters.odr_out, cl->pquery);
585     if ((status = cql_transform_rpn2cql_wrbuf(cqlt, wrb, zquery)))
586     {
587         yaz_log(YLOG_WARN, "failed to generate CQL query, code=%d", status);
588         return 0;
589     }
590     r = xstrdup(wrbuf_cstr(wrb));
591
592     wrbuf_destroy(wrb);
593     odr_reset(global_parameters.odr_out); // releases the zquery
594     cql_transform_close(cqlt);
595     return r;
596 }
597
598 // Parse the query given the settings specific to this client
599 int client_parse_query(struct client *cl, const char *query)
600 {
601     struct session *se = client_get_session(cl);
602     struct session_database *sdb = client_get_database(cl);
603     struct ccl_rpn_node *cn;
604     int cerror, cpos;
605     CCL_bibset ccl_map = prepare_cclmap(cl);
606     const char *sru = session_setting_oneval(sdb, PZ_SRU);
607
608     if (!ccl_map)
609         return -1;
610
611     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
612     ccl_qual_rm(&ccl_map);
613     if (!cn)
614     {
615         client_set_state(cl, Client_Error);
616         yaz_log(YLOG_WARN, "Failed to parse query for %s",
617                          client_get_database(cl)->database->url);
618         return -1;
619     }
620     wrbuf_rewind(se->wrbuf);
621     ccl_pquery(se->wrbuf, cn);
622     xfree(cl->pquery);
623     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
624
625     xfree(cl->cqlquery);
626     if (*sru)
627     {
628         if (!(cl->cqlquery = make_cqlquery(cl)))
629             return -1;
630     }
631     else
632         cl->cqlquery = 0;
633
634     if (!se->relevance)
635     {
636         // Initialize relevance structure with query terms
637         char *p[512];
638         extract_terms(se->nmem, cn, p);
639         se->relevance = relevance_create(
640             global_parameters.server->relevance_pct,
641             se->nmem, (const char **) p,
642             se->expected_maxrecs);
643     }
644
645     ccl_rpn_delete(cn);
646     return 0;
647 }
648
649 void client_set_session(struct client *cl, struct session *se)
650 {
651     cl->session = se;
652     cl->next = se->clients;
653     se->clients = cl;
654 }
655
656 int client_is_active(struct client *cl)
657 {
658     if (cl->connection && (cl->state == Client_Continue ||
659                            cl->state == Client_Connecting ||
660                            cl->state == Client_Working))
661         return 1;
662     return 0;
663 }
664
665 struct client *client_next_in_session(struct client *cl)
666 {
667     if (cl)
668         return cl->next;
669     return 0;
670
671 }
672
673 int client_get_hits(struct client *cl)
674 {
675     return cl->hits;
676 }
677
678 int client_get_num_records(struct client *cl)
679 {
680     return cl->record_offset;
681 }
682
683 int client_get_diagnostic(struct client *cl)
684 {
685     return cl->diagnostic;
686 }
687
688 void client_set_database(struct client *cl, struct session_database *db)
689 {
690     cl->database = db;
691 }
692
693 struct host *client_get_host(struct client *cl)
694 {
695     return client_get_database(cl)->database->host;
696 }
697
698 const char *client_get_url(struct client *cl)
699 {
700     return client_get_database(cl)->database->url;
701 }
702
703 /*
704  * Local variables:
705  * c-basic-offset: 4
706  * indent-tabs-mode: nil
707  * End:
708  * vim: shiftwidth=4 tabstop=8 expandtab
709  */