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