Neglected to initialize session->databases
[pazpar2-moved-to-github.git] / src / pazpar2.c
1 /* $Id: pazpar2.c,v 1.72 2007-04-11 13:27:06 quinn 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 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/time.h>
26 #include <unistd.h>
27 #include <sys/socket.h>
28 #include <netdb.h>
29 #include <signal.h>
30 #include <ctype.h>
31 #include <assert.h>
32
33 #include <yaz/marcdisp.h>
34 #include <yaz/comstack.h>
35 #include <yaz/tcpip.h>
36 #include <yaz/proto.h>
37 #include <yaz/readconf.h>
38 #include <yaz/pquery.h>
39 #include <yaz/otherinfo.h>
40 #include <yaz/yaz-util.h>
41 #include <yaz/nmem.h>
42
43 #if HAVE_CONFIG_H
44 #include "cconfig.h"
45 #endif
46
47 #define USE_TIMING 0
48 #if USE_TIMING
49 #include <yaz/timing.h>
50 #endif
51
52 #include <netinet/in.h>
53
54 #include "pazpar2.h"
55 #include "eventl.h"
56 #include "http.h"
57 #include "termlists.h"
58 #include "reclists.h"
59 #include "relevance.h"
60 #include "config.h"
61 #include "database.h"
62 #include "settings.h"
63
64 #define MAX_CHUNK 15
65
66 static void client_fatal(struct client *cl);
67 static void connection_destroy(struct connection *co);
68 static int client_prep_connection(struct client *cl);
69 static void ingest_records(struct client *cl, Z_Records *r);
70 void session_alert_watch(struct session *s, int what);
71 char *session_setting_oneval(struct session_database *db, int offset);
72
73 IOCHAN channel_list = 0;  // Master list of connections we're handling events to
74
75 static struct connection *connection_freelist = 0;
76 static struct client *client_freelist = 0;
77
78 static char *client_states[] = {
79     "Client_Connecting",
80     "Client_Connected",
81     "Client_Idle",
82     "Client_Initializing",
83     "Client_Searching",
84     "Client_Presenting",
85     "Client_Error",
86     "Client_Failed",
87     "Client_Disconnected",
88     "Client_Stopped"
89 };
90
91 // Note: Some things in this structure will eventually move to configuration
92 struct parameters global_parameters = 
93 {
94     "",
95     "",
96     "",
97     "",
98     0,
99     0,
100     30,
101     "81",
102     "Index Data PazPar2 (MasterKey)",
103     VERSION,
104     600, // 10 minutes
105     60,
106     100,
107     MAX_CHUNK,
108     0,
109     0
110 };
111
112 static int send_apdu(struct client *c, Z_APDU *a)
113 {
114     struct connection *co = c->connection;
115     char *buf;
116     int len, r;
117
118     if (!z_APDU(global_parameters.odr_out, &a, 0, 0))
119     {
120         odr_perror(global_parameters.odr_out, "Encoding APDU");
121         abort();
122     }
123     buf = odr_getbuf(global_parameters.odr_out, &len, 0);
124     r = cs_put(co->link, buf, len);
125     if (r < 0)
126     {
127         yaz_log(YLOG_WARN, "cs_put: %s", cs_errmsg(cs_errno(co->link)));
128         return -1;
129     }
130     else if (r == 1)
131     {
132         fprintf(stderr, "cs_put incomplete (ParaZ does not handle that)\n");
133         exit(1);
134     }
135     odr_reset(global_parameters.odr_out); /* release the APDU structure  */
136     co->state = Conn_Waiting;
137     return 0;
138 }
139
140 // Set authentication token in init if one is set for the client
141 // TODO: Extend this to handle other schemes than open (should be simple)
142 static void init_authentication(struct client *cl, Z_InitRequest *req)
143 {
144     struct session_database *sdb = cl->database;
145     char *auth = session_setting_oneval(sdb, PZ_AUTHENTICATION);
146
147     if (auth)
148     {
149         Z_IdAuthentication *idAuth = odr_malloc(global_parameters.odr_out,
150                 sizeof(*idAuth));
151         idAuth->which = Z_IdAuthentication_open;
152         idAuth->u.open = auth;
153         req->idAuthentication = idAuth;
154     }
155 }
156
157 static void send_init(IOCHAN i)
158 {
159
160     struct connection *co = iochan_getdata(i);
161     struct client *cl = co->client;
162     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_initRequest);
163
164     a->u.initRequest->implementationId = global_parameters.implementationId;
165     a->u.initRequest->implementationName = global_parameters.implementationName;
166     a->u.initRequest->implementationVersion =
167         global_parameters.implementationVersion;
168     ODR_MASK_SET(a->u.initRequest->options, Z_Options_search);
169     ODR_MASK_SET(a->u.initRequest->options, Z_Options_present);
170     ODR_MASK_SET(a->u.initRequest->options, Z_Options_namedResultSets);
171
172     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_1);
173     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_2);
174     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_3);
175
176     init_authentication(cl, a->u.initRequest);
177
178     /* add virtual host if tunneling through Z39.50 proxy */
179     
180     if (0 < strlen(global_parameters.zproxy_override) 
181         && 0 < strlen(cl->database->database->url))
182         yaz_oi_set_string_oidval(&a->u.initRequest->otherInfo, 
183                                  global_parameters.odr_out, VAL_PROXY,
184                                  1, cl->database->database->url);
185
186     if (send_apdu(cl, a) >= 0)
187     {
188         iochan_setflags(i, EVENT_INPUT);
189         cl->state = Client_Initializing;
190     }
191     else
192         cl->state = Client_Error;
193     odr_reset(global_parameters.odr_out);
194 }
195
196 static void pull_terms(NMEM nmem, struct ccl_rpn_node *n, char **termlist, int *num)
197 {
198     switch (n->kind)
199     {
200         case CCL_RPN_AND:
201         case CCL_RPN_OR:
202         case CCL_RPN_NOT:
203         case CCL_RPN_PROX:
204             pull_terms(nmem, n->u.p[0], termlist, num);
205             pull_terms(nmem, n->u.p[1], termlist, num);
206             break;
207         case CCL_RPN_TERM:
208             termlist[(*num)++] = nmem_strdup(nmem, n->u.t.term);
209             break;
210         default: // NOOP
211             break;
212     }
213 }
214
215 // Extract terms from query into null-terminated termlist
216 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
217 {
218     int num = 0;
219
220     pull_terms(nmem, query, termlist, &num);
221     termlist[num] = 0;
222 }
223
224 static void send_search(IOCHAN i)
225 {
226     struct connection *co = iochan_getdata(i);
227     struct client *cl = co->client; 
228     struct session *se = cl->session;
229     struct session_database *sdb = cl->database;
230     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_searchRequest);
231     int ndb, cerror, cpos;
232     char **databaselist;
233     Z_Query *zquery;
234     struct ccl_rpn_node *cn;
235     int ssub = 0, lslb = 100000, mspn = 10;
236     char *recsyn;
237     char *piggyback;
238
239     yaz_log(YLOG_DEBUG, "Sending search");
240
241     cn = ccl_find_str(sdb->database->ccl_map, se->query, &cerror, &cpos);
242     if (!cn)
243         return;
244
245     if (!se->relevance)
246     {
247         // Initialize relevance structure with query terms
248         char *p[512];
249         extract_terms(se->nmem, cn, p);
250         se->relevance = relevance_create(se->nmem, (const char **) p,
251                 se->expected_maxrecs);
252     }
253
254     a->u.searchRequest->query = zquery = odr_malloc(global_parameters.odr_out,
255             sizeof(Z_Query));
256     zquery->which = Z_Query_type_1;
257     zquery->u.type_1 = ccl_rpn_query(global_parameters.odr_out, cn);
258     ccl_rpn_delete(cn);
259
260     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
261         ;
262     databaselist = odr_malloc(global_parameters.odr_out, sizeof(char*) * ndb);
263     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
264         databaselist[ndb] = sdb->database->databases[ndb];
265
266     if (!(piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK)) || *piggyback == '1')
267     {
268         if ((recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX)))
269             a->u.searchRequest->preferredRecordSyntax =
270                     yaz_str_to_z3950oid(global_parameters.odr_out,
271                     CLASS_RECSYN, recsyn);
272         a->u.searchRequest->smallSetUpperBound = &ssub;
273         a->u.searchRequest->largeSetLowerBound = &lslb;
274         a->u.searchRequest->mediumSetPresentNumber = &mspn;
275     }
276     a->u.searchRequest->resultSetName = "Default";
277     a->u.searchRequest->databaseNames = databaselist;
278     a->u.searchRequest->num_databaseNames = ndb;
279
280     if (send_apdu(cl, a) >= 0)
281     {
282         iochan_setflags(i, EVENT_INPUT);
283         cl->state = Client_Searching;
284         cl->requestid = se->requestid;
285     }
286     else
287         cl->state = Client_Error;
288
289     odr_reset(global_parameters.odr_out);
290 }
291
292 static void send_present(IOCHAN i)
293 {
294     struct connection *co = iochan_getdata(i);
295     struct client *cl = co->client; 
296     struct session_database *sdb = cl->database;
297     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
298     int toget;
299     int start = cl->records + 1;
300     char *recsyn;
301
302     toget = global_parameters.chunk;
303     if (toget > global_parameters.toget - cl->records)
304         toget = global_parameters.toget - cl->records;
305     if (toget > cl->hits - cl->records)
306         toget = cl->hits - cl->records;
307
308     yaz_log(YLOG_DEBUG, "Trying to present %d records\n", toget);
309
310     a->u.presentRequest->resultSetStartPoint = &start;
311     a->u.presentRequest->numberOfRecordsRequested = &toget;
312
313     a->u.presentRequest->resultSetId = "Default";
314
315     if ((recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX)))
316         a->u.presentRequest->preferredRecordSyntax =
317                 yaz_str_to_z3950oid(global_parameters.odr_out,
318                 CLASS_RECSYN, recsyn);
319
320     if (send_apdu(cl, a) >= 0)
321     {
322         iochan_setflags(i, EVENT_INPUT);
323         cl->state = Client_Presenting;
324     }
325     else
326         cl->state = Client_Error;
327     odr_reset(global_parameters.odr_out);
328 }
329
330 static void do_initResponse(IOCHAN i, Z_APDU *a)
331 {
332     struct connection *co = iochan_getdata(i);
333     struct client *cl = co->client;
334     Z_InitResponse *r = a->u.initResponse;
335
336     yaz_log(YLOG_DEBUG, "Init response %s", cl->database->database->url);
337
338     if (*r->result)
339     {
340         cl->state = Client_Idle;
341     }
342     else
343         cl->state = Client_Failed; // FIXME need to do something to the connection
344 }
345
346 static void do_searchResponse(IOCHAN i, Z_APDU *a)
347 {
348     struct connection *co = iochan_getdata(i);
349     struct client *cl = co->client;
350     struct session *se = cl->session;
351     Z_SearchResponse *r = a->u.searchResponse;
352
353     yaz_log(YLOG_DEBUG, "Search response %s (status=%d)", 
354             cl->database->database->url, *r->searchStatus);
355
356     if (*r->searchStatus)
357     {
358         cl->hits = *r->resultCount;
359         se->total_hits += cl->hits;
360         if (r->presentStatus && !*r->presentStatus && r->records)
361         {
362             yaz_log(YLOG_DEBUG, "Records in search response %s", 
363                     cl->database->database->url);
364             ingest_records(cl, r->records);
365         }
366         cl->state = Client_Idle;
367     }
368     else
369     {          /*"FAILED"*/
370         cl->hits = 0;
371         cl->state = Client_Error;
372         if (r->records) {
373             Z_Records *recs = r->records;
374             if (recs->which == Z_Records_NSD)
375             {
376                 yaz_log(YLOG_WARN, 
377                         "Search response: Non-surrogate diagnostic %s",
378                         cl->database->database->url);
379                 cl->diagnostic = *recs->u.nonSurrogateDiagnostic->condition;
380                 cl->state = Client_Error;
381             }
382         }
383     }
384 }
385
386 static void do_closeResponse(IOCHAN i, Z_APDU *a)
387 {
388     struct connection *co = iochan_getdata(i);
389     struct client *cl = co->client;
390     /* Z_Close *r = a->u.close; */
391
392     yaz_log(YLOG_WARN, "Close response %s", cl->database->database->url);
393
394     cl->state = Client_Failed;
395     connection_destroy(co);
396 }
397
398
399 char *normalize_mergekey(char *buf, int skiparticle)
400 {
401     char *p = buf, *pout = buf;
402
403     if (skiparticle)
404     {
405         char firstword[64];
406         char articles[] = "the den der die des an a "; // must end in space
407
408         while (*p && !isalnum(*p))
409             p++;
410         pout = firstword;
411         while (*p && *p != ' ' && pout - firstword < 62)
412             *(pout++) = tolower(*(p++));
413         *(pout++) = ' ';
414         *(pout++) = '\0';
415         if (!strstr(articles, firstword))
416             p = buf;
417         pout = buf;
418     }
419
420     while (*p)
421     {
422         while (*p && !isalnum(*p))
423             p++;
424         while (isalnum(*p))
425             *(pout++) = tolower(*(p++));
426         if (*p)
427             *(pout++) = ' ';
428         while (*p && !isalnum(*p))
429             p++;
430     }
431     if (buf != pout)
432         do {
433             *(pout--) = '\0';
434         }
435         while (pout > buf && *pout == ' ');
436
437     return buf;
438 }
439
440 static void add_facet(struct session *s, const char *type, const char *value)
441 {
442     int i;
443
444     if (!*value)
445         return;
446     for (i = 0; i < s->num_termlists; i++)
447         if (!strcmp(s->termlists[i].name, type))
448             break;
449     if (i == s->num_termlists)
450     {
451         if (i == SESSION_MAX_TERMLISTS)
452         {
453             yaz_log(YLOG_FATAL, "Too many termlists");
454             exit(1);
455         }
456         s->termlists[i].name = nmem_strdup(s->nmem, type);
457         s->termlists[i].termlist = termlist_create(s->nmem, s->expected_maxrecs, 15);
458         s->num_termlists = i + 1;
459     }
460     termlist_insert(s->termlists[i].termlist, value);
461 }
462
463 static xmlDoc *normalize_record(struct client *cl, Z_External *rec)
464 {
465     struct database_retrievalmap *m;
466     struct database *db = cl->database->database;
467     xmlNode *res;
468     xmlDoc *rdoc;
469
470     // First normalize to XML
471     if (db->yaz_marc)
472     {
473         char *buf;
474         int len;
475         if (rec->which != Z_External_octet)
476         {
477             yaz_log(YLOG_WARN, "Unexpected external branch, probably BER %s",
478                     cl->database->database->url);
479             return 0;
480         }
481         buf = (char*) rec->u.octet_aligned->buf;
482         len = rec->u.octet_aligned->len;
483         if (yaz_marc_read_iso2709(db->yaz_marc, buf, len) < 0)
484         {
485             yaz_log(YLOG_WARN, "Failed to decode MARC %s",
486                     cl->database->database->url);
487             return 0;
488         }
489         if (yaz_marc_write_xml(db->yaz_marc, &res,
490                     "http://www.loc.gov/MARC21/slim", 0, 0) < 0)
491         {
492             yaz_log(YLOG_WARN, "Failed to encode as XML %s",
493                     cl->database->database->url);
494             return 0;
495         }
496         rdoc = xmlNewDoc((xmlChar *) "1.0");
497         xmlDocSetRootElement(rdoc, res);
498     }
499     else
500     {
501         yaz_log(YLOG_FATAL, "Unknown native_syntax in normalize_record");
502         exit(1);
503     }
504
505     if (global_parameters.dump_records)
506     {
507         fprintf(stderr, "Input Record (normalized):\n----------------\n");
508 #if LIBXML_VERSION >= 20600
509         xmlDocFormatDump(stderr, rdoc, 1);
510 #else
511         xmlDocDump(stderr, rdoc);
512 #endif
513     }
514
515     for (m = db->map; m; m = m->next)
516     {
517         xmlDoc *new;
518         if (!(new = xsltApplyStylesheet(m->stylesheet, rdoc, 0)))
519         {
520             yaz_log(YLOG_WARN, "XSLT transformation failed");
521             return 0;
522         }
523         xmlFreeDoc(rdoc);
524         rdoc = new;
525     }
526     if (global_parameters.dump_records)
527     {
528         fprintf(stderr, "Record:\n----------------\n");
529 #if LIBXML_VERSION >= 20600
530         xmlDocFormatDump(stderr, rdoc, 1);
531 #else
532         xmlDocDump(stderr, rdoc);
533 #endif
534     }
535     return rdoc;
536 }
537
538 // Extract what appears to be years from buf, storing highest and
539 // lowest values.
540 static int extract_years(const char *buf, int *first, int *last)
541 {
542     *first = -1;
543     *last = -1;
544     while (*buf)
545     {
546         const char *e;
547         int len;
548
549         while (*buf && !isdigit(*buf))
550             buf++;
551         len = 0;
552         for (e = buf; *e && isdigit(*e); e++)
553             len++;
554         if (len == 4)
555         {
556             int value = atoi(buf);
557             if (*first < 0 || value < *first)
558                 *first = value;
559             if (*last < 0 || value > *last)
560                 *last = value;
561         }
562         buf = e;
563     }
564     return *first;
565 }
566
567 static struct record *ingest_record(struct client *cl, Z_External *rec)
568 {
569     xmlDoc *xdoc = normalize_record(cl, rec);
570     xmlNode *root, *n;
571     struct record *res;
572     struct record_cluster *cluster;
573     struct session *se = cl->session;
574     xmlChar *mergekey, *mergekey_norm;
575     xmlChar *type = 0;
576     xmlChar *value = 0;
577     struct conf_service *service = global_parameters.server->service;
578
579     if (!xdoc)
580         return 0;
581
582     root = xmlDocGetRootElement(xdoc);
583     if (!(mergekey = xmlGetProp(root, (xmlChar *) "mergekey")))
584     {
585         yaz_log(YLOG_WARN, "No mergekey found in record");
586         xmlFreeDoc(xdoc);
587         return 0;
588     }
589
590     res = nmem_malloc(se->nmem, sizeof(struct record));
591     res->next = 0;
592     res->client = cl;
593     res->metadata = nmem_malloc(se->nmem,
594             sizeof(struct record_metadata*) * service->num_metadata);
595     memset(res->metadata, 0, sizeof(struct record_metadata*) * service->num_metadata);
596
597     mergekey_norm = (xmlChar *) nmem_strdup(se->nmem, (char*) mergekey);
598     xmlFree(mergekey);
599     normalize_mergekey((char *) mergekey_norm, 0);
600
601     cluster = reclist_insert(se->reclist, res, (char *) mergekey_norm, 
602                              &se->total_merged);
603     if (global_parameters.dump_records)
604         yaz_log(YLOG_LOG, "Cluster id %d from %s (#%d)", cluster->recid,
605                 cl->database->database->url, cl->records);
606     if (!cluster)
607     {
608         /* no room for record */
609         xmlFreeDoc(xdoc);
610         return 0;
611     }
612     relevance_newrec(se->relevance, cluster);
613
614     for (n = root->children; n; n = n->next)
615     {
616         if (type)
617             xmlFree(type);
618         if (value)
619             xmlFree(value);
620         type = value = 0;
621
622         if (n->type != XML_ELEMENT_NODE)
623             continue;
624         if (!strcmp((const char *) n->name, "metadata"))
625         {
626             struct conf_metadata *md = 0;
627             struct conf_sortkey *sk = 0;
628             struct record_metadata **wheretoput, *newm;
629             int imeta;
630             int first, last;
631
632             type = xmlGetProp(n, (xmlChar *) "type");
633             value = xmlNodeListGetString(xdoc, n->children, 0);
634
635             if (!type || !value)
636                 continue;
637
638             // First, find out what field we're looking at
639             for (imeta = 0; imeta < service->num_metadata; imeta++)
640                 if (!strcmp((const char *) type, service->metadata[imeta].name))
641                 {
642                     md = &service->metadata[imeta];
643                     if (md->sortkey_offset >= 0)
644                         sk = &service->sortkeys[md->sortkey_offset];
645                     break;
646                 }
647             if (!md)
648             {
649                 yaz_log(YLOG_WARN, "Ignoring unknown metadata element: %s", type);
650                 continue;
651             }
652
653             // Find out where we are putting it
654             if (md->merge == Metadata_merge_no)
655                 wheretoput = &res->metadata[imeta];
656             else
657                 wheretoput = &cluster->metadata[imeta];
658             
659             // Put it there
660             newm = nmem_malloc(se->nmem, sizeof(struct record_metadata));
661             newm->next = 0;
662             if (md->type == Metadata_type_generic)
663             {
664                 char *p, *pe;
665                 for (p = (char *) value; *p && isspace(*p); p++)
666                     ;
667                 for (pe = p + strlen(p) - 1;
668                         pe > p && strchr(" ,/.:([", *pe); pe--)
669                     *pe = '\0';
670                 newm->data.text = nmem_strdup(se->nmem, p);
671
672             }
673             else if (md->type == Metadata_type_year)
674             {
675                 if (extract_years((char *) value, &first, &last) < 0)
676                     continue;
677             }
678             else
679             {
680                 yaz_log(YLOG_WARN, "Unknown type in metadata element %s", type);
681                 continue;
682             }
683             if (md->type == Metadata_type_year && md->merge != Metadata_merge_range)
684             {
685                 yaz_log(YLOG_WARN, "Only range merging supported for years");
686                 continue;
687             }
688             if (md->merge == Metadata_merge_unique)
689             {
690                 struct record_metadata *mnode;
691                 for (mnode = *wheretoput; mnode; mnode = mnode->next)
692                     if (!strcmp((const char *) mnode->data.text, newm->data.text))
693                         break;
694                 if (!mnode)
695                 {
696                     newm->next = *wheretoput;
697                     *wheretoput = newm;
698                 }
699             }
700             else if (md->merge == Metadata_merge_longest)
701             {
702                 if (!*wheretoput ||
703                         strlen(newm->data.text) > strlen((*wheretoput)->data.text))
704                 {
705                     *wheretoput = newm;
706                     if (sk)
707                     {
708                         char *s = nmem_strdup(se->nmem, newm->data.text);
709                         if (!cluster->sortkeys[md->sortkey_offset])
710                             cluster->sortkeys[md->sortkey_offset] = 
711                                 nmem_malloc(se->nmem, sizeof(union data_types));
712                         normalize_mergekey(s,
713                                 (sk->type == Metadata_sortkey_skiparticle));
714                         cluster->sortkeys[md->sortkey_offset]->text = s;
715                     }
716                 }
717             }
718             else if (md->merge == Metadata_merge_all || md->merge == Metadata_merge_no)
719             {
720                 newm->next = *wheretoput;
721                 *wheretoput = newm;
722             }
723             else if (md->merge == Metadata_merge_range)
724             {
725                 assert(md->type == Metadata_type_year);
726                 if (!*wheretoput)
727                 {
728                     *wheretoput = newm;
729                     (*wheretoput)->data.number.min = first;
730                     (*wheretoput)->data.number.max = last;
731                     if (sk)
732                         cluster->sortkeys[md->sortkey_offset] = &newm->data;
733                 }
734                 else
735                 {
736                     if (first < (*wheretoput)->data.number.min)
737                         (*wheretoput)->data.number.min = first;
738                     if (last > (*wheretoput)->data.number.max)
739                         (*wheretoput)->data.number.max = last;
740                 }
741 #ifdef GAGA
742                 if (sk)
743                 {
744                     union data_types *sdata = cluster->sortkeys[md->sortkey_offset];
745                     yaz_log(YLOG_LOG, "SK range: %d-%d", sdata->number.min, sdata->number.max);
746                 }
747 #endif
748             }
749             else
750                 yaz_log(YLOG_WARN, "Don't know how to merge on element name %s", md->name);
751
752             if (md->rank)
753                 relevance_countwords(se->relevance, cluster, 
754                                      (char *) value, md->rank);
755             if (md->termlist)
756             {
757                 if (md->type == Metadata_type_year)
758                 {
759                     char year[64];
760                     sprintf(year, "%d", last);
761                     add_facet(se, (char *) type, year);
762                     if (first != last)
763                     {
764                         sprintf(year, "%d", first);
765                         add_facet(se, (char *) type, year);
766                     }
767                 }
768                 else
769                     add_facet(se, (char *) type, (char *) value);
770             }
771             xmlFree(type);
772             xmlFree(value);
773             type = value = 0;
774         }
775         else
776             yaz_log(YLOG_WARN, "Unexpected element %s in internal record", n->name);
777     }
778     if (type)
779         xmlFree(type);
780     if (value)
781         xmlFree(value);
782
783     xmlFreeDoc(xdoc);
784
785     relevance_donerecord(se->relevance, cluster);
786     se->total_records++;
787
788     return res;
789 }
790
791 // Retrieve first defined value for 'name' for given database.
792 // Will be extended to take into account user associated with session
793 char *session_setting_oneval(struct session_database *db, int offset)
794 {
795     if (!db->settings[offset])
796         return 0;
797     return db->settings[offset]->value;
798 }
799
800 static void ingest_records(struct client *cl, Z_Records *r)
801 {
802 #if USE_TIMING
803     yaz_timing_t t = yaz_timing_create();
804 #endif
805     struct record *rec;
806     struct session *s = cl->session;
807     Z_NamePlusRecordList *rlist;
808     int i;
809
810     if (r->which != Z_Records_DBOSD)
811         return;
812     rlist = r->u.databaseOrSurDiagnostics;
813     for (i = 0; i < rlist->num_records; i++)
814     {
815         Z_NamePlusRecord *npr = rlist->records[i];
816
817         cl->records++;
818         if (npr->which != Z_NamePlusRecord_databaseRecord)
819         {
820             yaz_log(YLOG_WARN, 
821                     "Unexpected record type, probably diagnostic %s",
822                     cl->database->database->url);
823             continue;
824         }
825
826         rec = ingest_record(cl, npr->u.databaseRecord);
827         if (!rec)
828             continue;
829     }
830     if (s->watchlist[SESSION_WATCH_RECORDS].fun && rlist->num_records)
831         session_alert_watch(s, SESSION_WATCH_RECORDS);
832
833 #if USE_TIMING
834     yaz_timing_stop(t);
835     yaz_log(YLOG_LOG, "ingest_records %6.5f %3.2f %3.2f", 
836             yaz_timing_get_real(t), yaz_timing_get_user(t),
837             yaz_timing_get_sys(t));
838     yaz_timing_destroy(&t);
839 #endif
840 }
841
842 static void do_presentResponse(IOCHAN i, Z_APDU *a)
843 {
844     struct connection *co = iochan_getdata(i);
845     struct client *cl = co->client;
846     Z_PresentResponse *r = a->u.presentResponse;
847
848     if (r->records) {
849         Z_Records *recs = r->records;
850         if (recs->which == Z_Records_NSD)
851         {
852             yaz_log(YLOG_WARN, "Non-surrogate diagnostic %s",
853                     cl->database->database->url);
854             cl->diagnostic = *recs->u.nonSurrogateDiagnostic->condition;
855             cl->state = Client_Error;
856         }
857     }
858
859     if (!*r->presentStatus && cl->state != Client_Error)
860     {
861         yaz_log(YLOG_DEBUG, "Good Present response %s",
862                 cl->database->database->url);
863         ingest_records(cl, r->records);
864         cl->state = Client_Idle;
865     }
866     else if (*r->presentStatus) 
867     {
868         yaz_log(YLOG_WARN, "Bad Present response %s",
869                 cl->database->database->url);
870         cl->state = Client_Error;
871     }
872 }
873
874 static void handler(IOCHAN i, int event)
875 {
876     struct connection *co = iochan_getdata(i);
877     struct client *cl = co->client;
878     struct session *se = 0;
879
880     if (cl)
881         se = cl->session;
882     else
883     {
884         yaz_log(YLOG_WARN, "Destroying orphan connection");
885         connection_destroy(co);
886         return;
887     }
888
889     if (co->state == Conn_Connecting && event & EVENT_OUTPUT)
890     {
891         int errcode;
892         socklen_t errlen = sizeof(errcode);
893
894         if (getsockopt(cs_fileno(co->link), SOL_SOCKET, SO_ERROR, &errcode,
895             &errlen) < 0 || errcode != 0)
896         {
897             client_fatal(cl);
898             return;
899         }
900         else
901         {
902             yaz_log(YLOG_DEBUG, "Connect OK");
903             co->state = Conn_Open;
904             if (cl)
905                 cl->state = Client_Connected;
906         }
907     }
908
909     else if (event & EVENT_INPUT)
910     {
911         int len = cs_get(co->link, &co->ibuf, &co->ibufsize);
912
913         if (len < 0)
914         {
915             yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
916                     cl->database->database->url);
917             connection_destroy(co);
918             return;
919         }
920         else if (len == 0)
921         {
922             yaz_log(YLOG_WARN, "EOF reading from %s", cl->database->database->url);
923             connection_destroy(co);
924             return;
925         }
926         else if (len > 1) // We discard input if we have no connection
927         {
928             co->state = Conn_Open;
929
930             if (cl && (cl->requestid == se->requestid || cl->state == Client_Initializing))
931             {
932                 Z_APDU *a;
933
934                 odr_reset(global_parameters.odr_in);
935                 odr_setbuf(global_parameters.odr_in, co->ibuf, len, 0);
936                 if (!z_APDU(global_parameters.odr_in, &a, 0, 0))
937                 {
938                     client_fatal(cl);
939                     return;
940                 }
941                 switch (a->which)
942                 {
943                     case Z_APDU_initResponse:
944                         do_initResponse(i, a);
945                         break;
946                     case Z_APDU_searchResponse:
947                         do_searchResponse(i, a);
948                         break;
949                     case Z_APDU_presentResponse:
950                         do_presentResponse(i, a);
951                         break;
952                     case Z_APDU_close:
953                         do_closeResponse(i, a);
954                         break;
955                     default:
956                         yaz_log(YLOG_WARN, 
957                                 "Unexpected Z39.50 response from %s",  
958                                 cl->database->database->url);
959                         client_fatal(cl);
960                         return;
961                 }
962                 // We aren't expecting staggered output from target
963                 // if (cs_more(t->link))
964                 //    iochan_setevent(i, EVENT_INPUT);
965             }
966             else  // we throw away response and go to idle mode
967             {
968                 yaz_log(YLOG_DEBUG, "Ignoring result of expired operation");
969                 cl->state = Client_Idle;
970             }
971         }
972         /* if len==1 we do nothing but wait for more input */
973     }
974
975     if (cl->state == Client_Connected) {
976         send_init(i);
977     }
978
979     if (cl->state == Client_Idle)
980     {
981         if (cl->requestid != se->requestid && *se->query) {
982             send_search(i);
983         }
984         else if (cl->hits > 0 && cl->records < global_parameters.toget &&
985             cl->records < cl->hits) {
986             send_present(i);
987         }
988     }
989 }
990
991 // Disassociate connection from client
992 static void connection_release(struct connection *co)
993 {
994     struct client *cl = co->client;
995
996     yaz_log(YLOG_DEBUG, "Connection release %s", co->host->hostport);
997     if (!cl)
998         return;
999     cl->connection = 0;
1000     co->client = 0;
1001 }
1002
1003 // Close connection and recycle structure
1004 static void connection_destroy(struct connection *co)
1005 {
1006     struct host *h = co->host;
1007     cs_close(co->link);
1008     iochan_destroy(co->iochan);
1009
1010     yaz_log(YLOG_DEBUG, "Connection destroy %s", co->host->hostport);
1011     if (h->connections == co)
1012         h->connections = co->next;
1013     else
1014     {
1015         struct connection *pco;
1016         for (pco = h->connections; pco && pco->next != co; pco = pco->next)
1017             ;
1018         if (pco)
1019             pco->next = co->next;
1020         else
1021             abort();
1022     }
1023     if (co->client)
1024     {
1025         if (co->client->state != Client_Idle)
1026             co->client->state = Client_Disconnected;
1027         co->client->connection = 0;
1028     }
1029     co->next = connection_freelist;
1030     connection_freelist = co;
1031 }
1032
1033 // Creates a new connection for client, associated with the host of 
1034 // client's database
1035 static struct connection *connection_create(struct client *cl)
1036 {
1037     struct connection *new;
1038     COMSTACK link; 
1039     int res;
1040     void *addr;
1041
1042
1043     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
1044         {
1045             yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
1046             exit(1);
1047         }
1048     
1049     if (0 == strlen(global_parameters.zproxy_override)){
1050         /* no Z39.50 proxy needed - direct connect */
1051         yaz_log(YLOG_DEBUG, "Connection create %s", cl->database->database->url);
1052         
1053         if (!(addr = cs_straddr(link, cl->database->database->host->ipport)))
1054             {
1055                 yaz_log(YLOG_WARN|YLOG_ERRNO, 
1056                         "Lookup of IP address %s failed", 
1057                         cl->database->database->host->ipport);
1058                 return 0;
1059             }
1060     
1061     } else {
1062         /* Z39.50 proxy connect */
1063         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
1064                 cl->database->database->url, global_parameters.zproxy_override);
1065
1066         if (!(addr = cs_straddr(link, global_parameters.zproxy_override)))
1067             {
1068                 yaz_log(YLOG_WARN|YLOG_ERRNO, 
1069                         "Lookup of IP address %s failed", 
1070                         global_parameters.zproxy_override);
1071                 return 0;
1072             }
1073     }
1074
1075     res = cs_connect(link, addr);
1076     if (res < 0)
1077     {
1078         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s", cl->database->database->url);
1079         return 0;
1080     }
1081
1082     if ((new = connection_freelist))
1083         connection_freelist = new->next;
1084     else
1085     {
1086         new = xmalloc(sizeof (struct connection));
1087         new->ibuf = 0;
1088         new->ibufsize = 0;
1089     }
1090     new->state = Conn_Connecting;
1091     new->host = cl->database->database->host;
1092     new->next = new->host->connections;
1093     new->host->connections = new;
1094     new->client = cl;
1095     cl->connection = new;
1096     new->link = link;
1097
1098     new->iochan = iochan_create(cs_fileno(link), handler, 0);
1099     iochan_setdata(new->iochan, new);
1100     new->iochan->next = channel_list;
1101     channel_list = new->iochan;
1102     return new;
1103 }
1104
1105 // Close connection and set state to error
1106 static void client_fatal(struct client *cl)
1107 {
1108     yaz_log(YLOG_WARN, "Fatal error from %s", cl->database->database->url);
1109     connection_destroy(cl->connection);
1110     cl->state = Client_Error;
1111 }
1112
1113 // Ensure that client has a connection associated
1114 static int client_prep_connection(struct client *cl)
1115 {
1116     struct connection *co;
1117     struct session *se = cl->session;
1118     struct host *host = cl->database->database->host;
1119
1120     co = cl->connection;
1121
1122     yaz_log(YLOG_DEBUG, "Client prep %s", cl->database->database->url);
1123
1124     if (!co)
1125     {
1126         // See if someone else has an idle connection
1127         // We should look at timestamps here to select the longest-idle connection
1128         for (co = host->connections; co; co = co->next)
1129             if (co->state == Conn_Open && (!co->client || co->client->session != se))
1130                 break;
1131         if (co)
1132         {
1133             connection_release(co);
1134             cl->connection = co;
1135             co->client = cl;
1136         }
1137         else
1138             co = connection_create(cl);
1139     }
1140     if (co)
1141     {
1142         if (co->state == Conn_Connecting)
1143         {
1144             cl->state = Client_Connecting;
1145             iochan_setflag(co->iochan, EVENT_OUTPUT);
1146         }
1147         else if (co->state == Conn_Open)
1148         {
1149             if (cl->state == Client_Error || cl->state == Client_Disconnected)
1150                 cl->state = Client_Idle;
1151             iochan_setflag(co->iochan, EVENT_OUTPUT);
1152         }
1153         return 1;
1154     }
1155     else
1156         return 0;
1157 }
1158
1159 static struct client *client_create(void)
1160 {
1161     struct client *r;
1162     if (client_freelist)
1163     {
1164         r = client_freelist;
1165         client_freelist = client_freelist->next;
1166     }
1167     else
1168         r = xmalloc(sizeof(struct client));
1169     r->database = 0;
1170     r->connection = 0;
1171     r->session = 0;
1172     r->hits = 0;
1173     r->records = 0;
1174     r->setno = 0;
1175     r->requestid = -1;
1176     r->diagnostic = 0;
1177     r->state = Client_Disconnected;
1178     r->next = 0;
1179     return r;
1180 }
1181
1182 void client_destroy(struct client *c)
1183 {
1184     struct session *se = c->session;
1185     if (c == se->clients)
1186         se->clients = c->next;
1187     else
1188     {
1189         struct client *cc;
1190         for (cc = se->clients; cc && cc->next != c; cc = cc->next)
1191             ;
1192         if (cc)
1193             cc->next = c->next;
1194     }
1195     if (c->connection)
1196         connection_release(c->connection);
1197     c->next = client_freelist;
1198     client_freelist = c;
1199 }
1200
1201 void session_set_watch(struct session *s, int what, session_watchfun fun, void *data)
1202 {
1203     s->watchlist[what].fun = fun;
1204     s->watchlist[what].data = data;
1205 }
1206
1207 void session_alert_watch(struct session *s, int what)
1208 {
1209     if (!s->watchlist[what].fun)
1210         return;
1211     (*s->watchlist[what].fun)(s->watchlist[what].data);
1212     s->watchlist[what].fun = 0;
1213     s->watchlist[what].data = 0;
1214 }
1215
1216 //callback for grep_databases
1217 static void select_targets_callback(void *context, struct session_database *db)
1218 {
1219     struct session *se = (struct session*) context;
1220     struct client *cl = client_create();
1221     cl->database = db;
1222     cl->session = se;
1223     cl->next = se->clients;
1224     se->clients = cl;
1225 }
1226
1227 // Associates a set of clients with a session;
1228 // Note: Session-databases represent databases with per-session setting overrides
1229 int select_targets(struct session *se, struct database_criterion *crit)
1230 {
1231     while (se->clients)
1232         client_destroy(se->clients);
1233
1234     return session_grep_databases(se, crit, select_targets_callback);
1235 }
1236
1237 int session_active_clients(struct session *s)
1238 {
1239     struct client *c;
1240     int res = 0;
1241
1242     for (c = s->clients; c; c = c->next)
1243         if (c->connection && (c->state == Client_Connecting ||
1244                     c->state == Client_Initializing ||
1245                     c->state == Client_Searching ||
1246                     c->state == Client_Presenting))
1247             res++;
1248
1249     return res;
1250 }
1251
1252 // parses crit1=val1,crit2=val2|val3,...
1253 static struct database_criterion *parse_filter(NMEM m, const char *buf)
1254 {
1255     struct database_criterion *res = 0;
1256     char **values;
1257     int num;
1258     int i;
1259
1260     if (!buf || !*buf)
1261         return 0;
1262     nmem_strsplit(m, ",", buf,  &values, &num);
1263     for (i = 0; i < num; i++)
1264     {
1265         char **subvalues;
1266         int subnum;
1267         int subi;
1268         struct database_criterion *new = nmem_malloc(m, sizeof(*new));
1269         char *eq = strchr(values[i], '=');
1270         if (!eq)
1271         {
1272             yaz_log(YLOG_WARN, "Missing equal-sign in filter");
1273             return 0;
1274         }
1275         *(eq++) = '\0';
1276         new->name = values[i];
1277         nmem_strsplit(m, "|", eq, &subvalues, &subnum);
1278         new->values = 0;
1279         for (subi = 0; subi < subnum; subi++)
1280         {
1281             struct database_criterion_value *newv = nmem_malloc(m, sizeof(*newv));
1282             newv->value = subvalues[subi];
1283             newv->next = new->values;
1284             new->values = newv;
1285         }
1286         new->next = res;
1287         res = new;
1288     }
1289     return res;
1290 }
1291
1292 char *search(struct session *se, char *query, char *filter)
1293 {
1294     int live_channels = 0;
1295     struct client *cl;
1296     struct database_criterion *criteria;
1297
1298     yaz_log(YLOG_DEBUG, "Search");
1299
1300     nmem_reset(se->nmem);
1301     criteria = parse_filter(se->nmem, filter);
1302     strcpy(se->query, query);
1303     se->requestid++;
1304     select_targets(se, criteria);
1305     for (cl = se->clients; cl; cl = cl->next)
1306     {
1307         if (client_prep_connection(cl))
1308             live_channels++;
1309     }
1310     if (live_channels)
1311     {
1312         int maxrecs = live_channels * global_parameters.toget;
1313         se->num_termlists = 0;
1314         se->reclist = reclist_create(se->nmem, maxrecs);
1315         // This will be initialized in send_search()
1316         se->relevance = 0;
1317         se->total_records = se->total_hits = se->total_merged = 0;
1318         se->expected_maxrecs = maxrecs;
1319     }
1320     else
1321         return "NOTARGETS";
1322
1323     return 0;
1324 }
1325
1326 // Apply a session override to a database
1327 void session_apply_setting(struct session *se, char *dbname, char *setting, char *value)
1328 {
1329     struct session_database *sdb;
1330
1331     for (sdb = se->databases; sdb; sdb = sdb->next)
1332         if (!strcmp(dbname, sdb->database->url))
1333         {
1334             struct setting *new = nmem_malloc(se->session_nmem, sizeof(*new));
1335             int offset = settings_offset(setting);
1336
1337             if (offset < 0)
1338             {
1339                 yaz_log(YLOG_WARN, "Unknown setting %s", setting);
1340                 return;
1341             }
1342             new->precedence = 0;
1343             new->target = dbname;
1344             new->name = setting;
1345             new->value = value;
1346             new->user = "";
1347             new->next = sdb->settings[offset];
1348             sdb->settings[offset] = new;
1349             break;
1350         }
1351     if (!sdb)
1352         yaz_log(YLOG_WARN, "Unknown database in setting override: %s", dbname);
1353 }
1354
1355 void session_init_databases_fun(void *context, struct database *db)
1356 {
1357     struct session *se = (struct session *) context;
1358     struct session_database *new = nmem_malloc(se->session_nmem, sizeof(*new));
1359     int num = settings_num();
1360     int i;
1361
1362     new->database = db;
1363     new->settings = nmem_malloc(se->session_nmem, sizeof(struct settings *) * num);
1364     for (i = 0; i < num; i++)
1365         new->settings[i] = db->settings[i];
1366     new->next = se->databases;
1367     se->databases = new;
1368 }
1369
1370 // Initialize session_database list -- this represents this session's view
1371 // of the database list -- subject to modification by the settings ws command
1372 void session_init_databases(struct session *se)
1373 {
1374     se->databases = 0;
1375     grep_databases(se, 0, session_init_databases_fun);
1376 }
1377
1378 void destroy_session(struct session *s)
1379 {
1380     yaz_log(YLOG_LOG, "Destroying session");
1381     while (s->clients)
1382         client_destroy(s->clients);
1383     nmem_destroy(s->nmem);
1384     wrbuf_destroy(s->wrbuf);
1385 }
1386
1387 struct session *new_session(NMEM nmem) 
1388 {
1389     int i;
1390     struct session *session = nmem_malloc(nmem, sizeof(*session));
1391
1392     yaz_log(YLOG_DEBUG, "New Pazpar2 session");
1393     
1394     session->total_hits = 0;
1395     session->total_records = 0;
1396     session->num_termlists = 0;
1397     session->reclist = 0;
1398     session->requestid = -1;
1399     session->clients = 0;
1400     session->expected_maxrecs = 0;
1401     session->query[0] = '\0';
1402     session->session_nmem = nmem;
1403     session->nmem = nmem_create();
1404     session->wrbuf = wrbuf_alloc();
1405     session_init_databases(session);
1406     for (i = 0; i <= SESSION_WATCH_MAX; i++)
1407     {
1408         session->watchlist[i].data = 0;
1409         session->watchlist[i].fun = 0;
1410     }
1411
1412     return session;
1413 }
1414
1415 struct hitsbytarget *hitsbytarget(struct session *se, int *count)
1416 {
1417     static struct hitsbytarget res[1000]; // FIXME MM
1418     struct client *cl;
1419
1420     *count = 0;
1421     for (cl = se->clients; cl; cl = cl->next)
1422     {
1423         res[*count].id = cl->database->database->url;
1424         res[*count].name = cl->database->database->name;
1425         res[*count].hits = cl->hits;
1426         res[*count].records = cl->records;
1427         res[*count].diagnostic = cl->diagnostic;
1428         res[*count].state = client_states[cl->state];
1429         res[*count].connected  = cl->connection ? 1 : 0;
1430         (*count)++;
1431     }
1432
1433     return res;
1434 }
1435
1436 struct termlist_score **termlist(struct session *s, const char *name, int *num)
1437 {
1438     int i;
1439
1440     for (i = 0; i < s->num_termlists; i++)
1441         if (!strcmp((const char *) s->termlists[i].name, name))
1442             return termlist_highscore(s->termlists[i].termlist, num);
1443     return 0;
1444 }
1445
1446 #ifdef MISSING_HEADERS
1447 void report_nmem_stats(void)
1448 {
1449     size_t in_use, is_free;
1450
1451     nmem_get_memory_in_use(&in_use);
1452     nmem_get_memory_free(&is_free);
1453
1454     yaz_log(YLOG_LOG, "nmem stat: use=%ld free=%ld", 
1455             (long) in_use, (long) is_free);
1456 }
1457 #endif
1458
1459 struct record_cluster *show_single(struct session *s, int id)
1460 {
1461     struct record_cluster *r;
1462
1463     reclist_rewind(s->reclist);
1464     while ((r = reclist_read_record(s->reclist)))
1465         if (r->recid == id)
1466             return r;
1467     return 0;
1468 }
1469
1470 struct record_cluster **show(struct session *s, struct reclist_sortparms *sp, int start,
1471         int *num, int *total, int *sumhits, NMEM nmem_show)
1472 {
1473     struct record_cluster **recs = nmem_malloc(nmem_show, *num 
1474                                        * sizeof(struct record_cluster *));
1475     struct reclist_sortparms *spp;
1476     int i;
1477 #if USE_TIMING    
1478     yaz_timing_t t = yaz_timing_create();
1479 #endif
1480
1481     for (spp = sp; spp; spp = spp->next)
1482         if (spp->type == Metadata_sortkey_relevance)
1483         {
1484             relevance_prepare_read(s->relevance, s->reclist);
1485             break;
1486         }
1487     reclist_sort(s->reclist, sp);
1488
1489     *total = s->reclist->num_records;
1490     *sumhits = s->total_hits;
1491
1492     for (i = 0; i < start; i++)
1493         if (!reclist_read_record(s->reclist))
1494         {
1495             *num = 0;
1496             recs = 0;
1497             break;
1498         }
1499
1500     for (i = 0; i < *num; i++)
1501     {
1502         struct record_cluster *r = reclist_read_record(s->reclist);
1503         if (!r)
1504         {
1505             *num = i;
1506             break;
1507         }
1508         recs[i] = r;
1509     }
1510 #if USE_TIMING
1511     yaz_timing_stop(t);
1512     yaz_log(YLOG_LOG, "show %6.5f %3.2f %3.2f", 
1513             yaz_timing_get_real(t), yaz_timing_get_user(t),
1514             yaz_timing_get_sys(t));
1515     yaz_timing_destroy(&t);
1516 #endif
1517     return recs;
1518 }
1519
1520 void statistics(struct session *se, struct statistics *stat)
1521 {
1522     struct client *cl;
1523     int count = 0;
1524
1525     memset(stat, 0, sizeof(*stat));
1526     for (cl = se->clients; cl; cl = cl->next)
1527     {
1528         if (!cl->connection)
1529             stat->num_no_connection++;
1530         switch (cl->state)
1531         {
1532             case Client_Connecting: stat->num_connecting++; break;
1533             case Client_Initializing: stat->num_initializing++; break;
1534             case Client_Searching: stat->num_searching++; break;
1535             case Client_Presenting: stat->num_presenting++; break;
1536             case Client_Idle: stat->num_idle++; break;
1537             case Client_Failed: stat->num_failed++; break;
1538             case Client_Error: stat->num_error++; break;
1539             default: break;
1540         }
1541         count++;
1542     }
1543     stat->num_hits = se->total_hits;
1544     stat->num_records = se->total_records;
1545
1546     stat->num_clients = count;
1547 }
1548
1549 static void start_http_listener(void)
1550 {
1551     char hp[128] = "";
1552     struct conf_server *ser = global_parameters.server;
1553
1554     if (*global_parameters.listener_override)
1555         strcpy(hp, global_parameters.listener_override);
1556     else
1557     {
1558         strcpy(hp, ser->host ? ser->host : "");
1559         if (ser->port)
1560         {
1561             if (*hp)
1562                 strcat(hp, ":");
1563             sprintf(hp + strlen(hp), "%d", ser->port);
1564         }
1565     }
1566     http_init(hp);
1567 }
1568
1569 static void start_proxy(void)
1570 {
1571     char hp[128] = "";
1572     struct conf_server *ser = global_parameters.server;
1573
1574     if (*global_parameters.proxy_override)
1575         strcpy(hp, global_parameters.proxy_override);
1576     else if (ser->proxy_host || ser->proxy_port)
1577     {
1578         strcpy(hp, ser->proxy_host ? ser->proxy_host : "");
1579         if (ser->proxy_port)
1580         {
1581             if (*hp)
1582                 strcat(hp, ":");
1583             sprintf(hp + strlen(hp), "%d", ser->proxy_port);
1584         }
1585     }
1586     else
1587         return;
1588
1589     http_set_proxyaddr(hp, ser->myurl ? ser->myurl : "");
1590 }
1591
1592 static void start_zproxy(void)
1593 {
1594     struct conf_server *ser = global_parameters.server;
1595
1596     if (*global_parameters.zproxy_override){
1597         yaz_log(YLOG_LOG, "Z39.50 proxy  %s", 
1598                 global_parameters.zproxy_override);
1599         return;
1600     }
1601
1602     else if (ser->zproxy_host || ser->zproxy_port)
1603     {
1604         char hp[128] = "";
1605
1606         strcpy(hp, ser->zproxy_host ? ser->zproxy_host : "");
1607         if (ser->zproxy_port)
1608         {
1609             if (*hp)
1610                 strcat(hp, ":");
1611             else
1612                 strcat(hp, "@:");
1613
1614             sprintf(hp + strlen(hp), "%d", ser->zproxy_port);
1615         }
1616         strcpy(global_parameters.zproxy_override, hp);
1617         yaz_log(YLOG_LOG, "Z39.50 proxy  %s", 
1618                 global_parameters.zproxy_override);
1619
1620     }
1621     else
1622         return;
1623 }
1624
1625
1626
1627 int main(int argc, char **argv)
1628 {
1629     int ret;
1630     char *arg;
1631
1632     if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
1633         yaz_log(YLOG_WARN|YLOG_ERRNO, "signal");
1634
1635     yaz_log_init(YLOG_DEFAULT_LEVEL, "pazpar2", 0);
1636
1637     while ((ret = options("t:f:x:h:p:z:s:d", argv, argc, &arg)) != -2)
1638     {
1639         switch (ret) {
1640             case 'f':
1641                 if (!read_config(arg))
1642                     exit(1);
1643                 break;
1644             case 'h':
1645                 strcpy(global_parameters.listener_override, arg);
1646                 break;
1647             case 'p':
1648                 strcpy(global_parameters.proxy_override, arg);
1649                 break;
1650             case 'z':
1651                 strcpy(global_parameters.zproxy_override, arg);
1652                 break;
1653             case 't':
1654                 strcpy(global_parameters.settings_path_override, arg);
1655                 break;
1656             case 's':
1657                 load_simpletargets(arg);
1658                 break;
1659             case 'd':
1660                 global_parameters.dump_records = 1;
1661                 break;
1662             default:
1663                 fprintf(stderr, "Usage: pazpar2\n"
1664                         "    -f configfile\n"
1665                         "    -h [host:]port          (REST protocol listener)\n"
1666                         "    -C cclconfig\n"
1667                         "    -s simpletargetfile\n"
1668                         "    -p hostname[:portno]    (HTTP proxy)\n"
1669                         "    -z hostname[:portno]    (Z39.50 proxy)\n"
1670                         "    -d                      (show internal records)\n");
1671                 exit(1);
1672         }
1673     }
1674
1675     if (!config)
1676     {
1677         yaz_log(YLOG_FATAL, "Load config with -f");
1678         exit(1);
1679     }
1680     global_parameters.server = config->servers;
1681
1682     start_http_listener();
1683     start_proxy();
1684     start_zproxy();
1685
1686     if (*global_parameters.settings_path_override)
1687         settings_read(global_parameters.settings_path_override);
1688     else if (global_parameters.server->settings)
1689         settings_read(global_parameters.server->settings);
1690     else
1691         yaz_log(YLOG_WARN, "No settings-directory specified. Problems may well ensue!");
1692     prepare_databases();
1693     global_parameters.odr_in = odr_createmem(ODR_DECODE);
1694     global_parameters.odr_out = odr_createmem(ODR_ENCODE);
1695
1696     event_loop(&channel_list);
1697
1698     return 0;
1699 }
1700
1701 /*
1702  * Local variables:
1703  * c-basic-offset: 4
1704  * indent-tabs-mode: nil
1705  * End:
1706  * vim: shiftwidth=4 tabstop=8 expandtab
1707  */