Handle present out of range better in sample server.
[yazpp-moved-to-github.git] / src / yaz-my-client.cpp
1 /*
2  * Copyright (c) 1998-2004, Index Data.
3  * See the file LICENSE for details.
4  * 
5  * $Id: yaz-my-client.cpp,v 1.18 2005-01-17 09:55:58 adam Exp $
6  */
7
8 #include <stdlib.h>
9 #include <yaz/log.h>
10 #include <yaz/options.h>
11 #include <yaz/diagbib1.h>
12 #include <yaz/marcdisp.h>
13 #include <yaz++/ir-assoc.h>
14 #include <yaz++/pdu-assoc.h>
15 #include <yaz++/socket-manager.h>
16
17 extern "C" {
18 #if HAVE_READLINE_READLINE_H
19 #include <readline/readline.h>
20 #endif
21 #if HAVE_READLINE_HISTORY_H
22 #include <readline/history.h>
23 #endif
24 }
25
26 class YAZ_EXPORT MyClient : public Yaz_IR_Assoc {
27 private:
28     int m_interactive_flag;
29     char m_thisCommand[1024];
30     char m_lastCommand[1024];
31     int m_setOffset;
32     Yaz_SocketManager *m_socketManager;
33 public:
34     MyClient(IYaz_PDU_Observable *the_PDU_Observable,
35              Yaz_SocketManager *the_SocketManager);
36     IYaz_PDU_Observer *sessionNotify(
37         IYaz_PDU_Observable *the_PDU_Observable, int fd);
38     int args(Yaz_SocketManager *socketManager, int argc, char **argv);
39     int interactive(Yaz_SocketManager *socketManager);
40     int wait();
41     void recv_initResponse(Z_InitResponse *initResponse);
42     void recv_searchResponse(Z_SearchResponse *searchResponse);
43     void recv_presentResponse(Z_PresentResponse *presentResponse);
44     void recv_records (Z_Records *records);
45     void recv_diagrecs(Z_DiagRec **pp, int num);
46     void recv_namePlusRecord (Z_NamePlusRecord *zpr, int offset);
47     void recv_record(Z_DatabaseRecord *record, int offset,
48                      const char *databaseName);
49     void recv_textRecord(int type, const char *buf, size_t len);
50     void recv_genericRecord(Z_GenericRecord *r);
51     void display_genericRecord(Z_GenericRecord *r, int level);
52     void display_variant(Z_Variant *v, int level);
53     void connectNotify();
54     void failNotify();
55     void timeoutNotify();
56     char *get_cookie (Z_OtherInformation **oi);
57     int processCommand(const char *cmd);
58     const char *MyClient::getCommand();
59     int cmd_open(char *host);
60     int cmd_connect(char *host);
61     int cmd_quit(char *args);
62     int cmd_close(char *args);
63     int cmd_find(char *args);
64     int cmd_show(char *args);
65     int cmd_cookie(char *args);
66     int cmd_init(char *args);
67     int cmd_format(char *args);
68     int cmd_proxy(char *args);
69 };
70
71
72 void MyClient::connectNotify()
73 {
74     printf ("Connection accepted by target\n");
75     set_lastReceived(-1);
76 }
77
78 void MyClient::timeoutNotify()
79 {
80     printf ("Connection timeout\n");
81     close();
82 }
83
84 void MyClient::failNotify()
85 {
86     printf ("Connection closed by target\n");
87     set_lastReceived(-1);
88 }
89
90 IYaz_PDU_Observer *MyClient::sessionNotify(
91     IYaz_PDU_Observable *the_PDU_Observable, int fd)
92
93     return new MyClient(the_PDU_Observable, m_socketManager);
94 }
95
96 MyClient::MyClient(IYaz_PDU_Observable *the_PDU_Observable,
97                    Yaz_SocketManager *the_socketManager) :
98     Yaz_IR_Assoc (the_PDU_Observable)
99 {
100     m_setOffset = 1;
101     m_interactive_flag = 1;
102     m_thisCommand[0] = '\0';
103     m_lastCommand[0] = '\0';
104     m_socketManager = the_socketManager;
105 }
106
107 void usage(char *prog)
108 {
109     fprintf (stderr, "%s: [-v log] [-c cookie] [-p proxy] [zurl]\n", prog);
110     exit (1);
111 }
112
113 char *MyClient::get_cookie(Z_OtherInformation **otherInfo)
114 {
115     int oid[OID_SIZE];
116     Z_OtherInformationUnit *oi;
117     struct oident ent;
118     ent.proto = PROTO_Z3950;
119     ent.oclass = CLASS_USERINFO;
120     ent.value = (oid_value) VAL_COOKIE;
121
122     if (oid_ent_to_oid (&ent, oid) && 
123         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
124         oi->which == Z_OtherInfo_characterInfo)
125         return oi->information.characterInfo;
126     return 0;
127 }
128
129 void MyClient::recv_initResponse(Z_InitResponse *initResponse)
130 {
131     printf ("Got InitResponse. Status ");
132     if (*initResponse->result)
133     {
134         printf ("Ok\n");
135
136         const char *p = get_cookie (&initResponse->otherInfo);
137         if (p)
138         {
139             printf ("cookie = %s\n", p);
140             set_cookie(p);
141         }
142     }
143     else
144         printf ("Fail\n");
145 }
146
147 void MyClient::recv_diagrecs(Z_DiagRec **pp, int num)
148 {
149     int i;
150     oident *ent;
151     Z_DefaultDiagFormat *r;
152
153     printf("Diagnostic message(s) from database:\n");
154     for (i = 0; i<num; i++)
155     {
156         Z_DiagRec *p = pp[i];
157         if (p->which != Z_DiagRec_defaultFormat)
158         {
159             printf("Diagnostic record not in default format.\n");
160             return;
161         }
162         else
163             r = p->u.defaultFormat;
164         if (!(ent = oid_getentbyoid(r->diagnosticSetId)) ||
165             ent->oclass != CLASS_DIAGSET || ent->value != VAL_BIB1)
166             printf("Missing or unknown diagset\n");
167         printf("    [%d] %s", *r->condition, diagbib1_str(*r->condition));
168 #ifdef ASN_COMPILED
169         switch (r->which)
170         {
171         case Z_DefaultDiagFormat_v2Addinfo:
172             printf (" -- v2 addinfo '%s'\n", r->u.v2Addinfo);
173             break;
174         case Z_DefaultDiagFormat_v3Addinfo:
175             printf (" -- v3 addinfo '%s'\n", r->u.v3Addinfo);
176             break;
177         }
178 #else
179         if (r->addinfo && *r->addinfo)
180             printf(" -- '%s'\n", r->addinfo);
181         else
182             printf("\n");
183 #endif
184     }
185 }
186
187 void MyClient::recv_textRecord(int type, const char *buf, size_t len)
188 {
189     fwrite (buf, 1, len, stdout);
190     fputc ('\n', stdout);
191 }
192
193 void MyClient::display_variant(Z_Variant *v, int level)
194 {
195     int i;
196
197     for (i = 0; i < v->num_triples; i++)
198     {
199         printf("%*sclass=%d,type=%d", level * 4, "", *v->triples[i]->zclass,
200             *v->triples[i]->type);
201         if (v->triples[i]->which == Z_Triple_internationalString)
202             printf(",value=%s\n", v->triples[i]->value.internationalString);
203         else
204             printf("\n");
205     }
206 }
207
208 void MyClient::display_genericRecord(Z_GenericRecord *r, int level)
209 {
210     int i;
211
212     if (!r)
213         return;
214     for (i = 0; i < r->num_elements; i++)
215     {
216         Z_TaggedElement *t;
217
218         printf("%*s", level * 4, "");
219         t = r->elements[i];
220         printf("(");
221         if (t->tagType)
222             printf("%d,", *t->tagType);
223         else
224             printf("?,");
225         if (t->tagValue->which == Z_StringOrNumeric_numeric)
226             printf("%d) ", *t->tagValue->u.numeric);
227         else
228             printf("%s) ", t->tagValue->u.string);
229         if (t->content->which == Z_ElementData_subtree)
230         {
231             printf("\n");
232             display_genericRecord(t->content->u.subtree, level+1);
233         }
234         else if (t->content->which == Z_ElementData_string)
235             printf("%s\n", t->content->u.string);
236         else if (t->content->which == Z_ElementData_numeric)
237             printf("%d\n", *t->content->u.numeric);
238         else if (t->content->which == Z_ElementData_oid)
239         {
240             int *ip = t->content->u.oid;
241             oident *oent;
242
243             if ((oent = oid_getentbyoid(t->content->u.oid)))
244                 printf("OID: %s\n", oent->desc);
245             else
246             {
247                 printf("{");
248                 while (ip && *ip >= 0)
249                     printf(" %d", *(ip++));
250                 printf(" }\n");
251             }
252         }
253         else if (t->content->which == Z_ElementData_noDataRequested)
254             printf("[No data requested]\n");
255         else if (t->content->which == Z_ElementData_elementEmpty)
256             printf("[Element empty]\n");
257         else if (t->content->which == Z_ElementData_elementNotThere)
258             printf("[Element not there]\n");
259         else
260             printf("??????\n");
261         if (t->appliedVariant)
262             display_variant(t->appliedVariant, level+1);
263         if (t->metaData && t->metaData->supportedVariants)
264         {
265             int c;
266
267             printf("%*s---- variant list\n", (level+1)*4, "");
268             for (c = 0; c < t->metaData->num_supportedVariants; c++)
269             {
270                 printf("%*svariant #%d\n", (level+1)*4, "", c);
271                 display_variant(t->metaData->supportedVariants[c], level + 2);
272             }
273         }
274     }
275 }
276
277 void MyClient::recv_genericRecord(Z_GenericRecord *r)
278 {
279     display_genericRecord(r, 0);
280 }
281
282 void MyClient::recv_record(Z_DatabaseRecord *record, int offset,
283                            const char *databaseName)
284 {
285     Z_External *r = (Z_External*) record;
286     oident *ent = oid_getentbyoid(r->direct_reference);
287
288     /*
289      * Tell the user what we got.
290      */
291     if (r->direct_reference)
292     {
293         printf("Record type: ");
294         if (ent)
295             printf("%s\n", ent->desc);
296     }
297     /* Check if this is a known, ASN.1 type tucked away in an octet string */
298     Z_ext_typeent *etype = z_ext_getentbyref(ent->value);
299     if (ent && (r->which == Z_External_octet || r->which == Z_External_single)
300         && (etype = z_ext_getentbyref(ent->value)))
301
302     {
303         void *rr;
304         /*
305          * Call the given decoder to process the record.
306          */
307         odr_setbuf(odr_decode(), (char*)record->u.octet_aligned->buf,
308                    record->u.octet_aligned->len, 0);
309         if (!(*etype->fun)(odr_decode(), (char **)&rr, 0, 0))
310         {
311             odr_perror(odr_decode(), "Decoding constructed record.");
312             fprintf(stderr, "[Near %d]\n", odr_offset(odr_decode()));
313             fprintf(stderr, "Packet dump:\n---------\n");
314             odr_dumpBER(stderr, (char*)record->u.octet_aligned->buf,
315                         record->u.octet_aligned->len);
316             fprintf(stderr, "---------\n");
317         }
318         if (etype->what == Z_External_sutrs)
319         {
320             Z_SUTRS *sutrs = (Z_SUTRS *) rr;
321             recv_textRecord ((int) VAL_SUTRS, (const char *) sutrs->buf,
322                              (size_t) sutrs->len);
323         }
324         return;
325     }
326     if (r->which == Z_External_octet && record->u.octet_aligned->len)
327     {
328         switch (ent->value)
329         {
330         case VAL_ISO2709:
331         case VAL_UNIMARC:
332         case VAL_INTERMARC:
333         case VAL_USMARC:
334         case VAL_UKMARC:
335         case VAL_NORMARC:
336         case VAL_LIBRISMARC:
337         case VAL_DANMARC:
338         case VAL_FINMARC:
339         case VAL_MAB:
340         case VAL_CANMARC:
341         case VAL_SBN:
342         case VAL_PICAMARC:
343         case VAL_AUSMARC:
344         case VAL_IBERMARC:
345         case VAL_CATMARC:
346         case VAL_MALMARC:
347         case VAL_JPMARC:
348         case VAL_SWEMARC:
349         case VAL_SIGLEMARC:
350         case VAL_ISDSMARC:
351         case VAL_RUSMARC:
352             marc_display((char*) record->u.octet_aligned->buf,0);
353             break;
354         default:
355             recv_textRecord((int) ent->value,
356                             (const char *) record->u.octet_aligned->buf,
357                             (size_t) record->u.octet_aligned->len);
358         }
359     }
360     else if (ent && ent->value == VAL_SUTRS && r->which == Z_External_sutrs)
361         recv_textRecord((int) VAL_SUTRS, (const char *) r->u.sutrs->buf,
362                         (size_t) r->u.sutrs->len);
363     else if (ent && ent->value == VAL_GRS1 && r->which == Z_External_grs1)
364         recv_genericRecord(r->u.grs1);
365     else 
366     {
367         printf("Unknown record representation.\n");
368         if (!z_External(odr_print(), &r, 0, 0))
369         {
370             odr_perror(odr_print(), "Printing external");
371             odr_reset(odr_print());
372         }
373     }    
374 }
375
376 void MyClient::recv_namePlusRecord (Z_NamePlusRecord *zpr, int offset)
377 {
378     if (zpr->databaseName)
379         printf("[%s]", zpr->databaseName);
380     if (zpr->which == Z_NamePlusRecord_surrogateDiagnostic)
381         recv_diagrecs(&zpr->u.surrogateDiagnostic, 1);
382     else
383         recv_record(zpr->u.databaseRecord, offset, zpr->databaseName);
384 }
385
386 void MyClient::recv_records (Z_Records *records)
387 {
388 #ifdef ASN_COMPILED
389     Z_DiagRec dr, *dr_p = &dr;
390 #endif
391     if (!records)
392         return;
393     int i;
394     switch (records->which)
395     {
396     case Z_Records_DBOSD:
397         for (i = 0; i < records->u.databaseOrSurDiagnostics->num_records; i++)
398             recv_namePlusRecord(records->u.databaseOrSurDiagnostics->
399                                 records[i], i + m_setOffset);
400         m_setOffset += records->u.databaseOrSurDiagnostics->num_records;
401         break;
402     case Z_Records_NSD:
403 #ifdef ASN_COMPILED
404         dr.which = Z_DiagRec_defaultFormat;
405         dr.u.defaultFormat = records->u.nonSurrogateDiagnostic;
406         recv_diagrecs (&dr_p, 1);
407 #else
408         recv_diagrecs (&records->u.nonSurrogateDiagnostic, 1);
409 #endif
410         break;
411     case Z_Records_multipleNSD:
412         recv_diagrecs (records->u.multipleNonSurDiagnostics->diagRecs,
413                        records->u.multipleNonSurDiagnostics->num_diagRecs);
414         break;
415     }
416 }
417
418 void MyClient::recv_searchResponse(Z_SearchResponse *searchResponse)
419 {
420     printf ("Got SearchResponse. Status ");
421     if (!*searchResponse->searchStatus)
422     {
423         printf ("Fail\n");
424     }
425     else
426     {
427         printf ("Ok\n");
428         printf ("Hits: %d\n", *searchResponse->resultCount);
429     }
430     recv_records (searchResponse->records);
431 }
432
433 void MyClient::recv_presentResponse(Z_PresentResponse *presentResponse)
434 {
435     printf ("Got PresentResponse\n");
436     recv_records (presentResponse->records);
437 }
438
439 int MyClient::wait()
440 {
441     set_lastReceived(0);
442     while (m_socketManager->processEvent() > 0)
443     {
444         if (get_lastReceived())
445             return 1;
446     }
447     return 0;
448 }
449
450
451 #define C_PROMPT "Z>"
452
453 int MyClient::cmd_connect(char *host)
454 {
455     client (host);
456     timeout (10);
457     wait ();
458     timeout (-1);
459     return 1;
460 }
461
462 int MyClient::cmd_open(char *host)
463 {
464     client (host);
465     timeout (10);
466     wait ();
467     timeout (-1);
468     send_initRequest();
469     wait ();
470     return 1;
471 }
472
473 int MyClient::cmd_init(char *args)
474 {
475     if (send_initRequest() >= 0)
476         wait();
477     else
478         close();
479     return 1;
480 }
481
482 int MyClient::cmd_quit(char *args)
483 {
484     return 0;
485 }
486
487 int MyClient::cmd_close(char *args)
488 {
489     close();
490     return 1;
491 }
492
493 int MyClient::cmd_find(char *args)
494 {
495     Yaz_Z_Query query;
496
497     if (query.set_rpn(args) <= 0)
498     {
499         printf ("Bad RPN query\n");
500         return 1;
501     }
502     if (send_searchRequest(&query) >= 0)
503         wait();
504     else
505         printf ("Not connected\n");
506     return 1;
507 }
508
509 int MyClient::cmd_show(char *args)
510 {
511     int start = m_setOffset, number = 1;
512
513     sscanf (args, "%d %d", &start, &number);
514     m_setOffset = start;
515     if (send_presentRequest(start, number) >= 0)
516         wait();
517     else
518         printf ("Not connected\n");
519     return 1;
520 }
521
522 int MyClient::cmd_cookie(char *args)
523 {
524     set_cookie(*args ? args : 0);
525     return 1;
526 }
527
528 int MyClient::cmd_format(char *args)
529 {
530     set_preferredRecordSyntax(args);
531     return 1;
532 }
533
534 int MyClient::cmd_proxy(char *args)
535 {
536     set_proxy(args);
537     return 1;
538 }
539
540 int MyClient::processCommand(const char *commandLine)
541 {
542     char cmdStr[1024], cmdArgs[1024];
543     cmdArgs[0] = '\0';
544     cmdStr[0] = '\0';
545     static struct {
546         const char *cmd;
547         int (MyClient::*fun)(char *arg);
548         const char *ad;
549     } cmd[] = {
550         {"open", &MyClient::cmd_open, "<host>[':'<port>][/<database>]"},
551         {"connect", &MyClient::cmd_connect, "<host>[':'<port>][/<database>]"},
552         {"quit", &MyClient::cmd_quit, ""},
553         {"close", &MyClient::cmd_close, ""},
554         {"find", &MyClient::cmd_find, "<query>"},
555         {"show", &MyClient::cmd_show, "[<start> [<number>]]"},
556         {"cookie", &MyClient::cmd_cookie, "<cookie>"},
557         {"init", &MyClient::cmd_init, ""},
558         {"format", &MyClient::cmd_format, "<record-syntax>"},
559         {"proxy", &MyClient::cmd_proxy, "<host>:[':'<port>]"},
560         {0,0,0}
561     };
562     
563     if (sscanf(commandLine, "%s %[^;]", cmdStr, cmdArgs) < 1)
564         return 1;
565     int i;
566     for (i = 0; cmd[i].cmd; i++)
567         if (!strncmp(cmd[i].cmd, cmdStr, strlen(cmdStr)))
568             break;
569     
570     int res = 1;
571     if (cmd[i].cmd) // Invoke command handler
572         res = (this->*cmd[i].fun)(cmdArgs);
573     else            // Dump help screen
574     {
575         printf("Unknown command: %s.\n", cmdStr);
576         printf("Currently recognized commands:\n");
577         for (i = 0; cmd[i].cmd; i++)
578             printf("   %s %s\n", cmd[i].cmd, cmd[i].ad);
579     }
580     return res;
581 }
582
583 const char *MyClient::getCommand()
584 {
585 #if HAVE_READLINE_READLINE_H
586     // Read using GNU readline
587     char *line_in;
588     line_in=readline(C_PROMPT);
589     if (!line_in)
590         return 0;
591 #if HAVE_READLINE_HISTORY_H
592     if (*line_in)
593         add_history(line_in);
594 #endif
595     strncpy(m_thisCommand,line_in, 1023);
596     m_thisCommand[1023] = '\0';
597     free (line_in);
598 #else    
599     // Read using fgets(3)
600     printf (C_PROMPT);
601     fflush(stdout);
602     if (!fgets(m_thisCommand, 1023, stdin))
603         return 0;
604 #endif
605     // Remove trailing whitespace
606     char *cp = m_thisCommand + strlen(m_thisCommand);
607     while (cp != m_thisCommand && strchr("\t \n", cp[-1]))
608         cp--;
609     *cp = '\0';
610     cp = m_thisCommand;
611     // Remove leading spaces...
612     while (*cp && strchr ("\t \n", *cp))
613         cp++;
614     // Save command if non-empty
615     if (*cp != '\0')
616         strcpy (m_lastCommand, cp);
617     return m_lastCommand;
618 }
619
620 int MyClient::interactive(Yaz_SocketManager *socketManager)
621 {
622     const char *cmd;
623     if (!m_interactive_flag)
624         return 0;
625     while ((cmd = getCommand()))
626     {
627         if (!processCommand(cmd))
628             break;
629     }
630     return 0;
631 }
632
633 int MyClient::args(Yaz_SocketManager *socketManager, int argc, char **argv)
634 {
635     char *host = 0;
636     char *proxy = 0;
637     char *arg;
638     char *prog = argv[0];
639     int ret;
640
641     while ((ret = options("c:p:v:q", argv, argc, &arg)) != -2)
642     {
643         switch (ret)
644         {
645         case 0:
646             if (host)
647             {
648                 usage(prog);
649                 return 1;
650             }
651             host = arg;
652             break;
653         case 'p':
654             if (proxy)
655             {
656                 usage(prog);
657                 return 1;
658             }
659             set_proxy(arg);
660             break;
661         case 'c':
662             set_cookie(arg);
663             break;
664         case 'v':
665             yaz_log_init_level (yaz_log_mask_str(arg));
666             break;
667         case 'q':
668             m_interactive_flag = 0;
669             break;
670         default:
671             usage(prog);
672             return 1;
673         }
674     }
675     if (host)
676     {
677         client (host);
678         timeout (10);
679         wait ();
680         timeout (-1);
681         send_initRequest();
682         wait ();
683     }
684     return 0;
685 }
686
687 int main(int argc, char **argv)
688 {
689     Yaz_SocketManager mySocketManager;
690     Yaz_PDU_Assoc *some = new Yaz_PDU_Assoc(&mySocketManager);
691
692     MyClient z(some, &mySocketManager);
693
694     if (z.args(&mySocketManager, argc, argv))
695         exit (1);
696     if (z.interactive(&mySocketManager))
697         exit (1);
698     return 0;
699 }