Fix problem hit count caching in async mode
[yaz-moved-to-github.git] / src / zoom-c.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file zoom-c.c
7  * \brief Implements ZOOM C interface.
8  */
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <assert.h>
14 #include <string.h>
15 #include <errno.h>
16 #include "zoom-p.h"
17
18 #include <yaz/yaz-util.h>
19 #include <yaz/xmalloc.h>
20 #include <yaz/otherinfo.h>
21 #include <yaz/log.h>
22 #include <yaz/diagbib1.h>
23 #include <yaz/charneg.h>
24 #include <yaz/query-charset.h>
25 #include <yaz/snprintf.h>
26 #include <yaz/facet.h>
27
28 #include <yaz/shptr.h>
29
30 #if SHPTR
31 YAZ_SHPTR_TYPE(WRBUF)
32 #endif
33
34 static int log_api0 = 0;
35 static int log_details0 = 0;
36
37 static void resultset_destroy(ZOOM_resultset r);
38 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out);
39
40 static void initlog(void)
41 {
42     static int log_level_initialized = 0;
43
44     if (!log_level_initialized)
45     {
46         log_api0 = yaz_log_module_level("zoom");
47         log_details0 = yaz_log_module_level("zoomdetails");
48         log_level_initialized = 1;
49     }
50 }
51
52 void ZOOM_connection_remove_tasks(ZOOM_connection c);
53
54 void ZOOM_set_dset_error(ZOOM_connection c, int error,
55                          const char *dset,
56                          const char *addinfo, const char *addinfo2)
57 {
58     char *cp;
59
60     xfree(c->addinfo);
61     c->addinfo = 0;
62     c->error = error;
63     if (!c->diagset || strcmp(dset, c->diagset))
64     {
65         xfree(c->diagset);
66         c->diagset = xstrdup(dset);
67         /* remove integer part from SRW diagset .. */
68         if ((cp = strrchr(c->diagset, '/')))
69             *cp = '\0';
70     }
71     if (addinfo && addinfo2)
72     {
73         c->addinfo = (char*) xmalloc(strlen(addinfo) + strlen(addinfo2) + 3);
74         strcpy(c->addinfo, addinfo);
75         strcat(c->addinfo, ": ");
76         strcat(c->addinfo, addinfo2);
77     }
78     else if (addinfo)
79         c->addinfo = xstrdup(addinfo);
80     if (error != ZOOM_ERROR_NONE)
81     {
82         yaz_log(c->log_api, "%p set_dset_error %s %s:%d %s %s",
83                 c, c->host_port ? c->host_port : "<>", dset, error,
84                 addinfo ? addinfo : "",
85                 addinfo2 ? addinfo2 : "");
86         ZOOM_connection_remove_tasks(c);
87     }
88 }
89
90 int ZOOM_uri_to_code(const char *uri)
91 {
92     int code = 0;
93     const char *cp;
94     if ((cp = strrchr(uri, '/')))
95         code = atoi(cp+1);
96     return code;
97 }
98
99 void ZOOM_set_error(ZOOM_connection c, int error, const char *addinfo)
100 {
101     ZOOM_set_dset_error(c, error, "ZOOM", addinfo, 0);
102 }
103
104 static void clear_error(ZOOM_connection c)
105 {
106     /*
107      * If an error is tied to an operation then it's ok to clear: for
108      * example, a diagnostic returned from a search is cleared by a
109      * subsequent search.  However, problems such as Connection Lost
110      * or Init Refused are not cleared, because they are not
111      * recoverable: doing another search doesn't help.
112      */
113
114     ZOOM_connection_remove_events(c);
115     switch (c->error)
116     {
117     case ZOOM_ERROR_CONNECT:
118     case ZOOM_ERROR_MEMORY:
119     case ZOOM_ERROR_DECODE:
120     case ZOOM_ERROR_CONNECTION_LOST:
121     case ZOOM_ERROR_INIT:
122     case ZOOM_ERROR_INTERNAL:
123     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
124         break;
125     default:
126         ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
127     }
128 }
129
130 void ZOOM_connection_show_task(ZOOM_task task)
131 {
132     switch(task->which)
133     {
134     case ZOOM_TASK_SEARCH:
135         yaz_log(YLOG_LOG, "search p=%p", task);
136         break;
137     case ZOOM_TASK_CONNECT:
138         yaz_log(YLOG_LOG, "connect p=%p", task);
139         break;
140     case ZOOM_TASK_SCAN:
141         yaz_log(YLOG_LOG, "scan p=%p", task);
142         break;
143     }
144 }
145
146 void ZOOM_connection_show_tasks(ZOOM_connection c)
147 {
148     ZOOM_task task;
149     yaz_log(YLOG_LOG, "connection p=%p tasks", c);
150     for (task = c->tasks; task; task = task->next)
151         ZOOM_connection_show_task(task);
152 }
153
154 ZOOM_task ZOOM_connection_add_task(ZOOM_connection c, int which)
155 {
156     ZOOM_task *taskp = &c->tasks;
157     while (*taskp)
158         taskp = &(*taskp)->next;
159     *taskp = (ZOOM_task) xmalloc(sizeof(**taskp));
160     (*taskp)->running = 0;
161     (*taskp)->which = which;
162     (*taskp)->next = 0;
163     clear_error(c);
164     return *taskp;
165 }
166
167 ZOOM_API(int) ZOOM_connection_is_idle(ZOOM_connection c)
168 {
169     return c->tasks ? 0 : 1;
170 }
171
172 ZOOM_task ZOOM_connection_insert_task(ZOOM_connection c, int which)
173 {
174     ZOOM_task task = (ZOOM_task) xmalloc(sizeof(*task));
175
176     task->next = c->tasks;
177     c->tasks = task;
178
179     task->running = 0;
180     task->which = which;
181     return task;
182 }
183
184 void ZOOM_connection_remove_task(ZOOM_connection c)
185 {
186     ZOOM_task task = c->tasks;
187
188     if (task)
189     {
190         c->tasks = task->next;
191         switch (task->which)
192         {
193         case ZOOM_TASK_SEARCH:
194             resultset_destroy(task->u.search.resultset);
195             xfree(task->u.search.syntax);
196             xfree(task->u.search.elementSetName);
197             xfree(task->u.search.schema);
198             break;
199         case ZOOM_TASK_CONNECT:
200             break;
201         case ZOOM_TASK_SCAN:
202             ZOOM_scanset_destroy(task->u.scan.scan);
203             break;
204         case ZOOM_TASK_PACKAGE:
205             ZOOM_package_destroy(task->u.package);
206             break;
207         case ZOOM_TASK_SORT:
208             resultset_destroy(task->u.sort.resultset);
209             ZOOM_query_destroy(task->u.sort.q);
210             break;
211         default:
212             assert(0);
213         }
214         xfree(task);
215
216         if (!c->tasks)
217         {
218             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_END);
219             ZOOM_connection_put_event(c, event);
220         }
221     }
222 }
223
224 void ZOOM_connection_remove_tasks(ZOOM_connection c)
225 {
226     while (c->tasks)
227         ZOOM_connection_remove_task(c);
228 }
229
230 static void odr_wrbuf_write(ODR o, void *handle, int type,
231                             const char *buf, int len)
232 {
233     WRBUF w = (WRBUF) handle;
234     wrbuf_write(w, buf, len);
235 }
236
237 ZOOM_API(ZOOM_connection)
238     ZOOM_connection_create(ZOOM_options options)
239 {
240     ZOOM_connection c = (ZOOM_connection) xmalloc(sizeof(*c));
241
242     initlog();
243
244     c->log_api = log_api0;
245     c->log_details = log_details0;
246
247     yaz_log(c->log_api, "%p ZOOM_connection_create", c);
248
249     c->proto = PROTO_Z3950;
250     c->cs = 0;
251     ZOOM_connection_set_mask(c, 0);
252     c->reconnect_ok = 0;
253     c->state = STATE_IDLE;
254     c->addinfo = 0;
255     c->diagset = 0;
256     ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
257     c->buf_in = 0;
258     c->len_in = 0;
259     c->buf_out = 0;
260     c->len_out = 0;
261     c->resultsets = 0;
262
263     c->options = ZOOM_options_create_with_parent(options);
264
265     c->host_port = 0;
266     c->proxy = 0;
267     c->tproxy = 0;
268
269     c->charset = c->lang = 0;
270
271     c->cookie_out = 0;
272     c->cookie_in = 0;
273     c->client_IP = 0;
274     c->tasks = 0;
275
276     c->user = 0;
277     c->group = 0;
278     c->password = 0;
279     c->url_authentication = 0;
280
281     c->maximum_record_size = 0;
282     c->preferred_message_size = 0;
283
284     c->odr_in = odr_createmem(ODR_DECODE);
285     c->odr_out = odr_createmem(ODR_ENCODE);
286     c->odr_print = 0;
287     c->odr_save = 0;
288
289     c->async = 0;
290     c->support_named_resultsets = 0;
291     c->last_event = ZOOM_EVENT_NONE;
292
293     c->m_queue_front = 0;
294     c->m_queue_back = 0;
295
296     c->sru_version = 0;
297     c->no_redirects = 0;
298     c->cookies = 0;
299     c->saveAPDU_wrbuf = 0;
300
301 #if HAVE_LIBMEMCACHED_MEMCACHED_H
302     c->mc_st = 0;
303 #endif
304     return c;
305 }
306
307 ZOOM_API(void) ZOOM_connection_save_apdu_wrbuf(ZOOM_connection c, WRBUF w)
308 {
309     if (c->odr_save)
310     {
311         odr_destroy(c->odr_save);
312         c->odr_save = 0;
313     }
314     if (w)
315     {
316         c->odr_save = odr_createmem(ODR_PRINT);
317         odr_set_stream(c->odr_save, w, odr_wrbuf_write, 0);
318     }
319 }
320
321 /* set database names. Take local databases (if set); otherwise
322    take databases given in ZURL (if set); otherwise use Default */
323 char **ZOOM_connection_get_databases(ZOOM_connection con, ZOOM_options options,
324                                      int *num, ODR odr)
325 {
326     char **databaseNames;
327     const char *cp = ZOOM_options_get(options, "databaseName");
328
329     if ((!cp || !*cp) && con->host_port)
330         cs_get_host_args(con->host_port, &cp);
331     if (!cp || !*cp)
332         cp = "Default";
333     nmem_strsplit(odr_getmem(odr), "+", cp,  &databaseNames, num);
334     return databaseNames;
335 }
336
337 ZOOM_API(ZOOM_connection)
338     ZOOM_connection_new(const char *host, int portnum)
339 {
340     ZOOM_connection c = ZOOM_connection_create(0);
341
342     ZOOM_connection_connect(c, host, portnum);
343     return c;
344 }
345
346 static zoom_sru_mode get_sru_mode_from_string(const char *s)
347 {
348     if (!s || !*s)
349         return zoom_sru_soap;
350     if (!yaz_matchstr(s, "soap"))
351         return zoom_sru_soap;
352     else if (!yaz_matchstr(s, "get"))
353         return zoom_sru_get;
354     else if (!yaz_matchstr(s, "post"))
355         return zoom_sru_post;
356     else if (!yaz_matchstr(s, "solr"))
357         return zoom_sru_solr;
358     return zoom_sru_error;
359 }
360
361 ZOOM_API(void)
362     ZOOM_connection_connect(ZOOM_connection c,
363                             const char *host, int portnum)
364 {
365     const char *val;
366     const char *http_lead;
367
368     initlog();
369
370     yaz_log(c->log_api, "%p ZOOM_connection_connect host=%s portnum=%d",
371             c, host ? host : "null", portnum);
372
373     ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
374     ZOOM_connection_remove_tasks(c);
375
376     if (c->odr_print)
377     {
378         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
379         odr_destroy(c->odr_print);
380     }
381     if (ZOOM_options_get_bool(c->options, "apdulog", 0))
382     {
383         c->odr_print = odr_createmem(ODR_PRINT);
384         odr_setprint(c->odr_print, yaz_log_file());
385     }
386     else
387         c->odr_print = 0;
388
389     if (c->cs)
390     {
391         yaz_log(c->log_details, "%p ZOOM_connection_connect reconnect ok", c);
392         c->reconnect_ok = 1;
393         return;
394     }
395     yaz_log(c->log_details, "%p ZOOM_connection_connect connect", c);
396     xfree(c->proxy);
397     c->proxy = 0;
398     val = ZOOM_options_get(c->options, "proxy");
399     if (val && *val)
400     {
401         yaz_log(c->log_details, "%p ZOOM_connection_connect proxy=%s", c, val);
402         c->proxy = xstrdup(val);
403     }
404
405     xfree(c->tproxy);
406     c->tproxy = 0;
407     val = ZOOM_options_get(c->options, "tproxy");
408     if (val && *val)
409     {
410         yaz_log(c->log_details, "%p ZOOM_connection_connect tproxy=%s", c, val);
411         c->tproxy = xstrdup(val);
412     }
413
414     xfree(c->charset);
415     c->charset = 0;
416     val = ZOOM_options_get(c->options, "charset");
417     if (val && *val)
418     {
419         yaz_log(c->log_details, "%p ZOOM_connection_connect charset=%s", c, val);
420         c->charset = xstrdup(val);
421     }
422
423     xfree(c->lang);
424     val = ZOOM_options_get(c->options, "lang");
425     if (val && *val)
426     {
427         yaz_log(c->log_details, "%p ZOOM_connection_connect lang=%s", c, val);
428         c->lang = xstrdup(val);
429     }
430     else
431         c->lang = 0;
432
433     val = ZOOM_options_get(c->options, "sru");
434     if (val && *val && !strstr(host, "://"))
435         http_lead = "http://";
436     else
437         http_lead = "";
438     c->sru_mode = get_sru_mode_from_string(val);
439
440     if (host)
441     {
442         char hostn[128];
443         xfree(c->host_port);
444         if (portnum)
445         {
446             sprintf(hostn, "%.80s:%d", host, portnum);
447             host = hostn;
448         }
449         c->host_port = xmalloc(strlen(host) + strlen(http_lead) + 1);
450         strcpy(c->host_port, http_lead);
451         strcat(c->host_port, host);
452     }
453
454     {
455         /*
456          * If the "<scheme>:" part of the host string is preceded by one
457          * or more comma-separated <name>=<value> pairs, these are taken
458          * to be options to be set on the connection object.  Among other
459          * applications, this facility can be used to embed authentication
460          * in a host string:
461          *          user=admin,password=secret,tcp:localhost:9999
462          */
463         char *remainder = c->host_port;
464         char *pcolon = strchr(remainder, ':');
465         char *pcomma;
466         char *pequals;
467         while ((pcomma = strchr(remainder, ',')) != 0 &&
468                (pcolon == 0 || pcomma < pcolon))
469         {
470             *pcomma = '\0';
471             if ((pequals = strchr(remainder, '=')) != 0)
472             {
473                 *pequals = '\0';
474                 ZOOM_connection_option_set(c, remainder, pequals+1);
475             }
476             remainder = pcomma+1;
477         }
478
479         if (remainder != c->host_port)
480         {
481             remainder = xstrdup(remainder);
482             xfree(c->host_port);
483             c->host_port = remainder;
484         }
485     }
486
487     xfree(c->sru_version);
488     val = ZOOM_options_get(c->options, "sru_version");
489     c->sru_version = xstrdup(val ? val : "1.2");
490
491     ZOOM_options_set(c->options, "host", c->host_port);
492
493     xfree(c->cookie_out);
494     c->cookie_out = 0;
495     val = ZOOM_options_get(c->options, "cookie");
496     if (val && *val)
497     {
498         yaz_log(c->log_details, "%p ZOOM_connection_connect cookie=%s", c, val);
499         c->cookie_out = xstrdup(val);
500     }
501
502     xfree(c->client_IP);
503     c->client_IP = 0;
504     val = ZOOM_options_get(c->options, "clientIP");
505     if (val && *val)
506     {
507         yaz_log(c->log_details, "%p ZOOM_connection_connect clientIP=%s",
508                 c, val);
509         c->client_IP = xstrdup(val);
510     }
511
512     xfree(c->group);
513     c->group = 0;
514     val = ZOOM_options_get(c->options, "group");
515     if (val && *val)
516         c->group = xstrdup(val);
517
518     xfree(c->user);
519     c->user = 0;
520     val = ZOOM_options_get(c->options, "user");
521     if (val && *val)
522         c->user = xstrdup(val);
523
524     xfree(c->password);
525     c->password = 0;
526     val = ZOOM_options_get(c->options, "password");
527     if (!val)
528         val = ZOOM_options_get(c->options, "pass");
529     if (val && *val)
530         c->password = xstrdup(val);
531
532     val = ZOOM_options_get(c->options, "authenticationMode");
533     if (val && !strcmp(val, "url"))
534         c->url_authentication = 1;
535     else
536         c->url_authentication = 0;
537
538     c->maximum_record_size =
539         ZOOM_options_get_int(c->options, "maximumRecordSize", 64*1024*1024);
540     c->preferred_message_size =
541         ZOOM_options_get_int(c->options, "preferredMessageSize", 64*1024*1024);
542
543     c->async = ZOOM_options_get_bool(c->options, "async", 0);
544
545     yaz_cookies_destroy(c->cookies);
546     c->cookies = yaz_cookies_create();
547
548 #if HAVE_LIBMEMCACHED_MEMCACHED_H
549     if (c->mc_st)
550     {
551         memcached_free(c->mc_st);
552         c->mc_st = 0;
553     }
554 #endif
555     val = ZOOM_options_get(c->options, "memcached");
556     if (val && *val)
557     {
558 #if HAVE_LIBMEMCACHED_MEMCACHED_H
559         c->mc_st = memcached(val, strlen(val));
560         if (!c->mc_st)
561         {
562             ZOOM_set_error(c, ZOOM_ERROR_MEMCACHED, val);
563             return;
564         }
565 #else
566         ZOOM_set_error(c, ZOOM_ERROR_MEMCACHED, "not enabled");
567         return;
568 #endif
569     }
570
571     if (c->sru_mode == zoom_sru_error)
572     {
573         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, val);
574         ZOOM_connection_remove_tasks(c);
575         return;
576     }
577
578     yaz_log(c->log_details, "%p ZOOM_connection_connect async=%d", c, c->async);
579     ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
580
581     if (!c->async)
582     {
583         while (ZOOM_event(1, &c))
584             ;
585     }
586 }
587
588 ZOOM_API(void) ZOOM_resultset_release(ZOOM_resultset r)
589 {
590     if (r->connection)
591     {
592         /* remove ourselves from the resultsets in connection */
593         ZOOM_resultset *rp = &r->connection->resultsets;
594         while (1)
595         {
596             assert(*rp);   /* we must be in this list!! */
597             if (*rp == r)
598             {   /* OK, we're here - take us out of it */
599                 *rp = (*rp)->next;
600                 break;
601             }
602             rp = &(*rp)->next;
603         }
604         r->connection = 0;
605     }
606 }
607
608 ZOOM_API(void)
609     ZOOM_connection_destroy(ZOOM_connection c)
610 {
611     ZOOM_resultset r;
612     if (!c)
613         return;
614     yaz_log(c->log_api, "%p ZOOM_connection_destroy", c);
615
616 #if HAVE_LIBMEMCACHED_MEMCACHED_H
617     if (c->mc_st)
618         memcached_free(c->mc_st);
619 #endif
620     if (c->cs)
621         cs_close(c->cs);
622
623     for (r = c->resultsets; r; r = r->next)
624         r->connection = 0;
625
626     xfree(c->buf_in);
627     xfree(c->addinfo);
628     xfree(c->diagset);
629     odr_destroy(c->odr_in);
630     odr_destroy(c->odr_out);
631     if (c->odr_save)
632         odr_destroy(c->odr_save);
633     if (c->odr_print)
634     {
635         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
636         odr_destroy(c->odr_print);
637     }
638     ZOOM_options_destroy(c->options);
639     ZOOM_connection_remove_tasks(c);
640     ZOOM_connection_remove_events(c);
641     xfree(c->host_port);
642     xfree(c->proxy);
643     xfree(c->tproxy);
644     xfree(c->charset);
645     xfree(c->lang);
646     xfree(c->cookie_out);
647     xfree(c->cookie_in);
648     xfree(c->client_IP);
649     xfree(c->user);
650     xfree(c->group);
651     xfree(c->password);
652     xfree(c->sru_version);
653     yaz_cookies_destroy(c->cookies);
654     wrbuf_destroy(c->saveAPDU_wrbuf);
655     xfree(c);
656 }
657
658 void ZOOM_resultset_addref(ZOOM_resultset r)
659 {
660     if (r)
661     {
662         yaz_mutex_enter(r->mutex);
663         (r->refcount)++;
664         yaz_log(log_details0, "%p ZOOM_resultset_addref count=%d",
665                 r, r->refcount);
666         yaz_mutex_leave(r->mutex);
667     }
668 }
669
670 static int g_resultsets = 0;
671 static YAZ_MUTEX g_resultset_mutex = 0;
672
673 /* TODO We need to initialize this before running threaded:
674  * call resultset_use(0)  */
675
676 static int resultset_use(int delta) {
677     int resultset_count;
678     if (g_resultset_mutex == 0)
679         yaz_mutex_create(&g_resultset_mutex);
680     yaz_mutex_enter(g_resultset_mutex);
681     g_resultsets += delta;
682     resultset_count = g_resultsets;
683     yaz_mutex_leave(g_resultset_mutex);
684     return resultset_count;
685 }
686
687 int resultsets_count(void) {
688     return resultset_use(0);
689 }
690
691 ZOOM_resultset ZOOM_resultset_create(void)
692 {
693     int i;
694     ZOOM_resultset r = (ZOOM_resultset) xmalloc(sizeof(*r));
695
696     initlog();
697
698     yaz_log(log_details0, "%p ZOOM_resultset_create", r);
699     r->refcount = 1;
700     r->size = 0;
701     r->odr = odr_createmem(ODR_DECODE);
702     r->piggyback = 1;
703     r->setname = 0;
704     r->step = 0;
705     for (i = 0; i<RECORD_HASH_SIZE; i++)
706         r->record_hash[i] = 0;
707     r->r_sort_spec = 0;
708     r->query = 0;
709     r->connection = 0;
710     r->databaseNames = 0;
711     r->num_databaseNames = 0;
712     r->facets = 0;
713     r->num_facets = 0;
714     r->facets_names = 0;
715     r->mutex = 0;
716     yaz_mutex_create(&r->mutex);
717 #if SHPTR
718     {
719         WRBUF w = wrbuf_alloc();
720         YAZ_SHPTR_INIT(r->record_wrbuf, w);
721     }
722 #endif
723     resultset_use(1);
724     r->mc_key = 0;
725     r->live_set = 0;
726     return r;
727 }
728
729 ZOOM_API(ZOOM_resultset)
730     ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
731 {
732     ZOOM_resultset r;
733     ZOOM_query s = ZOOM_query_create();
734
735     ZOOM_query_prefix(s, q);
736
737     r = ZOOM_connection_search(c, s);
738     ZOOM_query_destroy(s);
739     return r;
740 }
741
742 ZOOM_API(ZOOM_resultset)
743     ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
744 {
745     ZOOM_resultset r = ZOOM_resultset_create();
746     const char *cp;
747     ZOOM_task task;
748     int start, count;
749     const char *syntax, *elementSetName, *schema;
750     yaz_log(c->log_api, "%p ZOOM_connection_search set %p query %p", c, r, q);
751     r->r_sort_spec = ZOOM_query_get_sortspec(q);
752     r->query = q;
753     ZOOM_query_addref(q);
754
755     r->options = ZOOM_options_create_with_parent(c->options);
756
757     start = ZOOM_options_get_int(r->options, "start", 0);
758     count = ZOOM_options_get_int(r->options, "count", 0);
759     {
760         /* If "presentChunk" is defined use that; otherwise "step" */
761         const char *cp = ZOOM_options_get(r->options, "presentChunk");
762         r->step = ZOOM_options_get_int(r->options,
763                                        (cp != 0 ? "presentChunk": "step"), 0);
764     }
765     r->piggyback = ZOOM_options_get_bool(r->options, "piggyback", 1);
766     cp = ZOOM_options_get(r->options, "setname");
767     if (cp)
768         r->setname = xstrdup(cp);
769
770     r->databaseNames = ZOOM_connection_get_databases(c, c->options, &r->num_databaseNames,
771                                          r->odr);
772
773     r->connection = c;
774     r->next = c->resultsets;
775     c->resultsets = r;
776
777 #if HAVE_LIBMEMCACHED_MEMCACHED_H
778     r->mc_key = wrbuf_alloc();
779     wrbuf_puts(r->mc_key, "0;");
780     wrbuf_puts(r->mc_key, c->host_port);
781     wrbuf_puts(r->mc_key, ";");
782     if (c->user)
783         wrbuf_puts(r->mc_key, c->user);
784     wrbuf_puts(r->mc_key, ";");
785     if (c->group)
786         wrbuf_puts(r->mc_key, c->group);
787     wrbuf_puts(r->mc_key, ";");
788     if (c->password)
789         wrbuf_sha1_puts(r->mc_key, c->password, 1);
790     wrbuf_puts(r->mc_key, ";");
791     wrbuf_sha1_puts(r->mc_key, ZOOM_query_get_query_string(q), 1);
792     wrbuf_puts(r->mc_key, ";");
793 #endif
794
795     if (c->host_port && c->proto == PROTO_HTTP)
796     {
797         if (!c->cs)
798         {
799             yaz_log(c->log_details, "ZOOM_connection_search: no comstack");
800             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
801         }
802         else
803         {
804             yaz_log(c->log_details, "ZOOM_connection_search: reconnect");
805             c->reconnect_ok = 1;
806         }
807     }
808
809     task = ZOOM_connection_add_task(c, ZOOM_TASK_SEARCH);
810     task->u.search.resultset = r;
811     task->u.search.start = start;
812     task->u.search.count = count;
813
814     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax");
815     task->u.search.syntax = syntax ? xstrdup(syntax) : 0;
816     elementSetName = ZOOM_options_get(r->options, "elementSetName");
817     task->u.search.elementSetName = elementSetName ?
818         xstrdup(elementSetName) : 0;
819     schema = ZOOM_options_get(r->options, "schema");
820     task->u.search.schema = schema ? xstrdup(schema) : 0;
821
822     ZOOM_resultset_addref(r);
823
824     if (!c->async)
825     {
826         while (ZOOM_event(1, &c))
827             ;
828     }
829     return r;
830 }
831
832 ZOOM_API(void)
833     ZOOM_resultset_sort(ZOOM_resultset r,
834                          const char *sort_type, const char *sort_spec)
835 {
836     (void) ZOOM_resultset_sort1(r, sort_type, sort_spec);
837 }
838
839 ZOOM_API(int)
840     ZOOM_resultset_sort1(ZOOM_resultset r,
841                          const char *sort_type, const char *sort_spec)
842 {
843     ZOOM_connection c = r->connection;
844     ZOOM_task task;
845     ZOOM_query newq;
846
847     newq = ZOOM_query_create();
848     if (ZOOM_query_sortby(newq, sort_spec) < 0)
849         return -1;
850
851     yaz_log(c->log_api, "%p ZOOM_resultset_sort r=%p sort_type=%s sort_spec=%s",
852             r, r, sort_type, sort_spec);
853     if (!c)
854         return 0;
855
856     if (c->host_port && c->proto == PROTO_HTTP)
857     {
858         if (!c->cs)
859         {
860             yaz_log(c->log_details, "%p ZOOM_resultset_sort: no comstack", r);
861             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
862         }
863         else
864         {
865             yaz_log(c->log_details, "%p ZOOM_resultset_sort: prepare reconnect",
866                     r);
867             c->reconnect_ok = 1;
868         }
869     }
870
871     ZOOM_resultset_cache_reset(r);
872     task = ZOOM_connection_add_task(c, ZOOM_TASK_SORT);
873     task->u.sort.resultset = r;
874     task->u.sort.q = newq;
875
876     ZOOM_resultset_addref(r);
877
878     if (!c->async)
879     {
880         while (ZOOM_event(1, &c))
881             ;
882     }
883
884     return 0;
885 }
886
887 ZOOM_API(void)
888     ZOOM_resultset_destroy(ZOOM_resultset r)
889 {
890     resultset_destroy(r);
891 }
892
893 static void resultset_destroy(ZOOM_resultset r)
894 {
895     if (!r)
896         return;
897     yaz_mutex_enter(r->mutex);
898     (r->refcount)--;
899     yaz_log(log_details0, "%p ZOOM_resultset_destroy r=%p count=%d",
900             r, r, r->refcount);
901     if (r->refcount == 0)
902     {
903         yaz_mutex_leave(r->mutex);
904
905         yaz_log(log_details0, "%p ZOOM_connection resultset_destroy: Deleting resultset (%p) ", r->connection, r);
906         ZOOM_resultset_cache_reset(r);
907         ZOOM_resultset_release(r);
908         ZOOM_query_destroy(r->query);
909         ZOOM_options_destroy(r->options);
910         odr_destroy(r->odr);
911         xfree(r->setname);
912         yaz_mutex_destroy(&r->mutex);
913 #if SHPTR
914         YAZ_SHPTR_DEC(r->record_wrbuf, wrbuf_destroy);
915 #endif
916         wrbuf_destroy(r->mc_key);
917         resultset_use(-1);
918         xfree(r);
919     }
920     else
921         yaz_mutex_leave(r->mutex);
922 }
923
924 ZOOM_API(size_t)
925     ZOOM_resultset_size(ZOOM_resultset r)
926 {
927     return r->size;
928 }
929
930 int ZOOM_test_reconnect(ZOOM_connection c)
931 {
932     ZOOM_Event event;
933
934     if (!c->reconnect_ok)
935         return 0;
936     ZOOM_connection_close(c);
937     c->reconnect_ok = 0;
938     c->tasks->running = 0;
939     ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
940
941     event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
942     ZOOM_connection_put_event(c, event);
943
944     return 1;
945 }
946
947 static void ZOOM_resultset_retrieve(ZOOM_resultset r,
948                                     int force_sync, int start, int count)
949 {
950     ZOOM_task task;
951     ZOOM_connection c;
952     const char *cp;
953     const char *syntax, *elementSetName;
954
955     if (!r)
956         return;
957     yaz_log(log_details0, "%p ZOOM_resultset_retrieve force_sync=%d start=%d"
958             " count=%d", r, force_sync, start, count);
959     c = r->connection;
960     if (!c)
961         return;
962
963     if (c->host_port && c->proto == PROTO_HTTP)
964     {
965         if (!c->cs)
966         {
967             yaz_log(log_details0, "%p ZOOM_resultset_retrieve: no comstack", r);
968             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
969         }
970         else
971         {
972             yaz_log(log_details0, "%p ZOOM_resultset_retrieve: prepare "
973                     "reconnect", r);
974             c->reconnect_ok = 1;
975         }
976     }
977     task = ZOOM_connection_add_task(c, ZOOM_TASK_SEARCH);
978     task->u.search.resultset = r;
979     task->u.search.start = start;
980     task->u.search.count = count;
981
982     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax");
983     task->u.search.syntax = syntax ? xstrdup(syntax) : 0;
984     elementSetName = ZOOM_options_get(r->options, "elementSetName");
985     task->u.search.elementSetName = elementSetName
986         ? xstrdup(elementSetName) : 0;
987
988     cp = ZOOM_options_get(r->options, "schema");
989     task->u.search.schema = cp ? xstrdup(cp) : 0;
990
991     ZOOM_resultset_addref(r);
992
993     if (!r->connection->async || force_sync)
994         while (r->connection && ZOOM_event(1, &r->connection))
995             ;
996 }
997
998 ZOOM_API(void)
999     ZOOM_resultset_records(ZOOM_resultset r, ZOOM_record *recs,
1000                            size_t start, size_t count)
1001 {
1002     int force_present = 0;
1003
1004     if (!r)
1005         return ;
1006     yaz_log(log_api0, "%p ZOOM_resultset_records r=%p start=%ld count=%ld",
1007             r, r, (long) start, (long) count);
1008     if (count && recs)
1009         force_present = 1;
1010     ZOOM_resultset_retrieve(r, force_present, start, count);
1011     if (force_present)
1012     {
1013         size_t i;
1014         for (i = 0; i< count; i++)
1015             recs[i] = ZOOM_resultset_record_immediate(r, i+start);
1016     }
1017 }
1018
1019 ZOOM_API(size_t)
1020     ZOOM_resultset_facets_size(ZOOM_resultset r)
1021 {
1022     return r->num_facets;
1023 }
1024
1025 ZOOM_API(ZOOM_facet_field)
1026     ZOOM_resultset_get_facet_field(ZOOM_resultset r, const char *name)
1027 {
1028     int num = r->num_facets;
1029     ZOOM_facet_field *facets = r->facets;
1030     int i;
1031     for (i = 0; i < num; i++)
1032         if (!strcmp(facets[i]->facet_name, name))
1033             return facets[i];
1034     return 0;
1035 }
1036
1037 ZOOM_API(ZOOM_facet_field)
1038     ZOOM_resultset_get_facet_field_by_index(ZOOM_resultset r, int idx)
1039 {
1040     int num = r->num_facets;
1041     ZOOM_facet_field *facets = r->facets;
1042     if (idx >= 0 && idx < num)
1043         return facets[idx];
1044     return 0;
1045 }
1046
1047 ZOOM_API(ZOOM_facet_field *)
1048     ZOOM_resultset_facets(ZOOM_resultset r)
1049 {
1050     return r->facets;
1051 }
1052
1053 ZOOM_API(const char**)
1054     ZOOM_resultset_facets_names(ZOOM_resultset r)
1055 {
1056     return (const char **) r->facets_names;
1057 }
1058
1059 ZOOM_API(const char*)
1060     ZOOM_facet_field_name(ZOOM_facet_field field)
1061 {
1062     return field->facet_name;
1063 }
1064
1065 ZOOM_API(size_t)
1066     ZOOM_facet_field_term_count(ZOOM_facet_field field)
1067 {
1068     return field->num_terms;
1069 }
1070
1071 ZOOM_API(const char*)
1072     ZOOM_facet_field_get_term(ZOOM_facet_field field, size_t idx, int *freq)
1073 {
1074     *freq = field->facet_terms[idx].frequency;
1075     return field->facet_terms[idx].term;
1076 }
1077
1078
1079 static void get_cert(ZOOM_connection c)
1080 {
1081     char *cert_buf;
1082     int cert_len;
1083
1084     if (cs_get_peer_certificate_x509(c->cs, &cert_buf, &cert_len))
1085     {
1086         ZOOM_connection_option_setl(c, "sslPeerCert",
1087                                     cert_buf, cert_len);
1088         xfree(cert_buf);
1089     }
1090 }
1091
1092 static zoom_ret do_connect_host(ZOOM_connection c,
1093                                 const char *logical_url);
1094
1095 static zoom_ret do_connect(ZOOM_connection c)
1096 {
1097     return do_connect_host(c, c->host_port);
1098 }
1099
1100 static zoom_ret do_connect_host(ZOOM_connection c, const char *logical_url)
1101 {
1102     void *add;
1103
1104     if (c->cs)
1105         cs_close(c->cs);
1106     c->cs = cs_create_host_proxy(logical_url, 0, &add,
1107                                  c->tproxy ? c->tproxy : c->proxy);
1108
1109     if (c->cs && c->cs->protocol == PROTO_HTTP)
1110     {
1111 #if YAZ_HAVE_XML2
1112         c->proto = PROTO_HTTP;
1113 #else
1114         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
1115         ZOOM_connection_close(c);
1116         return zoom_complete;
1117 #endif
1118     }
1119     if (c->cs)
1120     {
1121         int ret = cs_connect(c->cs, add);
1122         if (ret == 0)
1123         {
1124             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1125             ZOOM_connection_put_event(c, event);
1126             get_cert(c);
1127             if (c->proto == PROTO_Z3950)
1128                 ZOOM_connection_Z3950_send_init(c);
1129             else
1130             {
1131                 /* no init request for SRW .. */
1132                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
1133                 ZOOM_connection_remove_task(c);
1134                 ZOOM_connection_set_mask(c, 0);
1135                 ZOOM_connection_exec_task(c);
1136             }
1137             c->state = STATE_ESTABLISHED;
1138             return zoom_pending;
1139         }
1140         else if (ret > 0)
1141         {
1142             int mask = ZOOM_SELECT_EXCEPT;
1143             if (c->cs->io_pending & CS_WANT_WRITE)
1144                 mask += ZOOM_SELECT_WRITE;
1145             if (c->cs->io_pending & CS_WANT_READ)
1146                 mask += ZOOM_SELECT_READ;
1147             ZOOM_connection_set_mask(c, mask);
1148             c->state = STATE_CONNECTING;
1149             return zoom_pending;
1150         }
1151     }
1152     c->state = STATE_IDLE;
1153     ZOOM_set_error(c, ZOOM_ERROR_CONNECT, logical_url);
1154     return zoom_complete;
1155 }
1156
1157 /* returns 1 if PDU was sent OK (still pending )
1158    0 if PDU was not sent OK (nothing to wait for)
1159 */
1160
1161 ZOOM_API(ZOOM_record)
1162     ZOOM_resultset_record_immediate(ZOOM_resultset s,size_t pos)
1163 {
1164     const char *syntax =
1165         ZOOM_options_get(s->options, "preferredRecordSyntax");
1166     const char *elementSetName =
1167         ZOOM_options_get(s->options, "elementSetName");
1168     const char *schema =
1169         ZOOM_options_get(s->options, "schema");
1170
1171     return ZOOM_record_cache_lookup(s, pos, syntax, elementSetName, schema);
1172 }
1173
1174 ZOOM_API(ZOOM_record)
1175     ZOOM_resultset_record(ZOOM_resultset r, size_t pos)
1176 {
1177     ZOOM_record rec = ZOOM_resultset_record_immediate(r, pos);
1178
1179     if (!rec)
1180     {
1181         /*
1182          * MIKE: I think force_sync should always be zero, but I don't
1183          * want to make this change until I get the go-ahead from
1184          * Adam, in case something depends on the old synchronous
1185          * behaviour.
1186          */
1187         int force_sync = 1;
1188         if (getenv("ZOOM_RECORD_NO_FORCE_SYNC")) force_sync = 0;
1189         ZOOM_resultset_retrieve(r, force_sync, pos, 1);
1190         rec = ZOOM_resultset_record_immediate(r, pos);
1191     }
1192     return rec;
1193 }
1194
1195 ZOOM_API(ZOOM_scanset)
1196     ZOOM_connection_scan(ZOOM_connection c, const char *start)
1197 {
1198     ZOOM_scanset s;
1199     ZOOM_query q = ZOOM_query_create();
1200
1201     ZOOM_query_prefix(q, start);
1202
1203     s = ZOOM_connection_scan1(c, q);
1204     ZOOM_query_destroy(q);
1205     return s;
1206
1207 }
1208
1209 ZOOM_API(ZOOM_scanset)
1210     ZOOM_connection_scan1(ZOOM_connection c, ZOOM_query q)
1211 {
1212     ZOOM_scanset scan = 0;
1213     Z_Query *z_query = ZOOM_query_get_Z_Query(q);
1214
1215     if (!z_query)
1216         return 0;
1217     scan = (ZOOM_scanset) xmalloc(sizeof(*scan));
1218     scan->connection = c;
1219     scan->odr = odr_createmem(ODR_DECODE);
1220     scan->options = ZOOM_options_create_with_parent(c->options);
1221     scan->refcount = 1;
1222     scan->scan_response = 0;
1223     scan->srw_scan_response = 0;
1224
1225     scan->query = q;
1226     ZOOM_query_addref(q);
1227     scan->databaseNames = ZOOM_connection_get_databases(c, c->options,
1228                                             &scan->num_databaseNames,
1229                                             scan->odr);
1230
1231     if (1)
1232     {
1233         ZOOM_task task = ZOOM_connection_add_task(c, ZOOM_TASK_SCAN);
1234         task->u.scan.scan = scan;
1235
1236         (scan->refcount)++;
1237         if (!c->async)
1238         {
1239             while (ZOOM_event(1, &c))
1240                 ;
1241         }
1242     }
1243     return scan;
1244 }
1245
1246 ZOOM_API(void)
1247     ZOOM_scanset_destroy(ZOOM_scanset scan)
1248 {
1249     if (!scan)
1250         return;
1251     (scan->refcount)--;
1252     if (scan->refcount == 0)
1253     {
1254         ZOOM_query_destroy(scan->query);
1255
1256         odr_destroy(scan->odr);
1257
1258         ZOOM_options_destroy(scan->options);
1259         xfree(scan);
1260     }
1261 }
1262
1263 static zoom_ret send_package(ZOOM_connection c)
1264 {
1265     ZOOM_Event event;
1266
1267     yaz_log(c->log_details, "%p send_package", c);
1268     if (!c->tasks)
1269         return zoom_complete;
1270     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
1271
1272     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1273     ZOOM_connection_put_event(c, event);
1274
1275     c->buf_out = c->tasks->u.package->buf_out;
1276     c->len_out = c->tasks->u.package->len_out;
1277
1278     return ZOOM_send_buf(c);
1279 }
1280
1281 ZOOM_API(size_t)
1282     ZOOM_scanset_size(ZOOM_scanset scan)
1283 {
1284     if (!scan)
1285         return 0;
1286
1287     if (scan->scan_response && scan->scan_response->entries)
1288         return scan->scan_response->entries->num_entries;
1289     else if (scan->srw_scan_response)
1290         return scan->srw_scan_response->num_terms;
1291     return 0;
1292 }
1293
1294 static void ZOOM_scanset_term_x(ZOOM_scanset scan, size_t pos,
1295                                 size_t *occ,
1296                                 const char **value_term, size_t *value_len,
1297                                 const char **disp_term, size_t *disp_len)
1298 {
1299     size_t noent = ZOOM_scanset_size(scan);
1300
1301     *value_term = 0;
1302     *value_len = 0;
1303
1304     *disp_term = 0;
1305     *disp_len = 0;
1306
1307     *occ = 0;
1308     if (pos >= noent)
1309         return;
1310     if (scan->scan_response)
1311     {
1312         Z_ScanResponse *res = scan->scan_response;
1313         if (res->entries->entries[pos]->which == Z_Entry_termInfo)
1314         {
1315             Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
1316
1317             *value_term = (const char *) t->term->u.general->buf;
1318             *value_len = t->term->u.general->len;
1319             if (t->displayTerm)
1320             {
1321                 *disp_term = t->displayTerm;
1322                 *disp_len = strlen(*disp_term);
1323             }
1324             else if (t->term->which == Z_Term_general)
1325             {
1326                 *disp_term = (const char *) t->term->u.general->buf;
1327                 *disp_len = t->term->u.general->len;
1328             }
1329             *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
1330         }
1331     }
1332     if (scan->srw_scan_response)
1333     {
1334         Z_SRW_scanResponse *res = scan->srw_scan_response;
1335         Z_SRW_scanTerm *t = res->terms + pos;
1336         if (t)
1337         {
1338             *value_term = t->value;
1339             *value_len = strlen(*value_term);
1340
1341             if (t->displayTerm)
1342                 *disp_term = t->displayTerm;
1343             else
1344                 *disp_term = t->value;
1345             *disp_len = strlen(*disp_term);
1346             *occ = t->numberOfRecords ? *t->numberOfRecords : 0;
1347         }
1348     }
1349 }
1350
1351 ZOOM_API(const char *)
1352     ZOOM_scanset_term(ZOOM_scanset scan, size_t pos,
1353                       size_t *occ, size_t *len)
1354 {
1355     const char *value_term = 0;
1356     size_t value_len = 0;
1357     const char *disp_term = 0;
1358     size_t disp_len = 0;
1359
1360     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
1361                         &disp_term, &disp_len);
1362
1363     *len = value_len;
1364     return value_term;
1365 }
1366
1367 ZOOM_API(const char *)
1368     ZOOM_scanset_display_term(ZOOM_scanset scan, size_t pos,
1369                               size_t *occ, size_t *len)
1370 {
1371     const char *value_term = 0;
1372     size_t value_len = 0;
1373     const char *disp_term = 0;
1374     size_t disp_len = 0;
1375
1376     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
1377                         &disp_term, &disp_len);
1378
1379     *len = disp_len;
1380     return disp_term;
1381 }
1382
1383 ZOOM_API(const char *)
1384     ZOOM_scanset_option_get(ZOOM_scanset scan, const char *key)
1385 {
1386     return ZOOM_options_get(scan->options, key);
1387 }
1388
1389 ZOOM_API(void)
1390     ZOOM_scanset_option_set(ZOOM_scanset scan, const char *key,
1391                             const char *val)
1392 {
1393     ZOOM_options_set(scan->options, key, val);
1394 }
1395
1396
1397 ZOOM_API(ZOOM_package)
1398     ZOOM_connection_package(ZOOM_connection c, ZOOM_options options)
1399 {
1400     ZOOM_package p = (ZOOM_package) xmalloc(sizeof(*p));
1401
1402     p->connection = c;
1403     p->odr_out = odr_createmem(ODR_ENCODE);
1404     p->options = ZOOM_options_create_with_parent2(options, c->options);
1405     p->refcount = 1;
1406     p->buf_out = 0;
1407     p->len_out = 0;
1408     return p;
1409 }
1410
1411 ZOOM_API(void)
1412     ZOOM_package_destroy(ZOOM_package p)
1413 {
1414     if (!p)
1415         return;
1416     (p->refcount)--;
1417     if (p->refcount == 0)
1418     {
1419         odr_destroy(p->odr_out);
1420         xfree(p->buf_out);
1421
1422         ZOOM_options_destroy(p->options);
1423         xfree(p);
1424     }
1425 }
1426
1427 ZOOM_API(const char *)
1428     ZOOM_package_option_get(ZOOM_package p, const char *key)
1429 {
1430     return ZOOM_options_get(p->options, key);
1431 }
1432
1433 ZOOM_API(const char *)
1434     ZOOM_package_option_getl(ZOOM_package p, const char *key, int *lenp)
1435 {
1436     return ZOOM_options_getl(p->options, key, lenp);
1437 }
1438
1439 ZOOM_API(void)
1440     ZOOM_package_option_set(ZOOM_package p, const char *key,
1441                             const char *val)
1442 {
1443     ZOOM_options_set(p->options, key, val);
1444 }
1445
1446 ZOOM_API(void)
1447     ZOOM_package_option_setl(ZOOM_package p, const char *key,
1448                              const char *val, int len)
1449 {
1450     ZOOM_options_setl(p->options, key, val, len);
1451 }
1452
1453 ZOOM_API(int)
1454     ZOOM_connection_exec_task(ZOOM_connection c)
1455 {
1456     ZOOM_task task = c->tasks;
1457     zoom_ret ret = zoom_complete;
1458
1459     if (!task)
1460         return 0;
1461     yaz_log(c->log_details, "%p ZOOM_connection_exec_task type=%d run=%d",
1462             c, task->which, task->running);
1463     if (c->error != ZOOM_ERROR_NONE)
1464     {
1465         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1466                 "removing tasks because of error = %d", c, c->error);
1467         ZOOM_connection_remove_tasks(c);
1468         return 0;
1469     }
1470     if (task->running)
1471     {
1472         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1473                 "task already running", c);
1474         return 0;
1475     }
1476     task->running = 1;
1477     ret = zoom_complete;
1478     if (c->cs || task->which == ZOOM_TASK_CONNECT)
1479     {
1480         switch (task->which)
1481         {
1482         case ZOOM_TASK_SEARCH:
1483             if (c->proto == PROTO_HTTP)
1484                 ret = ZOOM_connection_srw_send_search(c);
1485             else
1486                 ret = ZOOM_connection_Z3950_search(c);
1487             break;
1488         case ZOOM_TASK_CONNECT:
1489             ret = do_connect(c);
1490             break;
1491         case ZOOM_TASK_SCAN:
1492             if (c->proto == PROTO_HTTP)
1493                 ret = ZOOM_connection_srw_send_scan(c);
1494             else
1495                 ret = ZOOM_connection_Z3950_send_scan(c);
1496             break;
1497         case ZOOM_TASK_PACKAGE:
1498             ret = send_package(c);
1499             break;
1500         case ZOOM_TASK_SORT:
1501             c->tasks->u.sort.resultset->r_sort_spec =
1502                 ZOOM_query_get_sortspec(c->tasks->u.sort.q);
1503             ret = send_Z3950_sort(c, c->tasks->u.sort.resultset);
1504             break;
1505         }
1506     }
1507     else
1508     {
1509         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1510                 "remove tasks because no connection exist", c);
1511         ZOOM_connection_remove_tasks(c);
1512     }
1513     if (ret == zoom_complete)
1514     {
1515         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1516                 "task removed (complete)", c);
1517         ZOOM_connection_remove_task(c);
1518         return 0;
1519     }
1520     yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1521             "task pending", c);
1522     return 1;
1523 }
1524
1525 #if YAZ_HAVE_XML2
1526
1527 static zoom_ret send_HTTP_redirect(ZOOM_connection c, const char *uri)
1528 {
1529     Z_GDU *gdu = z_get_HTTP_Request_uri(c->odr_out, uri, 0, c->proxy ? 1 : 0);
1530
1531     gdu->u.HTTP_Request->method = odr_strdup(c->odr_out, "GET");
1532     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers, "Accept",
1533                       "text/xml");
1534     yaz_cookies_request(c->cookies, c->odr_out, gdu->u.HTTP_Request);
1535     if (c->user && c->password)
1536     {
1537         z_HTTP_header_add_basic_auth(c->odr_out, &gdu->u.HTTP_Request->headers,
1538                                      c->user, c->password);
1539     }
1540     return ZOOM_send_GDU(c, gdu);
1541 }
1542
1543 zoom_ret ZOOM_send_GDU(ZOOM_connection c, Z_GDU *gdu)
1544 {
1545     ZOOM_Event event;
1546
1547     int r = z_GDU(c->odr_out, &gdu, 0, 0);
1548     if (!r)
1549         return zoom_complete;
1550     if (c->odr_print)
1551         z_GDU(c->odr_print, &gdu, 0, 0);
1552     if (c->odr_save)
1553         z_GDU(c->odr_save, &gdu, 0, 0);
1554     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1555     odr_reset(c->odr_out);
1556
1557     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1558     ZOOM_connection_put_event(c, event);
1559
1560     return ZOOM_send_buf(c);
1561 }
1562
1563 #if YAZ_HAVE_XML2
1564 void ZOOM_set_HTTP_error(ZOOM_connection c, int error,
1565                          const char *addinfo, const char *addinfo2)
1566 {
1567     ZOOM_set_dset_error(c, error, "HTTP", addinfo, addinfo2);
1568 }
1569 #endif
1570
1571
1572 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
1573 {
1574     zoom_ret cret = zoom_complete;
1575     int ret = -1;
1576     char *addinfo = 0;
1577     const char *connection_head = z_HTTP_header_lookup(hres->headers,
1578                                                        "Connection");
1579     const char *location;
1580
1581     ZOOM_connection_set_mask(c, 0);
1582     yaz_log(c->log_details, "%p handle_http", c);
1583
1584     yaz_cookies_response(c->cookies, hres);
1585     if ((hres->code == 301 || hres->code == 302) && c->sru_mode == zoom_sru_get
1586         && (location = z_HTTP_header_lookup(hres->headers, "Location")))
1587     {
1588         c->no_redirects++;
1589         if (c->no_redirects > 10)
1590         {
1591             ZOOM_set_HTTP_error(c, hres->code, 0, 0);
1592             c->no_redirects = 0;
1593             ZOOM_connection_close(c);
1594         }
1595         else
1596         {
1597             /* since redirect may change host we just reconnect. A smarter
1598                implementation might check whether it's the same server */
1599
1600             int host_change = 0;
1601             location = yaz_check_location(c->odr_in, c->host_port,
1602                                           location, &host_change);
1603             if (do_connect_host(c, location) == zoom_complete)
1604                 return;  /* connect failed.. */
1605             send_HTTP_redirect(c, location);
1606             return;
1607         }
1608     }
1609     else
1610     {
1611         ret = ZOOM_handle_sru(c, hres, &cret, &addinfo);
1612         if (ret == 0)
1613         {
1614             if (c->no_redirects) /* end of redirect. change hosts again */
1615                 ZOOM_connection_close(c);
1616         }
1617         c->no_redirects = 0;
1618     }
1619     if (ret)
1620     {
1621         if (hres->code != 200)
1622             ZOOM_set_HTTP_error(c, hres->code, 0, 0);
1623         else
1624         {
1625             yaz_log(YLOG_LOG, "set error... addinfo=%s", addinfo ?
1626                     addinfo : "NULL");
1627             ZOOM_set_error(c, ZOOM_ERROR_DECODE, addinfo);
1628         }
1629         ZOOM_connection_close(c);
1630     }
1631     if (cret == zoom_complete)
1632     {
1633         yaz_log(c->log_details, "removing tasks in handle_http");
1634         ZOOM_connection_remove_task(c);
1635     }
1636     {
1637         int must_close = 0;
1638         if (!strcmp(hres->version, "1.0"))
1639         {
1640             /* HTTP 1.0: only if Keep-Alive we stay alive.. */
1641             if (!connection_head || strcmp(connection_head, "Keep-Alive"))
1642                 must_close = 1;
1643         }
1644         else
1645         {
1646             /* HTTP 1.1: only if no close we stay alive.. */
1647             if (connection_head && !strcmp(connection_head, "close"))
1648                 must_close = 1;
1649         }
1650         if (must_close)
1651         {
1652             ZOOM_connection_close(c);
1653             if (c->tasks)
1654             {
1655                 c->tasks->running = 0;
1656                 ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
1657                 c->reconnect_ok = 0;
1658             }
1659         }
1660         else
1661             c->reconnect_ok = 1; /* if the server closes anyway */
1662     }
1663 }
1664 #endif
1665
1666 static int do_read(ZOOM_connection c)
1667 {
1668     int r, more;
1669     ZOOM_Event event;
1670
1671     event = ZOOM_Event_create(ZOOM_EVENT_RECV_DATA);
1672     ZOOM_connection_put_event(c, event);
1673
1674     r = cs_get(c->cs, &c->buf_in, &c->len_in);
1675     more = cs_more(c->cs);
1676     yaz_log(c->log_details, "%p do_read len=%d more=%d", c, r, more);
1677     if (r == 1)
1678         return 0;
1679     if (r <= 0)
1680     {
1681         if (!ZOOM_test_reconnect(c))
1682         {
1683             ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
1684             ZOOM_connection_close(c);
1685         }
1686     }
1687     else
1688     {
1689         Z_GDU *gdu;
1690         ZOOM_Event event;
1691
1692         odr_reset(c->odr_in);
1693         odr_setbuf(c->odr_in, c->buf_in, r, 0);
1694         event = ZOOM_Event_create(ZOOM_EVENT_RECV_APDU);
1695         ZOOM_connection_put_event(c, event);
1696
1697         if (!z_GDU(c->odr_in, &gdu, 0, 0))
1698         {
1699             int x;
1700             int err = odr_geterrorx(c->odr_in, &x);
1701             char msg[100];
1702             const char *element = odr_getelement(c->odr_in);
1703             yaz_snprintf(msg, sizeof(msg),
1704                     "ODR code %d:%d element=%s offset=%d",
1705                     err, x, element ? element : "<unknown>",
1706                     odr_offset(c->odr_in));
1707             ZOOM_set_error(c, ZOOM_ERROR_DECODE, msg);
1708             if (c->log_api)
1709             {
1710                 FILE *ber_file = yaz_log_file();
1711                 if (ber_file)
1712                     odr_dumpBER(ber_file, c->buf_in, r);
1713             }
1714             ZOOM_connection_close(c);
1715         }
1716         else
1717         {
1718             if (c->odr_print)
1719                 z_GDU(c->odr_print, &gdu, 0, 0);
1720             if (c->odr_save)
1721                 z_GDU(c->odr_save, &gdu, 0, 0);
1722             if (gdu->which == Z_GDU_Z3950)
1723                 ZOOM_handle_Z3950_apdu(c, gdu->u.z3950);
1724             else if (gdu->which == Z_GDU_HTTP_Response)
1725             {
1726 #if YAZ_HAVE_XML2
1727                 handle_http(c, gdu->u.HTTP_Response);
1728 #else
1729                 ZOOM_set_error(c, ZOOM_ERROR_DECODE, 0);
1730                 ZOOM_connection_close(c);
1731 #endif
1732             }
1733         }
1734     }
1735     return 1;
1736 }
1737
1738 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out)
1739 {
1740     int r;
1741     ZOOM_Event event;
1742
1743     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
1744     ZOOM_connection_put_event(c, event);
1745
1746     yaz_log(c->log_details, "%p do_write_ex len=%d", c, len_out);
1747     if ((r = cs_put(c->cs, buf_out, len_out)) < 0)
1748     {
1749         yaz_log(c->log_details, "%p do_write_ex write failed", c);
1750         if (ZOOM_test_reconnect(c))
1751         {
1752             return zoom_pending;
1753         }
1754         if (c->state == STATE_CONNECTING)
1755             ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1756         else
1757             ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
1758         ZOOM_connection_close(c);
1759         return zoom_complete;
1760     }
1761     else if (r == 1)
1762     {
1763         int mask = ZOOM_SELECT_EXCEPT;
1764         if (c->cs->io_pending & CS_WANT_WRITE)
1765             mask += ZOOM_SELECT_WRITE;
1766         if (c->cs->io_pending & CS_WANT_READ)
1767             mask += ZOOM_SELECT_READ;
1768         ZOOM_connection_set_mask(c, mask);
1769         yaz_log(c->log_details, "%p do_write_ex write incomplete mask=%d",
1770                 c, c->mask);
1771     }
1772     else
1773     {
1774         ZOOM_connection_set_mask(c, ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT);
1775         yaz_log(c->log_details, "%p do_write_ex write complete mask=%d",
1776                 c, c->mask);
1777     }
1778     return zoom_pending;
1779 }
1780
1781 zoom_ret ZOOM_send_buf(ZOOM_connection c)
1782 {
1783     return do_write_ex(c, c->buf_out, c->len_out);
1784 }
1785
1786
1787 ZOOM_API(const char *)
1788     ZOOM_connection_option_get(ZOOM_connection c, const char *key)
1789 {
1790     if (!strcmp(key, "APDU"))
1791     {
1792         return c->saveAPDU_wrbuf ? wrbuf_cstr(c->saveAPDU_wrbuf) : "";
1793     }
1794     else
1795         return ZOOM_options_get(c->options, key);
1796 }
1797
1798 ZOOM_API(const char *)
1799     ZOOM_connection_option_getl(ZOOM_connection c, const char *key, int *lenp)
1800 {
1801     if (!strcmp(key, "APDU"))
1802     {
1803         if (c->saveAPDU_wrbuf)
1804         {
1805             *lenp = wrbuf_len(c->saveAPDU_wrbuf);
1806             return wrbuf_cstr(c->saveAPDU_wrbuf);
1807         }
1808         else
1809         {
1810             *lenp = 0;
1811             return "";
1812         }
1813     }
1814     else
1815         return ZOOM_options_getl(c->options, key, lenp);
1816 }
1817
1818 ZOOM_API(void)
1819     ZOOM_connection_option_set(ZOOM_connection c, const char *key,
1820                                const char *val)
1821 {
1822     if (!strcmp(key, "saveAPDU"))
1823     {
1824         if (val && strcmp(val, "0"))
1825         {
1826             if (!c->saveAPDU_wrbuf)
1827                 c->saveAPDU_wrbuf = wrbuf_alloc();
1828             else
1829                 wrbuf_rewind(c->saveAPDU_wrbuf);
1830         }
1831         else
1832         {
1833             wrbuf_destroy(c->saveAPDU_wrbuf);
1834             c->saveAPDU_wrbuf = 0;
1835         }
1836         ZOOM_connection_save_apdu_wrbuf(c, c->saveAPDU_wrbuf);
1837     }
1838     else
1839         ZOOM_options_set(c->options, key, val);
1840 }
1841
1842 ZOOM_API(void)
1843     ZOOM_connection_option_setl(ZOOM_connection c, const char *key,
1844                                 const char *val, int len)
1845 {
1846     ZOOM_options_setl(c->options, key, val, len);
1847 }
1848
1849 ZOOM_API(const char *)
1850     ZOOM_resultset_option_get(ZOOM_resultset r, const char *key)
1851 {
1852     return ZOOM_options_get(r->options, key);
1853 }
1854
1855 ZOOM_API(void)
1856     ZOOM_resultset_option_set(ZOOM_resultset r, const char *key,
1857                               const char *val)
1858 {
1859     ZOOM_options_set(r->options, key, val);
1860 }
1861
1862
1863 ZOOM_API(int)
1864     ZOOM_connection_errcode(ZOOM_connection c)
1865 {
1866     return ZOOM_connection_error(c, 0, 0);
1867 }
1868
1869 ZOOM_API(const char *)
1870     ZOOM_connection_errmsg(ZOOM_connection c)
1871 {
1872     const char *msg;
1873     ZOOM_connection_error(c, &msg, 0);
1874     return msg;
1875 }
1876
1877 ZOOM_API(const char *)
1878     ZOOM_connection_addinfo(ZOOM_connection c)
1879 {
1880     const char *addinfo;
1881     ZOOM_connection_error(c, 0, &addinfo);
1882     return addinfo;
1883 }
1884
1885 ZOOM_API(const char *)
1886     ZOOM_connection_diagset(ZOOM_connection c)
1887 {
1888     const char *diagset;
1889     ZOOM_connection_error_x(c, 0, 0, &diagset);
1890     return diagset;
1891 }
1892
1893 ZOOM_API(const char *)
1894     ZOOM_diag_str(int error)
1895 {
1896     switch (error)
1897     {
1898     case ZOOM_ERROR_NONE:
1899         return "No error";
1900     case ZOOM_ERROR_CONNECT:
1901         return "Connect failed";
1902     case ZOOM_ERROR_MEMORY:
1903         return "Out of memory";
1904     case ZOOM_ERROR_ENCODE:
1905         return "Encoding failed";
1906     case ZOOM_ERROR_DECODE:
1907         return "Decoding failed";
1908     case ZOOM_ERROR_CONNECTION_LOST:
1909         return "Connection lost";
1910     case ZOOM_ERROR_INIT:
1911         return "Init rejected";
1912     case ZOOM_ERROR_INTERNAL:
1913         return "Internal failure";
1914     case ZOOM_ERROR_TIMEOUT:
1915         return "Timeout";
1916     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
1917         return "Unsupported protocol";
1918     case ZOOM_ERROR_UNSUPPORTED_QUERY:
1919         return "Unsupported query type";
1920     case ZOOM_ERROR_INVALID_QUERY:
1921         return "Invalid query";
1922     case ZOOM_ERROR_CQL_PARSE:
1923         return "CQL parsing error";
1924     case ZOOM_ERROR_CQL_TRANSFORM:
1925         return "CQL transformation error";
1926     case ZOOM_ERROR_CCL_CONFIG:
1927         return "CCL configuration error";
1928     case ZOOM_ERROR_CCL_PARSE:
1929         return "CCL parsing error";
1930     case ZOOM_ERROR_ES_INVALID_ACTION:
1931         return "Extended Service. invalid action";
1932     case ZOOM_ERROR_ES_INVALID_VERSION:
1933         return "Extended Service. invalid version";
1934     case ZOOM_ERROR_ES_INVALID_SYNTAX:
1935         return "Extended Service. invalid syntax";
1936     case ZOOM_ERROR_MEMCACHED:
1937         return "Memcached";
1938     default:
1939         return diagbib1_str(error);
1940     }
1941 }
1942
1943 ZOOM_API(int)
1944     ZOOM_connection_error_x(ZOOM_connection c, const char **cp,
1945                             const char **addinfo, const char **diagset)
1946 {
1947     int error = c->error;
1948     if (cp)
1949     {
1950         if (!c->diagset || !strcmp(c->diagset, "ZOOM"))
1951             *cp = ZOOM_diag_str(error);
1952         else if (!strcmp(c->diagset, "HTTP"))
1953             *cp = z_HTTP_errmsg(c->error);
1954         else if (!strcmp(c->diagset, "Bib-1"))
1955             *cp = ZOOM_diag_str(error);
1956         else if (!strcmp(c->diagset, "info:srw/diagnostic/1"))
1957             *cp = yaz_diag_srw_str(c->error);
1958         else
1959             *cp = "Unknown error and diagnostic set";
1960     }
1961     if (addinfo)
1962         *addinfo = c->addinfo ? c->addinfo : "";
1963     if (diagset)
1964         *diagset = c->diagset ? c->diagset : "";
1965     return c->error;
1966 }
1967
1968 ZOOM_API(int)
1969     ZOOM_connection_error(ZOOM_connection c, const char **cp,
1970                           const char **addinfo)
1971 {
1972     return ZOOM_connection_error_x(c, cp, addinfo, 0);
1973 }
1974
1975 static void ZOOM_connection_do_io(ZOOM_connection c, int mask)
1976 {
1977     ZOOM_Event event = 0;
1978     int r = cs_look(c->cs);
1979     yaz_log(c->log_details, "%p ZOOM_connection_do_io mask=%d cs_look=%d",
1980             c, mask, r);
1981
1982     if (r == CS_NONE)
1983     {
1984         event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1985         ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1986         ZOOM_connection_close(c);
1987         ZOOM_connection_put_event(c, event);
1988     }
1989     else if (r == CS_CONNECT)
1990     {
1991         int ret = ret = cs_rcvconnect(c->cs);
1992         yaz_log(c->log_details, "%p ZOOM_connection_do_io "
1993                 "cs_rcvconnect returned %d", c, ret);
1994         if (ret == 1)
1995         {
1996             int mask = ZOOM_SELECT_EXCEPT;
1997             if (c->cs->io_pending & CS_WANT_WRITE)
1998                 mask += ZOOM_SELECT_WRITE;
1999             if (c->cs->io_pending & CS_WANT_READ)
2000                 mask += ZOOM_SELECT_READ;
2001             ZOOM_connection_set_mask(c, mask);
2002             event = ZOOM_Event_create(ZOOM_EVENT_NONE);
2003             ZOOM_connection_put_event(c, event);
2004         }
2005         else if (ret == 0)
2006         {
2007             event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
2008             ZOOM_connection_put_event(c, event);
2009             get_cert(c);
2010             if (c->proto == PROTO_Z3950)
2011                 ZOOM_connection_Z3950_send_init(c);
2012             else
2013             {
2014                 /* no init request for SRW .. */
2015                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
2016                 ZOOM_connection_remove_task(c);
2017                 ZOOM_connection_set_mask(c, 0);
2018                 ZOOM_connection_exec_task(c);
2019             }
2020             c->state = STATE_ESTABLISHED;
2021         }
2022         else
2023         {
2024             ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
2025             ZOOM_connection_close(c);
2026         }
2027     }
2028     else
2029     {
2030         if (mask & ZOOM_SELECT_EXCEPT)
2031         {
2032             if (!ZOOM_test_reconnect(c))
2033             {
2034                 ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
2035                 ZOOM_connection_close(c);
2036             }
2037             return;
2038         }
2039         if (mask & ZOOM_SELECT_READ)
2040             do_read(c);
2041         if (c->cs && (mask & ZOOM_SELECT_WRITE))
2042             ZOOM_send_buf(c);
2043     }
2044 }
2045
2046 ZOOM_API(int)
2047     ZOOM_connection_last_event(ZOOM_connection cs)
2048 {
2049     if (!cs)
2050         return ZOOM_EVENT_NONE;
2051     return cs->last_event;
2052 }
2053
2054
2055 ZOOM_API(int) ZOOM_connection_fire_event_timeout(ZOOM_connection c)
2056 {
2057     if (c->mask)
2058     {
2059         ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
2060         /* timeout and this connection was waiting */
2061         ZOOM_set_error(c, ZOOM_ERROR_TIMEOUT, 0);
2062         ZOOM_connection_close(c);
2063         ZOOM_connection_put_event(c, event);
2064     }
2065     return 0;
2066 }
2067
2068 ZOOM_API(int)
2069     ZOOM_connection_process(ZOOM_connection c)
2070 {
2071     ZOOM_Event event;
2072     if (!c)
2073         return 0;
2074
2075     event = ZOOM_connection_get_event(c);
2076     if (event)
2077     {
2078         ZOOM_Event_destroy(event);
2079         return 1;
2080     }
2081     ZOOM_connection_exec_task(c);
2082     event = ZOOM_connection_get_event(c);
2083     if (event)
2084     {
2085         ZOOM_Event_destroy(event);
2086         return 1;
2087     }
2088     return 0;
2089 }
2090
2091 ZOOM_API(int)
2092     ZOOM_event_nonblock(int no, ZOOM_connection *cs)
2093 {
2094     int i;
2095
2096     yaz_log(log_details0, "ZOOM_process_event(no=%d,cs=%p)", no, cs);
2097
2098     for (i = 0; i<no; i++)
2099     {
2100         ZOOM_connection c = cs[i];
2101
2102         if (c && ZOOM_connection_process(c))
2103             return i+1;
2104     }
2105     return 0;
2106 }
2107
2108 ZOOM_API(int) ZOOM_connection_fire_event_socket(ZOOM_connection c, int mask)
2109 {
2110     if (c->mask && mask)
2111         ZOOM_connection_do_io(c, mask);
2112     return 0;
2113 }
2114
2115 ZOOM_API(int) ZOOM_connection_get_socket(ZOOM_connection c)
2116 {
2117     if (c->cs)
2118         return cs_fileno(c->cs);
2119     return -1;
2120 }
2121
2122 ZOOM_API(int) ZOOM_connection_set_mask(ZOOM_connection c, int mask)
2123 {
2124     c->mask = mask;
2125     if (!c->cs)
2126         return -1;
2127     return 0;
2128 }
2129
2130 ZOOM_API(int) ZOOM_connection_get_mask(ZOOM_connection c)
2131 {
2132     if (c->cs)
2133         return c->mask;
2134     return 0;
2135 }
2136
2137 ZOOM_API(int) ZOOM_connection_get_timeout(ZOOM_connection c)
2138 {
2139     return ZOOM_options_get_int(c->options, "timeout", 30);
2140 }
2141
2142 ZOOM_API(void) ZOOM_connection_close(ZOOM_connection c)
2143 {
2144     if (c->cs)
2145         cs_close(c->cs);
2146     c->cs = 0;
2147     ZOOM_connection_set_mask(c, 0);
2148     c->state = STATE_IDLE;
2149 }
2150
2151 /*
2152  * Local variables:
2153  * c-basic-offset: 4
2154  * c-file-style: "Stroustrup"
2155  * indent-tabs-mode: nil
2156  * End:
2157  * vim: shiftwidth=4 tabstop=8 expandtab
2158  */
2159