Bump copyright year
[yazpp-moved-to-github.git] / src / yaz-my-client.cpp
1 /* This file is part of the yazpp toolkit.
2  * Copyright (C) 1998-2010 Index Data and Mike Taylor
3  * See the file LICENSE for details.
4  */
5
6 #include <stdlib.h>
7 #include <yaz/log.h>
8 #include <yaz/options.h>
9 #include <yaz/diagbib1.h>
10 #include <yaz/marcdisp.h>
11 #include <yazpp/ir-assoc.h>
12 #include <yazpp/pdu-assoc.h>
13 #include <yazpp/socket-manager.h>
14 #include <yaz/oid_db.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 using namespace yazpp_1;
26
27 class YAZ_EXPORT MyClient : public IR_Assoc {
28 private:
29     int m_interactive_flag;
30     char m_thisCommand[1024];
31     char m_lastCommand[1024];
32     int m_setOffset;
33     SocketManager *m_socketManager;
34 public:
35     MyClient(IPDU_Observable *the_PDU_Observable,
36              SocketManager *the_SocketManager);
37     IPDU_Observer *sessionNotify(
38         IPDU_Observable *the_PDU_Observable, int fd);
39     int args(SocketManager *socketManager, int argc, char **argv);
40     int interactive(SocketManager *socketManager);
41     int wait();
42     void recv_initResponse(Z_InitResponse *initResponse);
43     void recv_searchResponse(Z_SearchResponse *searchResponse);
44     void recv_presentResponse(Z_PresentResponse *presentResponse);
45     void recv_records (Z_Records *records);
46     void recv_diagrecs(Z_DiagRec **pp, int num);
47     void recv_namePlusRecord (Z_NamePlusRecord *zpr, int offset);
48     void recv_record(Z_DatabaseRecord *record, int offset,
49                      const char *databaseName);
50     void recv_textRecord(const char *buf, size_t len);
51     void recv_genericRecord(Z_GenericRecord *r);
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 *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 IPDU_Observer *MyClient::sessionNotify(IPDU_Observable *the_PDU_Observable,
90                                        int fd)
91
92     return new MyClient(the_PDU_Observable, m_socketManager);
93 }
94
95 MyClient::MyClient(IPDU_Observable *the_PDU_Observable,
96                    SocketManager *the_socketManager) :
97     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     Z_OtherInformationUnit *oi =
115         update_otherInformation(otherInfo, 0, yaz_oid_userinfo_cookie, 1, 1);
116
117     if (oi && oi->which == Z_OtherInfo_characterInfo)
118         return oi->information.characterInfo;
119     return 0;
120 }
121
122 void MyClient::recv_initResponse(Z_InitResponse *initResponse)
123 {
124     printf ("Got InitResponse. Status ");
125     if (*initResponse->result)
126     {
127         printf ("Ok\n");
128
129         const char *p = get_cookie (&initResponse->otherInfo);
130         if (p)
131         {
132             printf ("cookie = %s\n", p);
133             set_cookie(p);
134         }
135     }
136     else
137         printf ("Fail\n");
138 }
139
140 void MyClient::recv_diagrecs(Z_DiagRec **pp, int num)
141 {
142     int i;
143     Z_DefaultDiagFormat *r;
144
145     printf("Diagnostic message(s) from database:\n");
146     for (i = 0; i<num; i++)
147     {
148         Z_DiagRec *p = pp[i];
149         if (p->which != Z_DiagRec_defaultFormat)
150         {
151             printf("Diagnostic record not in default format.\n");
152             return;
153         }
154         else
155             r = p->u.defaultFormat;
156              printf("    [" ODR_INT_PRINTF "] %s", *r->condition, diagbib1_str(*r->condition));
157         switch (r->which)
158         {
159         case Z_DefaultDiagFormat_v2Addinfo:
160             printf (" -- v2 addinfo '%s'\n", r->u.v2Addinfo);
161             break;
162         case Z_DefaultDiagFormat_v3Addinfo:
163             printf (" -- v3 addinfo '%s'\n", r->u.v3Addinfo);
164             break;
165         }
166     }
167 }
168
169 void MyClient::recv_textRecord(const char *buf, size_t len)
170 {
171     fwrite (buf, 1, len, stdout);
172     fputc ('\n', stdout);
173 }
174
175 void MyClient::recv_genericRecord(Z_GenericRecord *r)
176 {
177     WRBUF w = wrbuf_alloc();
178     yaz_display_grs1(w, r, 0);
179     fwrite(wrbuf_buf(w), 1, wrbuf_len(w), stdout);
180     wrbuf_destroy(w);
181 }
182
183 void MyClient::recv_record(Z_DatabaseRecord *record, int offset,
184                            const char *databaseName)
185 {
186     Z_External *r = (Z_External*) record;
187     /*
188      * Tell the user what we got.
189      */
190     if (r->direct_reference)
191     {
192         char name_oid_str[OID_STR_MAX];
193         const char *name_oid = yaz_oid_to_string_buf(r->direct_reference, 0, 
194                                                      name_oid_str);
195         printf("Record type: %s\n", name_oid ? name_oid : "unknown");
196     }
197     if (r->which == Z_External_octet && record->u.octet_aligned->len)
198     {
199         if (yaz_oid_is_iso2709(r->direct_reference))
200         {
201             yaz_marc_t mt = yaz_marc_create();
202
203             const char *result_buf;
204             size_t result_size;
205             yaz_marc_decode_buf(mt, (const char *)
206                                 record->u.octet_aligned->buf,
207                                 record->u.octet_aligned->len,
208                                 &result_buf, &result_size);
209             fwrite(result_buf, 1, result_size, stdout);
210             yaz_marc_destroy(mt);
211         }
212         else
213         {
214             recv_textRecord((const char *) record->u.octet_aligned->buf,
215                             (size_t) record->u.octet_aligned->len);
216         }
217     }
218     else if (r->which == Z_External_sutrs)
219         recv_textRecord((const char *) r->u.sutrs->buf,
220                         (size_t) r->u.sutrs->len);
221     else if (r->which == Z_External_grs1)
222         recv_genericRecord(r->u.grs1);
223     else 
224     {
225         printf("Unknown record representation.\n");
226         if (!z_External(odr_print(), &r, 0, 0))
227         {
228             odr_perror(odr_print(), "Printing external");
229             odr_reset(odr_print());
230         }
231     }    
232 }
233
234 void MyClient::recv_namePlusRecord (Z_NamePlusRecord *zpr, int offset)
235 {
236     if (zpr->databaseName)
237         printf("[%s]", zpr->databaseName);
238     if (zpr->which == Z_NamePlusRecord_surrogateDiagnostic)
239         recv_diagrecs(&zpr->u.surrogateDiagnostic, 1);
240     else
241         recv_record(zpr->u.databaseRecord, offset, zpr->databaseName);
242 }
243
244 void MyClient::recv_records (Z_Records *records)
245 {
246     Z_DiagRec dr, *dr_p = &dr;
247     if (!records)
248         return;
249     int i;
250     switch (records->which)
251     {
252     case Z_Records_DBOSD:
253         for (i = 0; i < records->u.databaseOrSurDiagnostics->num_records; i++)
254             recv_namePlusRecord(records->u.databaseOrSurDiagnostics->
255                                 records[i], i + m_setOffset);
256         m_setOffset += records->u.databaseOrSurDiagnostics->num_records;
257         break;
258     case Z_Records_NSD:
259         dr.which = Z_DiagRec_defaultFormat;
260         dr.u.defaultFormat = records->u.nonSurrogateDiagnostic;
261         recv_diagrecs (&dr_p, 1);
262         break;
263     case Z_Records_multipleNSD:
264         recv_diagrecs (records->u.multipleNonSurDiagnostics->diagRecs,
265                        records->u.multipleNonSurDiagnostics->num_diagRecs);
266         break;
267     }
268 }
269
270 void MyClient::recv_searchResponse(Z_SearchResponse *searchResponse)
271 {
272     printf ("Got SearchResponse. Status ");
273     if (!*searchResponse->searchStatus)
274     {
275         printf ("Fail\n");
276     }
277     else
278     {
279         printf ("Ok\n");
280         printf ("Hits: " ODR_INT_PRINTF "\n", *searchResponse->resultCount);
281     }
282     recv_records (searchResponse->records);
283 }
284
285 void MyClient::recv_presentResponse(Z_PresentResponse *presentResponse)
286 {
287     printf ("Got PresentResponse\n");
288     recv_records (presentResponse->records);
289 }
290
291 int MyClient::wait()
292 {
293     set_lastReceived(0);
294     while (m_socketManager->processEvent() > 0)
295     {
296         if (get_lastReceived())
297             return 1;
298     }
299     return 0;
300 }
301
302
303 #define C_PROMPT "Z>"
304
305 int MyClient::cmd_connect(char *host)
306 {
307     client (host);
308     timeout (10);
309     wait ();
310     timeout (-1);
311     return 1;
312 }
313
314 int MyClient::cmd_open(char *host)
315 {
316     client (host);
317     timeout (10);
318     wait ();
319     timeout (-1);
320     send_initRequest();
321     wait ();
322     return 1;
323 }
324
325 int MyClient::cmd_init(char *args)
326 {
327     if (send_initRequest() >= 0)
328         wait();
329     else
330         close();
331     return 1;
332 }
333
334 int MyClient::cmd_quit(char *args)
335 {
336     return 0;
337 }
338
339 int MyClient::cmd_close(char *args)
340 {
341     close();
342     return 1;
343 }
344
345 int MyClient::cmd_find(char *args)
346 {
347     Yaz_Z_Query query;
348
349     if (query.set_rpn(args) <= 0)
350     {
351         printf ("Bad RPN query\n");
352         return 1;
353     }
354     if (send_searchRequest(&query) >= 0)
355         wait();
356     else
357         printf ("Not connected\n");
358     return 1;
359 }
360
361 int MyClient::cmd_show(char *args)
362 {
363     int start = m_setOffset, number = 1;
364
365     sscanf (args, "%d %d", &start, &number);
366     m_setOffset = start;
367     if (send_presentRequest(start, number) >= 0)
368         wait();
369     else
370         printf ("Not connected\n");
371     return 1;
372 }
373
374 int MyClient::cmd_cookie(char *args)
375 {
376     set_cookie(*args ? args : 0);
377     return 1;
378 }
379
380 int MyClient::cmd_format(char *args)
381 {
382     set_preferredRecordSyntax(args);
383     return 1;
384 }
385
386 int MyClient::cmd_proxy(char *args)
387 {
388     set_proxy(args);
389     return 1;
390 }
391
392 int MyClient::processCommand(const char *commandLine)
393 {
394     char cmdStr[1024], cmdArgs[1024];
395     cmdArgs[0] = '\0';
396     cmdStr[0] = '\0';
397     static struct {
398         const char *cmd;
399         int (MyClient::*fun)(char *arg);
400         const char *ad;
401     } cmd[] = {
402         {"open", &MyClient::cmd_open, "<host>[':'<port>][/<database>]"},
403         {"connect", &MyClient::cmd_connect, "<host>[':'<port>][/<database>]"},
404         {"quit", &MyClient::cmd_quit, ""},
405         {"close", &MyClient::cmd_close, ""},
406         {"find", &MyClient::cmd_find, "<query>"},
407         {"show", &MyClient::cmd_show, "[<start> [<number>]]"},
408         {"cookie", &MyClient::cmd_cookie, "<cookie>"},
409         {"init", &MyClient::cmd_init, ""},
410         {"format", &MyClient::cmd_format, "<record-syntax>"},
411         {"proxy", &MyClient::cmd_proxy, "<host>:[':'<port>]"},
412         {0,0,0}
413     };
414     
415     if (sscanf(commandLine, "%s %[^;]", cmdStr, cmdArgs) < 1)
416         return 1;
417     int i;
418     for (i = 0; cmd[i].cmd; i++)
419         if (!strncmp(cmd[i].cmd, cmdStr, strlen(cmdStr)))
420             break;
421     
422     int res = 1;
423     if (cmd[i].cmd) // Invoke command handler
424         res = (this->*cmd[i].fun)(cmdArgs);
425     else            // Dump help screen
426     {
427         printf("Unknown command: %s.\n", cmdStr);
428         printf("Currently recognized commands:\n");
429         for (i = 0; cmd[i].cmd; i++)
430             printf("   %s %s\n", cmd[i].cmd, cmd[i].ad);
431     }
432     return res;
433 }
434
435 const char *MyClient::getCommand()
436 {
437 #if HAVE_READLINE_READLINE_H
438     // Read using GNU readline
439     char *line_in;
440     line_in=readline(C_PROMPT);
441     if (!line_in)
442         return 0;
443 #if HAVE_READLINE_HISTORY_H
444     if (*line_in)
445         add_history(line_in);
446 #endif
447     strncpy(m_thisCommand,line_in, 1023);
448     m_thisCommand[1023] = '\0';
449     free (line_in);
450 #else    
451     // Read using fgets(3)
452     printf (C_PROMPT);
453     fflush(stdout);
454     if (!fgets(m_thisCommand, 1023, stdin))
455         return 0;
456 #endif
457     // Remove trailing whitespace
458     char *cp = m_thisCommand + strlen(m_thisCommand);
459     while (cp != m_thisCommand && strchr("\t \n", cp[-1]))
460         cp--;
461     *cp = '\0';
462     cp = m_thisCommand;
463     // Remove leading spaces...
464     while (*cp && strchr ("\t \n", *cp))
465         cp++;
466     // Save command if non-empty
467     if (*cp != '\0')
468         strcpy (m_lastCommand, cp);
469     return m_lastCommand;
470 }
471
472 int MyClient::interactive(SocketManager *socketManager)
473 {
474     const char *cmd;
475     if (!m_interactive_flag)
476         return 0;
477     while ((cmd = getCommand()))
478     {
479         if (!processCommand(cmd))
480             break;
481     }
482     return 0;
483 }
484
485 int MyClient::args(SocketManager *socketManager, int argc, char **argv)
486 {
487     char *host = 0;
488     char *proxy = 0;
489     char *arg;
490     char *prog = argv[0];
491     int ret;
492
493     while ((ret = options("c:p:v:q", argv, argc, &arg)) != -2)
494     {
495         switch (ret)
496         {
497         case 0:
498             if (host)
499             {
500                 usage(prog);
501                 return 1;
502             }
503             host = arg;
504             break;
505         case 'p':
506             if (proxy)
507             {
508                 usage(prog);
509                 return 1;
510             }
511             set_proxy(arg);
512             break;
513         case 'c':
514             set_cookie(arg);
515             break;
516         case 'v':
517             yaz_log_init_level (yaz_log_mask_str(arg));
518             break;
519         case 'q':
520             m_interactive_flag = 0;
521             break;
522         default:
523             usage(prog);
524             return 1;
525         }
526     }
527     if (host)
528     {
529         client (host);
530         timeout (10);
531         wait ();
532         timeout (-1);
533         send_initRequest();
534         wait ();
535     }
536     return 0;
537 }
538
539 int main(int argc, char **argv)
540 {
541     SocketManager mySocketManager;
542     PDU_Assoc *some = new PDU_Assoc(&mySocketManager);
543
544     MyClient z(some, &mySocketManager);
545
546     if (z.args(&mySocketManager, argc, argv))
547         exit (1);
548     if (z.interactive(&mySocketManager))
549         exit (1);
550     return 0;
551 }
552 /*
553  * Local variables:
554  * c-basic-offset: 4
555  * c-file-style: "Stroustrup"
556  * indent-tabs-mode: nil
557  * End:
558  * vim: shiftwidth=4 tabstop=8 expandtab
559  */
560