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