1. Fixed a bug whereby the display of request results was not showing up
[OpenSRF.git] / src / srfsh / srfsh.c
1 /**
2         @file srfsh.c
3         @brief Command-line tool for OSRF
4 */
5
6 #include <opensrf/transport_client.h>
7 #include <opensrf/osrf_message.h>
8 #include <opensrf/osrf_app_session.h>
9 #include <time.h>
10 #include <ctype.h>
11 #include <sys/timeb.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14
15 #include <opensrf/utils.h>
16 #include <opensrf/log.h>
17
18 #include <signal.h>
19
20 #include <stdio.h>
21 #include <readline/readline.h>
22 #include <readline/history.h>
23
24 /**
25         @brief A struct of convenience for parsing a command line
26 */
27 typedef struct {
28         const char* itr;            /**< iterator for input buffer */
29         growing_buffer* buf;        /**< output buffer */
30 } ArgParser;
31
32 static void get_string_literal( ArgParser* parser );
33 static void get_json_array( ArgParser* parser );
34 static void get_json_object( ArgParser* parser );
35 static void get_misc( ArgParser* parser );
36
37 #define SRFSH_PORT 5222
38 #define COMMAND_BUFSIZE 4096
39
40
41 /* shell prompt */
42 static const char* prompt = "srfsh# ";
43
44 static char* history_file = NULL;
45
46 //static int child_dead = 0;
47
48 static char* login_session = NULL;
49
50 /* true if we're pretty printing json results */
51 static int pretty_print = 1;
52 /* true if we're bypassing 'less' */
53 static int raw_print = 0;
54
55 /* our jabber connection */
56 static transport_client* client = NULL; 
57
58 /* the last result we received */
59 static osrfMessage* last_result = NULL;
60
61 /* functions */
62 static int process_request( const char* request );
63 static void parse_args( const char* request, osrfStringArray* cmd_array );
64
65 /* handles router requests */
66 static int handle_router( const osrfStringArray* cmd_array );
67
68 /* utility method for print time data */
69 static int handle_time( const osrfStringArray* cmd_array ); 
70
71 /* handles app level requests */
72 static int handle_request( const osrfStringArray* cmd_array, int relay );
73 static int handle_set( const osrfStringArray* cmd_array );
74 static int handle_print( const osrfStringArray* cmd_array );
75 static int send_request( const char* server,
76                                 const char* method, growing_buffer* buffer, int relay );
77 static int parse_error( const char* request );
78 static int router_query_servers( const char* server );
79 static int print_help( void );
80
81 //static int srfsh_client_connect();
82 //static char* tabs(int count);
83 //static void sig_child_handler( int s );
84 //static void sig_int_handler( int s );
85
86 static char* get_request( void );
87 static int load_history( void );
88 static int handle_math( const osrfStringArray* cmd_array );
89 static int do_math( int count, int style );
90 static int handle_introspect( const osrfStringArray* cmd_array );
91 static int handle_login( const osrfStringArray* cmd_array );
92 static int handle_open( const osrfStringArray* cmd_array );
93 static int handle_close( const osrfStringArray* cmd_array );
94 static void close_all_sessions( void );
95
96 static int recv_timeout = 120;
97 static int is_from_script = 0;
98
99 static osrfHash* server_hash = NULL;
100
101 int main( int argc, char* argv[] ) {
102
103         /* --------------------------------------------- */
104         /* see if they have a .srfsh.xml in their home directory */
105         char* home = getenv("HOME");
106         int l = strlen(home) + 36;
107         char fbuf[l];
108         snprintf(fbuf, sizeof(fbuf), "%s/.srfsh.xml", home);
109         
110         if(!access(fbuf, R_OK)) {
111                 if( ! osrf_system_bootstrap_client(fbuf, "srfsh") ) {
112                         fprintf(stderr,"Unable to bootstrap client for requests\n");
113                         osrfLogError( OSRF_LOG_MARK,  "Unable to bootstrap client for requests");
114                         return -1;
115                 }
116
117         } else {
118                 fprintf(stderr,"No Config file found at %s\n", fbuf ); 
119                 return -1;
120         }
121
122         if(argc > 1) {
123                 /* for now.. the first arg is used as a script file for processing */
124                 int f;
125                 if( (f = open(argv[1], O_RDONLY)) == -1 ) {
126                         osrfLogError( OSRF_LOG_MARK, "Unable to open file %s for reading, exiting...", argv[1]);
127                         return -1;
128                 }
129
130                 if(dup2(f, STDIN_FILENO) == -1) {
131                         osrfLogError( OSRF_LOG_MARK, "Unable to duplicate STDIN, exiting...");
132                         return -1;
133                 }
134
135                 close(f);
136                 is_from_script = 1;
137         }
138                 
139         /* --------------------------------------------- */
140         load_history();
141
142         client = osrfSystemGetTransportClient();
143         
144         // Disable special treatment for tabs by readline
145         // (by default they invoke command completion, which
146         // is not useful for srfsh)
147         rl_bind_key( '\t', rl_insert );
148         
149         /* main process loop */
150         int newline_needed = 1;  /* used as boolean */
151         char* request;
152         while( (request = get_request()) ) {
153
154                 // Find first non-whitespace character
155                 
156                 char * cmd = request;
157                 while( isspace( (unsigned char) *cmd ) )
158                         ++cmd;
159
160                 // Remove trailing whitespace.  We know at this point that
161                 // there is at least one non-whitespace character somewhere,
162                 // or we would have already skipped this line.  Hence we
163                 // needn't check to make sure that we don't back up past
164                 // the beginning.
165
166                 {
167                         // The curly braces limit the scope of the end variable
168                         
169                         char * end = cmd + strlen(cmd) - 1;
170                         while( isspace( (unsigned char) *end ) )
171                                 --end;
172                         end[1] = '\0';
173                 }
174
175                 if( !strcasecmp(cmd, "exit") || !strcasecmp(cmd, "quit"))
176                 {
177                         newline_needed = 0;
178                         break; 
179                 }
180
181                 process_request( cmd );
182                 if( request && *cmd ) {
183                         add_history(request);
184                 }
185
186                 free(request);
187
188                 fflush(stderr);
189                 fflush(stdout);
190         }
191
192         if( newline_needed ) {
193                 
194                 // We left the readline loop after seeing an EOF, not after
195                 // seeing "quit" or "exit".  So we issue a newline in order
196                 // to avoid leaving a dangling prompt.
197
198                 putchar( '\n' );
199         }
200
201         if(history_file != NULL )
202                 write_history(history_file);
203
204         // Free stuff
205         free(request);
206         free(login_session);
207         if( server_hash ) {
208                 if( osrfHashGetCount( server_hash ) > 0 )
209                         close_all_sessions();
210                 osrfHashFree( server_hash );
211         }
212
213         osrf_system_shutdown();
214         return 0;
215 }
216
217 // Get a logical line from one or more calls to readline(),
218 // skipping blank lines and comments.  Stitch continuation
219 // lines together as needed.  Caller is responsible for
220 // freeing the string returned.
221 // If EOF appears before a logical line is completed, 
222 // return NULL.
223 static char* get_request( void ) {
224         char* line;
225         char* p;
226
227         // Get the first physical line of the logical line
228         while( 1 ) {
229                 line = readline( prompt );
230                 if( ! line )
231                         return NULL;     // end of file
232
233                 // Skip leading white space
234                 for( p = line; isspace( *p ); ++p )
235                         ;
236
237                 if( '\\' == *p && '\0' == p[1] ) {
238                         // Just a trailing backslash; skip to next line
239                         free( line );
240                         continue;
241                 } else if( '\0' == p[0] || '#' == *p ) {
242                         free( line );
243                         continue;  // blank line or comment; skip it
244                 } else
245                         break;     // Not blank, not comment; take it
246         }
247
248         char* end = line + strlen( line ) - 1;
249         if( *end != '\\' )
250                 return line;    // No continuation line; we're done
251
252         // Remove the trailing backslash and collect
253         // the continuation line(s) into a growing_buffer
254         *end = '\0';
255
256         growing_buffer* logical_line = buffer_init( 256 );
257         buffer_add( logical_line, p );
258         free( line );
259
260         // Append any continuation lines
261         int finished = 0;      // boolean
262         while( !finished ) {
263                 line = readline( "> " );
264                 if( line ) {
265
266                         // Check for another continuation
267                         end = line + strlen( line ) - 1;
268                         if( '\\' == *end )
269                                 *end = '\0';
270                         else
271                                 finished = 1;
272
273                         buffer_add( logical_line, line );
274                         free( line );
275                 } else {
276                         fprintf( stderr, "Expected continuation line; found end of file\n" );
277                         buffer_free( logical_line );
278                         return NULL;
279                 }
280         }
281
282         return buffer_release( logical_line );
283 }
284
285 static int load_history( void ) {
286
287         char* home = getenv("HOME");
288         int l = strlen(home) + 24;
289         char fbuf[l];
290         snprintf(fbuf, sizeof(fbuf), "%s/.srfsh_history", home);
291         history_file = strdup(fbuf);
292
293         if(!access(history_file, W_OK | R_OK )) {
294                 history_length = 5000;
295                 read_history(history_file);
296         }
297         return 1;
298 }
299
300
301 static int parse_error( const char* request ) {
302         if( ! request )
303                 return 0;
304
305         fprintf( stderr, "???: %s\n", request );
306         return 0;
307
308 }
309
310
311 static int process_request( const char* request ) {
312
313         if( request == NULL )
314                 return 0;
315
316         int ret_val = 0;
317         osrfStringArray* cmd_array = osrfNewStringArray( 32 );
318
319         parse_args( request, cmd_array );
320         int wordcount = cmd_array->size;
321         if( 0 == wordcount ) {
322                 printf( "No words found in command\n" );
323                 osrfStringArrayFree( cmd_array );
324                 return 0;
325         }
326
327         /* pass off to the top level command */
328         const char* command = osrfStringArrayGetString( cmd_array, 0 );
329         if( !strcmp( command, "router" ) )
330                 ret_val = handle_router( cmd_array );
331
332         else if( !strcmp( command, "time" ) )
333                 ret_val = handle_time( cmd_array );
334
335         else if ( !strcmp( command, "request" ) )
336                 ret_val = handle_request( cmd_array, 0 );
337
338         else if ( !strcmp( command, "relay" ) )
339                 ret_val = handle_request( cmd_array, 1 );
340
341         else if ( !strcmp( command, "help" ) )
342                 ret_val = print_help();
343
344         else if ( !strcmp( command, "set" ) )
345                 ret_val = handle_set( cmd_array );
346
347         else if ( !strcmp( command, "print" ) )
348                 ret_val = handle_print( cmd_array );
349
350         else if ( !strcmp( command, "math_bench" ) )
351                 ret_val = handle_math( cmd_array );
352
353         else if ( !strcmp( command, "introspect" ) )
354                 ret_val = handle_introspect( cmd_array );
355
356         else if ( !strcmp( command, "login" ) )
357                 ret_val = handle_login( cmd_array );
358
359         else if ( !strcmp( command, "open" ) )
360                 ret_val = handle_open( cmd_array );
361
362         else if ( !strcmp( command, "close" ) )
363                 ret_val = handle_close( cmd_array );
364
365         else if ( request[0] == '!') {
366                 system( request + 1 );
367                 ret_val = 1;
368         }
369
370         osrfStringArrayFree( cmd_array );
371
372         if(!ret_val)
373                 return parse_error( request );
374         else
375                 return 1;
376 }
377
378
379 static int handle_introspect( const osrfStringArray* cmd_array ) {
380
381         const char* service = osrfStringArrayGetString( cmd_array, 1 );
382         if( ! service )
383                 return 0;
384
385         fprintf(stderr, "--> %s\n", service );
386
387         // Build a command in a suitably-sized
388         // buffer and then parse it
389
390         size_t len;
391         const char* method = osrfStringArrayGetString( cmd_array, 2 );
392         if( method ) {
393                 static const char text[] = "request %s opensrf.system.method %s";
394                 len = sizeof( text ) + strlen( service ) + strlen( method );
395                 char buf[len];
396                 snprintf( buf, sizeof(buf), text, service, method );
397                 return process_request( buf );
398
399         } else {
400                 static const char text[] = "request %s opensrf.system.method.all";
401                 len = sizeof( text ) + strlen( service );
402                 char buf[len];
403                 snprintf( buf, sizeof(buf), text, service );
404                 return process_request( buf );
405
406         }
407 }
408
409
410 static int handle_login( const osrfStringArray* cmd_array ) {
411
412         const char* username = osrfStringArrayGetString( cmd_array, 1 );
413         const char* password = osrfStringArrayGetString( cmd_array, 2 );
414
415         if( username && password ) {
416
417                 const char* type                = osrfStringArrayGetString( cmd_array, 3 );
418                 const char* orgloc              = osrfStringArrayGetString( cmd_array, 4 );
419                 const char* workstation = osrfStringArrayGetString( cmd_array, 5 );
420                 int orgloci = (orgloc) ? atoi(orgloc) : 0;
421                 if(!type) type = "opac";
422
423                 char login_text[] = "request open-ils.auth open-ils.auth.authenticate.init \"%s\"";
424                 size_t len = sizeof( login_text ) + strlen(username) + 1;
425
426                 char buf[len];
427                 snprintf( buf, sizeof(buf), login_text, username );
428                 process_request(buf);
429
430                 const char* hash;
431                 if(last_result && last_result->_result_content) {
432                         jsonObject* r = last_result->_result_content;
433                         hash = jsonObjectGetString(r);
434                 } else return 0;
435
436                 char* pass_buf = md5sum(password);
437
438                 size_t both_len = strlen( hash ) + strlen( pass_buf ) + 1;
439                 char both_buf[both_len];
440                 snprintf(both_buf, sizeof(both_buf), "%s%s", hash, pass_buf);
441
442                 char* mess_buf = md5sum(both_buf);
443
444                 growing_buffer* argbuf = buffer_init(64);
445                 buffer_fadd(argbuf, 
446                                 "request open-ils.auth open-ils.auth.authenticate.complete "
447                                 "{ \"username\" : \"%s\", \"password\" : \"%s\"", username, mess_buf );
448
449                 if(type) buffer_fadd( argbuf, ", \"type\" : \"%s\"", type );
450                 if(orgloci) buffer_fadd( argbuf, ", \"org\" : %d", orgloci );
451                 if(workstation) buffer_fadd( argbuf, ", \"workstation\" : \"%s\"", workstation);
452                 buffer_add(argbuf, "}");
453
454                 free(pass_buf);
455                 free(mess_buf);
456
457                 process_request( argbuf->buf );
458                 buffer_free(argbuf);
459
460                 if( login_session != NULL )
461                         free( login_session );
462
463                 const jsonObject* x = last_result->_result_content;
464                 double authtime = 0;
465                 if(x) {
466                         const char* authtoken = jsonObjectGetString(
467                                         jsonObjectGetKeyConst(jsonObjectGetKeyConst(x,"payload"), "authtoken"));
468                         authtime  = jsonObjectGetNumber(
469                                         jsonObjectGetKeyConst(jsonObjectGetKeyConst(x,"payload"), "authtime"));
470
471                         if(authtoken)
472                                 login_session = strdup(authtoken);
473                         else
474                                 login_session = NULL;
475                 }
476                 else login_session = NULL;
477
478                 printf("Login Session: %s.  Session timeout: %f\n",
479                            (login_session ? login_session : "(none)"), authtime );
480                 
481                 return 1;
482
483         }
484
485         return 0;
486 }
487
488 /**
489         @brief Open connections to one or more specified services.
490         @param cmd_array Pointer to a list of command line chunks.
491         @return 1 in all cases.
492
493         The first chunk of the command line is the "open" command.  Subsequent chunks, if any,
494         are server names.
495
496         Try to open all specified servers.  If no servers are specified, report what servers are
497         currently open.
498 */
499 static int handle_open( const osrfStringArray* cmd_array ) {
500         if( NULL == osrfStringArrayGetString( cmd_array, 1 ) ) {
501                 if( ! server_hash || osrfHashGetCount( server_hash ) == 0 ) {
502                         printf( "No services are currently open\n" );
503                         return 1;
504                 }
505
506                 printf( "Service(s) currently open:\n" );
507
508                 osrfHashIterator* itr = osrfNewHashIterator( server_hash );
509                 while( osrfHashIteratorNext( itr ) ) {
510                         printf( "\t%s\n", osrfHashIteratorKey( itr ) );
511                 }
512                 osrfHashIteratorFree( itr );
513                 return 1;
514         }
515
516         if( ! server_hash )
517                 server_hash = osrfNewHash( 6 );
518
519         int i;
520         for( i = 1; ; ++i ) {    // for each requested service
521                 const char* server = osrfStringArrayGetString( cmd_array, i );
522                 if( ! server )
523                         break;
524
525                 if( osrfHashGet( server_hash, server ) ) {
526                         printf( "Service %s is already open\n", server );
527                         continue;
528                 }
529
530                 // Try to open a session with the current specified server
531                 osrfAppSession* session = osrfAppSessionClientInit(server);
532
533                 if(!osrfAppSessionConnect(session)) {
534                         fprintf(stderr, "Unable to open service %s\n", server);
535                         osrfLogWarning( OSRF_LOG_MARK, "Unable to open remote service %s\n", server );
536                         osrfAppSessionFree( session );
537                 } else {
538                         osrfHashSet( server_hash, session, server );
539                         printf( "Service %s opened\n", server );
540                 }
541         }
542
543         return 1;
544 }
545
546 /**
547         @brief Close connections to one or more specified services.
548         @param cmd_array Pointer to a list of command line chunks.
549         @return 1 if any services were closed, or 0 if there were none to close.
550
551         The first chunk of the command line is the "close" command.  Subsequent chunks, if any,
552         are server names.
553 */
554 static int handle_close( const osrfStringArray* cmd_array ) {
555         if( cmd_array->size < 2 ) {
556                 fprintf( stderr, "No service specified for close\n" );
557                 return 0;
558         }
559
560         int i;
561         for( i = 1; ; ++i ) {
562                 const char* server = osrfStringArrayGetString( cmd_array, i );
563                 if( ! server )
564                         break;
565
566                 osrfAppSession* session = osrfHashRemove( server_hash, server );
567                 if( ! session ) {
568                         printf( "Service \"%s\" is not open\n", server );
569                         continue;
570                 }
571
572                 osrf_app_session_disconnect( session );
573                 osrfAppSessionFree( session );
574                 printf( "Service \"%s\" closed\n", server );
575         }
576
577         return 1;
578 }
579
580 /**
581         @brief Close all currently open connections to services.
582  */
583 static void close_all_sessions( void ) {
584
585         osrfAppSession* session;
586         osrfHashIterator* itr = osrfNewHashIterator( server_hash );
587
588         while(( session = osrfHashIteratorNext( itr ) )) {
589                 osrf_app_session_disconnect( session );
590                 osrfAppSessionFree( session );
591         }
592
593         osrfHashIteratorFree( itr );
594 }
595
596 static int handle_set( const osrfStringArray* cmd_array ) {
597
598         const char* variable = osrfStringArrayGetString( cmd_array, 1 );
599         if( variable ) {
600
601                 const char* val = osrfStringArrayGetString( cmd_array, 2 );
602                 if( val ) {
603
604                         if(!strcmp(variable,"pretty_print")) {
605                                 if(!strcmp(val,"true")) {
606                                         pretty_print = 1;
607                                         printf("pretty_print = true\n");
608                                         return 1;
609                                 } else if(!strcmp(val,"false")) {
610                                         pretty_print = 0;
611                                         printf("pretty_print = false\n");
612                                         return 1;
613                                 }
614                         }
615
616                         if(!strcmp(variable,"raw_print")) {
617                                 if(!strcmp(val,"true")) {
618                                         raw_print = 1;
619                                         printf("raw_print = true\n");
620                                         return 1;
621                                 } else if(!strcmp(val,"false")) {
622                                         raw_print = 0;
623                                         printf("raw_print = false\n");
624                                         return 1;
625                                 }
626                         }
627
628                 }
629         }
630
631         return 0;
632 }
633
634
635 static int handle_print( const osrfStringArray* cmd_array ) {
636
637         const char* variable = osrfStringArrayGetString( cmd_array, 1 );
638
639         if( variable ) {
640                 if(!strcmp(variable,"pretty_print")) {
641                         if(pretty_print) {
642                                 printf("pretty_print = true\n");
643                                 return 1;
644                         } else {
645                                 printf("pretty_print = false\n");
646                                 return 1;
647                         }
648                 }
649
650                 if(!strcmp(variable,"raw_print")) {
651                         if(raw_print) {
652                                 printf("raw_print = true\n");
653                                 return 1;
654                         } else {
655                                 printf("raw_print = false\n");
656                                 return 1;
657                         }
658                 }
659
660                 if(!strcmp(variable,"login")) {
661                         printf("login session = %s\n",
662                                    login_session ? login_session : "(none)" );
663                         return 1;
664                 }
665
666         }
667         return 0;
668 }
669
670 static int handle_router( const osrfStringArray* cmd_array ) {
671
672         if(!client)
673                 return 1;
674
675         int i;
676
677         const char* word_1 = osrfStringArrayGetString( cmd_array, 1 );
678         const char* word_2 = osrfStringArrayGetString( cmd_array, 2 );
679         if( word_1 ) {
680                 if( !strcmp( word_1,"query") ) {
681
682                         if( word_2 && !strcmp( word_2, "servers" ) ) {
683                                 for( i=3; i < COMMAND_BUFSIZE - 3; i++ ) {
684                                         const char* word = osrfStringArrayGetString( cmd_array, i );
685                                         if( word )
686                                                 router_query_servers( word );
687                                         else
688                                                 break;
689                                 }
690                                 return 1;
691                         }
692                         return 0;
693                 }
694                 return 0;
695         }
696         return 0;
697 }
698
699
700
701 static int handle_request( const osrfStringArray* cmd_array, int relay ) {
702
703         if(!client)
704                 return 1;
705
706         const char* server = osrfStringArrayGetString( cmd_array, 1 );
707         const char* method = osrfStringArrayGetString( cmd_array, 2 );
708
709         if( server ) {
710                 int i;
711                 growing_buffer* buffer = NULL;
712                 if( !relay ) {
713                         int first = 1;   // boolean
714                         buffer = buffer_init( 128 );
715                         buffer_add( buffer, "[" );
716                         for(i = 3; ; i++ ) {
717                                 const char* word = osrfStringArrayGetString( cmd_array, i );
718                                 if( !word )
719                                         break;
720
721                                 if( first )
722                                         first = 0;
723                                 else
724                                         buffer_add( buffer, ", " );
725
726                                 buffer_add( buffer, word );
727
728                                 /* remove trailing semicolon if user accidentally entered it */
729                                 if( word[ strlen( word ) - 1 ] == ';' )
730                                         buffer_chomp( buffer );
731                         }
732                         buffer_add_char( buffer, ']' );
733                 }
734
735                 int rc = send_request( server, method, buffer, relay );
736                 buffer_free( buffer );
737                 return rc;
738         } 
739
740         return 0;
741 }
742
743 int send_request( const char* server,
744                 const char* method, growing_buffer* buffer, int relay ) {
745         if( server == NULL || method == NULL )
746                 return 0;
747
748         jsonObject* params = NULL;
749         if( !relay ) {
750                 if( buffer != NULL && buffer->n_used > 0 ) {
751                         // Temporarily redirect parsing error messages to stderr
752                         osrfLogToStderr();
753                         params = jsonParse( OSRF_BUFFER_C_STR( buffer ) );
754                         osrfRestoreLogType();
755                 }
756         } else {
757                 if(!last_result || ! last_result->_result_content) { 
758                         printf("We're not going to call 'relay' with no result params\n");
759                         return 1;
760                 }
761                 else {
762                         params = jsonNewObject(NULL);
763                         jsonObjectPush(params, last_result->_result_content );
764                 }
765         }
766
767         if(buffer->n_used > 0 && params == NULL) {
768                 fprintf(stderr, "JSON error detected, not executing\n");
769                 jsonObjectFree(params);
770                 return 1;
771         }
772
773         int session_is_temporary;    // boolean
774         osrfAppSession* session = osrfHashGet( server_hash, server );
775         if( session ) {
776                 session_is_temporary = 0;     // use an existing session
777         } else {
778                 session = osrfAppSessionClientInit(server);   // open a session
779                 session_is_temporary = 1;                     // just for this request
780         }
781
782         if(!osrfAppSessionConnect(session)) {
783                 fprintf(stderr, "Unable to communicate with service %s\n", server);
784                 osrfLogWarning( OSRF_LOG_MARK,  "Unable to connect to remote service %s\n", server );
785                 osrfAppSessionFree( session );
786                 jsonObjectFree(params);
787                 return 1;
788         }
789
790         double start = get_timestamp_millis();
791
792         int req_id = osrfAppSessionMakeRequest( session, params, method, 1, NULL );
793         jsonObjectFree(params);
794
795         osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
796
797         if(!omsg) 
798                 printf("\nReceived no data from server\n");
799
800
801         signal(SIGPIPE, SIG_IGN);
802
803         FILE* less; 
804         if(!is_from_script) less = popen( "less -EX", "w");
805         else less = stdout;
806
807         if( less == NULL ) { less = stdout; }
808
809         growing_buffer* resp_buffer = buffer_init(4096);
810
811         while(omsg) {
812
813                 if(raw_print) {
814
815                         if(omsg->_result_content) {
816         
817                                 osrfMessageFree(last_result);
818                                 last_result = omsg;
819         
820                                 char* content;
821         
822                                 if( pretty_print ) {
823                                         char* j = jsonObjectToJSON(omsg->_result_content);
824                                         if( j ) {
825                                                 content = jsonFormatString(j);
826                                                 free(j);
827                                         } else
828                                                 content = strdup( "(null)" );
829                                 } else {
830                                         content = jsonObjectToJSON(omsg->_result_content);
831                                         if( ! content )
832                                                 content = strdup( "(null)" );
833                                 }
834
835                                 printf( "\nReceived Data: %s\n", content );
836                                 free(content);
837         
838                         } else {
839
840                                 char code[16];
841                                 snprintf( code, sizeof(code), "%d", omsg->status_code );
842                                 buffer_add( resp_buffer, code );
843
844                                 printf( "\nReceived Exception:\nName: %s\nStatus: %s\nStatus: %s\n", 
845                                                 omsg->status_name, omsg->status_text, code );
846
847                                 fflush(stdout);
848                                 osrfMessageFree(omsg);
849                         }
850
851                 } else {
852
853                         if(omsg->_result_content) {
854         
855                                 osrfMessageFree(last_result);
856                                 last_result = omsg;
857         
858                                 char* content;
859         
860                                 if( pretty_print && omsg->_result_content ) {
861                                         char* j = jsonObjectToJSON(omsg->_result_content);
862                                         if( j ) {
863                                                 content = jsonFormatString(j);
864                                                 free(j);
865                                         } else
866                                                 content = strdup( "(null)" );
867                                 } else {
868                                         content = jsonObjectToJSON(omsg->_result_content);
869                                         if( ! content )
870                                                 content = strdup( "(null)" );
871                                 }
872
873                                 buffer_add( resp_buffer, "\nReceived Data: " );
874                                 buffer_add( resp_buffer, content );
875                                 buffer_add_char( resp_buffer, '\n' );
876                                 free(content);
877
878                         } else {
879
880                                 buffer_add( resp_buffer, "\nReceived Exception:\nName: " );
881                                 buffer_add( resp_buffer, omsg->status_name );
882                                 buffer_add( resp_buffer, "\nStatus: " );
883                                 buffer_add( resp_buffer, omsg->status_text );
884                                 buffer_add( resp_buffer, "\nStatus: " );
885                                 char code[16];
886                                 snprintf( code, sizeof(code), "%d", omsg->status_code );
887                                 buffer_add( resp_buffer, code );
888                                 osrfMessageFree(omsg);
889                         }
890                 }
891
892                 omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
893
894         }
895
896         double end = get_timestamp_millis();
897
898         fputs( resp_buffer->buf, less );
899         buffer_free( resp_buffer );
900         fputs("\n------------------------------------\n", less);
901         if( osrf_app_session_request_complete( session, req_id ))
902                 fputs("Request Completed Successfully\n", less);
903
904
905         fprintf(less, "Request Time in seconds: %.6f\n", end - start );
906         fputs("------------------------------------\n", less);
907
908         pclose(less);
909
910         osrf_app_session_request_finish( session, req_id );
911
912         if( session_is_temporary ) {
913                 osrf_app_session_disconnect( session );
914                 osrfAppSessionFree( session );
915         }
916
917         return 1;
918
919
920 }
921
922 static int handle_time( const osrfStringArray* cmd_array ) {
923
924         const char* word_1 = osrfStringArrayGetString( cmd_array, 1 );
925         if( !word_1 ) {
926                 printf("%f\n", get_timestamp_millis());
927         } else {
928                 time_t epoch = (time_t) atoi( word_1 );
929                 printf("%s", ctime(&epoch));
930         }
931         return 1;
932 }
933
934
935 static int router_query_servers( const char* router_server ) {
936
937         if( ! router_server || strlen(router_server) == 0 ) 
938                 return 0;
939
940         const static char router_text[] = "router@%s/router";
941         size_t len = sizeof( router_text ) + strlen( router_server ) + 1;
942         char rbuf[len];
943         snprintf(rbuf, sizeof(rbuf), router_text, router_server );
944                 
945         transport_message* send = 
946                 message_init( "servers", NULL, NULL, rbuf, NULL );
947         message_set_router_info( send, NULL, NULL, NULL, "query", 0 );
948
949         client_send_message( client, send );
950         message_free( send );
951
952         transport_message* recv = client_recv( client, -1 );
953         if( recv == NULL ) {
954                 fprintf(stderr, "NULL message received from router\n");
955                 return 1;
956         }
957         
958         printf( 
959                         "---------------------------------------------------------------------------------\n"
960                         "Received from 'server' query on %s\n"
961                         "---------------------------------------------------------------------------------\n"
962                         "original reg time | latest reg time | last used time | class | server\n"
963                         "---------------------------------------------------------------------------------\n"
964                         "%s"
965                         "---------------------------------------------------------------------------------\n"
966                         , router_server, recv->body );
967
968         message_free( recv );
969         
970         return 1;
971 }
972
973 static int print_help( void ) {
974
975         fputs(
976                         "---------------------------------------------------------------------------------\n"
977                         "General commands:\n"
978                         "---------------------------------------------------------------------------------\n"
979                         "help                   - Display this message\n"
980                         "!<command> [args]      - Forks and runs the given command in the shell\n"
981                 /*
982                         "time                   - Prints the current time\n"
983                         "time <timestamp>       - Formats seconds since epoch into readable format\n"
984                 */
985                         "set <variable> <value> - Set a srfsh variable (e.g. set pretty_print true )\n"
986                         "print <variable>       - Displays the value of a srfsh variable\n"
987                         "\n"
988                         "---------------------------------------------------------------------------------\n"
989                         "Variables:\n"
990                         "---------------------------------------------------------------------------------\n"
991                         "pretty_print            - Display nicely formatted JSON results\n"
992                         "       - Accepted values: true, false\n"
993                         "       - Default value: true\n"
994                         "\n"
995                         "raw_print               - Pass JSON results through 'less' paging command\n"
996                         "       - Accepted values: true, false\n"
997                         "       - Default value: false\n"
998                         "\n"
999                         "---------------------------------------------------------------------------------\n"
1000                         "Commands for OpenSRF services and methods:\n"
1001                         "---------------------------------------------------------------------------------\n"
1002                         "introspect <service> [\"method-name\"]\n"
1003                         "       - Prints service API, limited to the methods that match the optional\n"
1004                         "                right-truncated method-name parameter\n"
1005                         "\n"
1006                         "request <service> <method> [ <JSON formatted string of params> ]\n"
1007                         "       - Anything passed in will be wrapped in a JSON array,\n"
1008                         "               so add commas if there is more than one param\n"
1009                         "\n"
1010                         "router query servers <server1 [, server2, ...]>\n"
1011                         "       - Returns stats on connected services\n"
1012                         "\n"
1013                         "relay <service> <method>\n"
1014                         "       - Performs the requested query using the last received result as the param\n"
1015                         "\n"
1016                         "math_bench <num_batches> [0|1|2]\n"
1017                         "       - 0 means don't reconnect, 1 means reconnect after each batch of 4, and\n"
1018                         "                2 means reconnect after every request\n"
1019                         "\n"
1020                         "---------------------------------------------------------------------------------\n"
1021                         " Commands for Evergreen\n"
1022                         "---------------------------------------------------------------------------------\n"
1023                         "login <username> <password> [type] [org_unit] [workstation]\n"
1024                         "       - Logs into the 'server' and displays the session id\n"
1025                         "       - To view the session id later, enter: print login\n"
1026                         "---------------------------------------------------------------------------------\n"
1027                         "\n"
1028                         "Note: long output is piped through 'less' unless the 'raw_print' variable\n"
1029                         "is true.  To search in 'less', type: /<search>\n"
1030                         "---------------------------------------------------------------------------------\n"
1031                         "\n",
1032                         stdout );
1033
1034         return 1;
1035 }
1036
1037
1038 /*
1039 static char* tabs(int count) {
1040         growing_buffer* buf = buffer_init(24);
1041         int i;
1042         for(i=0;i!=count;i++)
1043                 buffer_add(buf, "  ");
1044
1045         char* final = buffer_data( buf );
1046         buffer_free( buf );
1047         return final;
1048 }
1049 */
1050
1051
1052 static int handle_math( const osrfStringArray* cmd_array ) {
1053         const char* word = osrfStringArrayGetString( cmd_array, 1 );
1054         if( word )
1055                 return do_math( atoi( word ), 0 );
1056         return 0;
1057 }
1058
1059
1060 static int do_math( int count, int style ) {
1061
1062         osrfAppSession* session = osrfAppSessionClientInit( "opensrf.math" );
1063         osrfAppSessionConnect(session);
1064
1065         jsonObject* params = jsonNewObjectType( JSON_ARRAY );
1066         jsonObjectPush(params,jsonNewObject("1"));
1067         jsonObjectPush(params,jsonNewObject("2"));
1068
1069         char* methods[] = { "add", "sub", "mult", "div" };
1070         char* answers[] = { "3", "-1", "2", "0.5" };
1071
1072         float times[ count * 4 ];
1073         memset(times, 0, sizeof(times));
1074
1075         int k;
1076         for(k=0;k!=100;k++) {
1077                 if(!(k%10)) 
1078                         fprintf(stderr,"|");
1079                 else
1080                         fprintf(stderr,".");
1081         }
1082
1083         fprintf(stderr,"\n\n");
1084
1085         int running = 0;
1086         int i;
1087         for(i=0; i!= count; i++) {
1088
1089                 int j;
1090                 for(j=0; j != 4; j++) {
1091
1092                         ++running;
1093
1094                         double start = get_timestamp_millis();
1095                         int req_id = osrfAppSessionMakeRequest( session, params, methods[j], 1, NULL );
1096                         osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, 5 );
1097                         double end = get_timestamp_millis();
1098
1099                         times[(4*i) + j] = end - start;
1100
1101                         if(omsg) {
1102         
1103                                 if(omsg->_result_content) {
1104                                         char* jsn = jsonObjectToJSON(omsg->_result_content);
1105                                         if(!strcmp(jsn, answers[j]))
1106                                                 fprintf(stderr, "+");
1107                                         else
1108                                                 fprintf(stderr, "\n![%s] - should be %s\n", jsn, answers[j] );
1109                                         free(jsn);
1110                                 }
1111
1112
1113                                 osrfMessageFree(omsg);
1114                 
1115                         } else { fprintf( stderr, "\nempty message for tt: %d\n", req_id ); }
1116
1117                         osrf_app_session_request_finish( session, req_id );
1118
1119                         if(style == 2)
1120                                 osrf_app_session_disconnect( session );
1121
1122                         if(!(running%100))
1123                                 fprintf(stderr,"\n");
1124                 }
1125
1126                 if(style==1)
1127                         osrf_app_session_disconnect( session );
1128         }
1129
1130         osrfAppSessionFree( session );
1131         jsonObjectFree(params);
1132
1133         int c;
1134         float total = 0;
1135         for(c=0; c!= count*4; c++) 
1136                 total += times[c];
1137
1138         float avg = total / (count*4); 
1139         fprintf(stderr, "\n      Average round trip time: %f\n", avg );
1140
1141         return 1;
1142 }
1143
1144 /**
1145         @name Command line parser
1146
1147         This group of functions parses the command line into a series of chunks, and stores
1148         the chunks in an osrfStringArray.
1149
1150         A chunk may consist of a JSON string, complete with square brackets, curly braces, and
1151         embedded white space.  It wouldn't work simply to break up the line into  tokens
1152         separated by white space.  Sometimes white space separates chunks, and sometimes it
1153         occurs within a chunk.
1154
1155         When it sees a left square bracket or curly brace, the parser goes into JSON mode,
1156         collecting characters up to the corresponding right square bracket or curly brace.
1157         It also eliminates most kinds of unnecessary white space.
1158
1159         The JSON parsing is rudimentary.  It does not validate the syntax -- it merely looks
1160         for the end of the JSON string.  Eventually the JSON string will be passed to a real
1161         JSON parser, which will detect and report syntax errors.
1162
1163         When not in JSON mode, the parser collects tokens separated by white space.  It also
1164         collects character strings in quotation marks, possibly including embedded white space.
1165         Within a quoted string, an embedded quotation mark does not terminate the string if it
1166         is escaped by a preceding backslash.
1167 */
1168
1169 /**
1170         @brief Collect a string literal enclosed by quotation marks.
1171         @param parser Pointer to an ArgParser
1172
1173         A quotation mark serves as a terminator unless it is escaped by a preceding backslash.
1174         In the latter case, we collect both the backslash and the escaped quotation mark.
1175 */
1176 static void get_string_literal( ArgParser* parser ) {
1177         // Collect character until the first unescaped quotation mark, or EOL
1178         do {
1179                 OSRF_BUFFER_ADD_CHAR( parser->buf, *parser->itr );
1180
1181                 // Don't stop at a quotation mark if it's escaped
1182                 if( '\\' == *parser->itr && '\"' == *( parser->itr + 1 ) ) {
1183                                 OSRF_BUFFER_ADD_CHAR( parser->buf, '\"' );
1184                                 ++parser->itr;
1185                 }
1186
1187                 ++parser->itr;
1188         } while( *parser->itr && *parser->itr != '\"' );
1189
1190         OSRF_BUFFER_ADD_CHAR( parser->buf, '\"' );
1191         ++parser->itr;
1192 }
1193
1194 /**
1195         @brief Collect a JSON array (enclosed by square brackets).
1196         @param parser Pointer to an ArgParser.
1197
1198         Collect characters until you find the closing square bracket.  Collect any intervening
1199         JSON arrays, JSON objects, or string literals recursively.
1200  */
1201 static void get_json_array( ArgParser* parser ) {
1202
1203         OSRF_BUFFER_ADD_CHAR( parser->buf, '[' );
1204         ++parser->itr;
1205
1206         // Collect characters through the closing square bracket
1207         while( *parser->itr != ']' ) {
1208
1209                 if( '\"' == *parser->itr ) {
1210                         get_string_literal( parser );
1211                 } else if( '[' == *parser->itr ) {
1212                         get_json_array( parser );
1213                 } else if( '{' == *parser->itr ) {
1214                         get_json_object( parser );
1215                 } else if( isspace( (unsigned char) *parser->itr ) ) {
1216                         ++parser->itr;   // Ignore white space
1217                 } else if ( '\0' == *parser->itr ) {
1218                         return;   // Ignore failure to close the object
1219                 } else {
1220                         get_misc( parser );
1221                         // make sure that bare words don't run together
1222                         OSRF_BUFFER_ADD_CHAR( parser->buf, ' ' );
1223                 }
1224         } // end while
1225
1226         OSRF_BUFFER_ADD_CHAR( parser->buf, ']' );
1227         ++parser->itr;
1228 }
1229
1230 /**
1231         @brief Collect a JSON object (enclosed by curly braces).
1232         @param parser Pointer to an ArgParser.
1233
1234         Collect characters until you find the closing curly brace.  Collect any intervening
1235         JSON arrays, JSON objects, or string literals recursively.
1236  */
1237 static void get_json_object( ArgParser* parser ) {
1238
1239         OSRF_BUFFER_ADD_CHAR( parser->buf, '{' );
1240         ++parser->itr;
1241
1242         // Collect characters through the closing curly brace
1243         while( *parser->itr != '}' ) {
1244
1245                 if( '\"' == *parser->itr ) {
1246                         get_string_literal( parser );
1247                 } else if( '[' == *parser->itr ) {
1248                         get_json_array( parser );
1249                 } else if( '{' == *parser->itr ) {
1250                         get_json_object( parser );
1251                 } else if( isspace( (unsigned char) *parser->itr ) ) {
1252                         ++parser->itr;   // Ignore white space
1253                 } else if ( '\0' == *parser->itr ) {
1254                         return;   // Ignore failure to close the object
1255                 } else {
1256                         get_misc( parser );
1257                         // make sure that bare words don't run together
1258                         OSRF_BUFFER_ADD_CHAR( parser->buf, ' ' );
1259                 }
1260         } // end while
1261
1262         OSRF_BUFFER_ADD_CHAR( parser->buf, '}' );
1263         ++parser->itr;
1264 }
1265
1266 /**
1267         @brief Collect a token terminated by white space or a ']' or '}' character.
1268         @param parser Pointer to an ArgParser
1269
1270         For valid JSON, the chunk collected here would be either a number or one of the
1271         JSON key words "null", "true", or "false".  However at this stage we're not finicky.
1272         We just collect whatever we see until we find a terminator.
1273 */
1274 static void get_misc( ArgParser* parser ) {
1275         // Collect characters until we see one that doesn't belong
1276         while( 1 ) {
1277                 OSRF_BUFFER_ADD_CHAR( parser->buf, *parser->itr );
1278                 ++parser->itr;
1279                 char c = *parser->itr;
1280                 if( '\0' == c || isspace( (unsigned char) c ) 
1281                         || '{' == c || '}' == c || '[' == c || ']' == c || '\"' == c ) {
1282                         break;
1283                 }
1284         }
1285 }
1286
1287 /**
1288         @brief Parse the command line.
1289         @param request Pointer to the command line
1290         @param cmd_array Pointer to an osrfStringArray to hold the output of the parser.
1291
1292         The parser operates by recursive descent.  We build each chunk of command line in a
1293         growing_buffer belonging to an ArgParser, and then load the chunk into a slot in an
1294         osrfStringArray supplied by the calling code.
1295 */
1296 static void parse_args( const char* request, osrfStringArray* cmd_array )
1297 {
1298         ArgParser parser;
1299
1300         // Initialize the ArgParser
1301         parser.itr = request;
1302         parser.buf = buffer_init( 128 );
1303
1304         int done = 0;               // boolean
1305         while( !done ) {
1306                 OSRF_BUFFER_RESET( parser.buf );
1307
1308                 // skip any white space or commas
1309                 while( *parser.itr
1310                            && ( isspace( (unsigned char) *parser.itr ) || ',' == *parser.itr ) )
1311                         ++parser.itr;
1312
1313                 if( '\0' == *parser.itr )
1314                         done = 1;
1315                 else if( '{' == *parser.itr ) {
1316                         // Load a JSON object
1317                         get_json_object( &parser );
1318                 } else if( '[' == *parser.itr ) {
1319                         // Load a JSON array
1320                         get_json_array( &parser );
1321                 } else if( '\"' == *parser.itr ) {
1322                         // Load a string literal
1323                         get_string_literal( &parser );
1324                 } else {
1325                         // Anything else is delimited by white space
1326                         do {
1327                                 OSRF_BUFFER_ADD_CHAR( parser.buf, *parser.itr );
1328                                 ++parser.itr;
1329                         } while( *parser.itr && ! isspace( (unsigned char) *parser.itr ) );
1330                 }
1331
1332                 // Remove a trailing comma, if present
1333                 char lastc = OSRF_BUFFER_C_STR( parser.buf )[
1334                         strlen( OSRF_BUFFER_C_STR( parser.buf )  ) - 1 ];
1335                 if( ',' == lastc )
1336                         buffer_chomp( parser.buf );
1337
1338                 // Add the chunk to the osrfStringArray
1339                 const char* s = OSRF_BUFFER_C_STR( parser.buf );
1340                 if( s && *s ) {
1341                         osrfStringArrayAdd( cmd_array, s );
1342                 }
1343         }
1344
1345         buffer_free( parser.buf );
1346 }
1347 /*@}*/
1348