Tuesday, April 28, 2009

Console Based Schedule Reminder in Ruby

Ruby is a dynamic, open source programming language with a focus on simplicity and productivity.

Schedule Reminder is a console based small application that reads the events from a yaml file and displays reports based on the user's choice.

Its written in Ruby.For the beginning, prepare your schedule.yml file by using a text editor.

Sample yaml file can contain following lines in it for this program.

## YAML Template.

app1:
 date_time : 2009-04-22 00:00
 subject : Deadline for HW

app2:
 date_time : 2009-04-23 14:00
 subject : Lecture Hour

app3:
 date_time : 2009-04-29 14:00
 subject : Research

app4:
 date_time : 2009-04-28 14:00
 subject : Meeting at Office

app5:
 date_time : 2009-04-28 14:00
 subject : Meeting with friends

app6:
 date_time : 2009-04-28 14:00
 subject : Library

app7:
 date_time : 2009-04-30 20:00
 subject : Watch game

You can also download schedule.yml file from the link.

Then prepare your generateScheduleReport.rb file and create a new GenerateReport class in it.

When the user runs the program, a console based menu is displayed to the client. Until the client enters a valid input from the console it is displayed.

To find events of today following method can be implemented.

Method searches for the entry with today's time from the hash and if found puts it in an array.

  def generate_report_for_the_events_of_the_day()
      t = Time.now
      t = t.strftime("%Y-%m-%d")
      event_array, event_index, hashelement = load_yaml_from_location
      for i in (0...hashelement.keys.size)
        x = hashelement[hashelement.keys[i]]
        str = x[x.keys[1]]
        str = str[0..9]
        if str.eql?(t.to_s)
          event_array[event_index] = x[x.keys[0]] +" --- "+x[x.keys[1]]
          event_index = event_index + 1
        end
      end
      if event_array.size > 0
        puts "Report for the events of today is:\n"
        if event_array.size==1
            puts "There is 1 event in your schedule for today\n"
        else
            puts "There are "+event_array.size.to_s
            +" events in your schedule for today\n"
        end
        for j in (0 ... event_array.size)
          puts event_array[j]+"\n"
        end
      else
        puts "There is no event in schedule for today\n"
      end
  end

To find events of the week following method can be implemented. 

Method searches for the entry with this week's date from the hash and if found puts it in an array. strftime("%W") used to find the number of week.



  def generate_report_for_the_events_of_the_week()
      t = Time.now
      searched_week_of_year =t.strftime("%W")
      event_array, event_index, hashelement = load_yaml_from_location
      for i in (0...hashelement.keys.size)
        x = hashelement[hashelement.keys[i]]
        str = x[x.keys[1]]
        time_from_yaml = Time.parse(str)
        week_of_year_from_yaml = time_from_yaml.strftime("%W")
        if searched_week_of_year.eql?(week_of_year_from_yaml)
           event_array[event_index] = x[x.keys[0]] +" --- "+x[x.keys[1]]
           event_index = event_index + 1
        end
        week_of_year_from_yaml = 0
      end
      if event_array.size > 0
        puts "Report for the events of the week is:\n"
        if event_array.size==1
            puts "There is 1 event in your schedule for this week\n"
        else
            puts "There are "+event_array.size.to_s
            +" events in your schedule for this week\n"
        end
        for j in (0 ... event_array.size)
          puts event_array[j]+"\n"
        end
      else
        puts "There is no event in schedule for this week\n"
      end
  end

To find events of the month following method can be implemented.


Method searches for the entry with this month's date from the hash and if found puts it in an array. 

  def generate_report_for_the_events_of_the_month()
      t = Time.now
      t = t.strftime("%Y-%m-%d")
      event_array, event_index, hashelement = load_yaml_from_location
      for i in (0...hashelement.keys.size)
        x = hashelement[hashelement.keys[i]]
        str = x[x.keys[1]]
        str = str[5..6]
        if str.eql?(t.to_s[5..6])
          event_array[event_index] = x[x.keys[0]] +" --- "+x[x.keys[1]]
          event_index = event_index + 1
        end
      end
      if event_array.size > 0
        puts "Report for the events of the month is:\n"
        if event_array.size==1
            puts "There is 1 event in your schedule for this month\n"
        else
            puts "There are "+event_array.size.to_s
            +" events in your schedule for this month\n"
        end
        for j in (0 ... event_array.size)
          puts event_array[j]+"\n"
        end
      else
        puts "There is no event in schedule for this month\n"
      end
  end
To find events in the upcoming n days following method can be implemented. 

  def generate_report_for_the_events_in_the_upcoming_n_days()
      while(1)
        puts "Please enter for the number of upcoming days to generate report"
        user_input = gets
        user_input = user_input.chomp
        if is_a_number?(user_input)
          t = Time.now
          day_of_year = t.yday
          searched_day_of_year = day_of_year + user_input.to_i
          t = t.strftime("%Y-%m-%d")
          event_array, event_index, hashelement = load_yaml_from_location
          for i in (0...hashelement.keys.size)
            x = hashelement[hashelement.keys[i]]
            str = x[x.keys[1]]
            time_from_yaml = Time.parse(str)
            day_of_year_from_yaml = time_from_yaml.yday
            if day_of_year_from_yaml >= day_of_year
               and day_of_year_from_yaml <= searched_day_of_year
              event_array[event_index] = x[x.keys[0]] +" --- "+x[x.keys[1]]
              event_index = event_index + 1
            end
            day_of_year_from_yaml = 0
          end
          if event_array.size > 0
           puts "Report for the events of the upcoming "+user_input.to_s
                 +" days is:\n"
           if event_array.size==1
            puts "There is 1 event in your schedule for upcoming "
                  +user_input.to_s+" days\n"
           else
            puts "There are "+event_array.size.to_s
                  +" events in your schedule for upcoming "
                  +user_input.to_s+" days\n"
           end
           for j in (0 ... event_array.size)
             puts event_array[j]+"\n"
           end
           break
          else
            puts "There is no event in schedule for upcoming "
                  +user_input.to_s+" days"
            break
          end
        else
          puts "Enter a valid number of upcoming days!"
        end
      end
  end

To find events in the upcoming n work days following method can be implemented. 

yday method returns day of year. Exclude Sunday and Saturday in the if condition.

  def generate_report_for_the_events_in_the_upcoming_n_work_days()
      while(1)
        puts "Please enter for the number of"
              +" upcoming n working days to generate report"
        user_input = gets
        user_input = user_input.chomp
        if is_a_number?(user_input)
          t = Time.now
          day_of_year = t.yday
          searched_day_of_year = day_of_year + user_input.to_i
          t = t.strftime("%Y-%m-%d")
          event_array, event_index, hashelement = load_yaml_from_location
          for i in (0...hashelement.keys.size)
            x = hashelement[hashelement.keys[i]]
            str = x[x.keys[1]]
            time_from_yaml = Time.parse(str)
            day_from_yaml = time_from_yaml.strftime("%a")
            day_of_year_from_yaml = time_from_yaml.yday
            if day_of_year_from_yaml >= day_of_year
               and day_of_year_from_yaml <= searched_day_of_year
              if (day_from_yaml !="Sun" and day_from_yaml != "Sat")
                  event_array[event_index] = x[x.keys[0]] +" --- "+x[x.keys[1]]
                  event_index = event_index + 1
              end
            end
            day_of_year_from_yaml = 0
          end
          if event_array.size > 0
           puts "Report for the events of the upcoming "
                 +user_input.to_s+" working days is:\n"
           if event_array.size==1
            puts "There is 1 event in your schedule for upcoming "
                  +user_input.to_s+" working days\n"
           else
            puts "There are "+event_array.size.to_s
                  +" events in your schedule for upcoming "
                  +user_input.to_s+" working days\n"
           end
           for j in (0 ... event_array.size)
             puts event_array[j]+"\n"
           end
           break
          else
            puts "There is no event in schedule for upcoming "
                  +user_input.to_s+" working days"
            break
          end
        else
          puts "Enter a valid number of upcoming days!"
        end
      end
  end


To find events in the weekend following method can be implemented. strftime("%W") returns week of year. 

  def generate_report_for_the_events_in_the_weekend()
      t = Time.now
      searched_week_of_year =t.strftime("%W")
      event_array, event_index, hashelement = load_yaml_from_location
      for i in (0...hashelement.keys.size)
        x = hashelement[hashelement.keys[i]]
        str = x[x.keys[1]]
        time_from_yaml = Time.parse(str)
        day_from_yaml = time_from_yaml.strftime("%a")
        week_of_year_from_yaml = time_from_yaml.strftime("%W")
        if searched_week_of_year.eql?(week_of_year_from_yaml)
           and (day_from_yaml.eql?("Sun") or day_from_yaml.eql?("Sat"))
           event_array[event_index] = x[x.keys[0]] +" --- "+x[x.keys[1]]
           event_index = event_index + 1
        end
        week_of_year_from_yaml = 0
      end
      if event_array.size > 0
        puts "Report for the events of the weekend is:\n"
        if event_array.size==1
            puts "There is 1 event in your schedule for weekend\n"
        else
            puts "There are "+event_array.size.to_s
                  +" events in your schedule for weekend\n"
        end
        for j in (0 ... event_array.size)
          puts event_array[j]+"\n"
        end
      else
        puts "There is no event in schedule for this weekend\n"
      end
  end


To load yaml file and store its content in a hash, following method can be implemented: 

  def load_yaml_from_location
    hashelement = {}
    hashelement = YAML::load_file('/home/tufangorel/Desktop/schedule_reminder/schedule.yml')
    event_array = Array.new
    event_index = 0
    return event_array, event_index, hashelement
  end

Ruby comes with a rich date library operations.

You can download generateScheduleReport.rb file from the link. 

Put this generateScheduleReport.rb and schedule.yml files into the same folder and execute following command from the console to run the application.

user@machine:~/Desktop/reminder$ ruby generateScheduleReport.rb

A welcome menu is displayed and it loops until the user enters one of the options. 

Tested on ubuntu9.04

Passing multiple parameters with rpcgen

RPC handles arbitrary data structures and converts these structures to a standard transfer format called external data representation (XDR) before sending them over the transport.

The conversion from a machine representation to XDR is called serializing, and the reverse process is called deserializing.

For example, to send 2 string parameters, the routine is packaged in a structure containing these 2 different string parameters.

Client sends two different string parameters which are names of programs that are going to be executed at server side and the result is going to be returned back to the client.

Names of programs are set in client main() function. First program to be executed is "netstat -s" and the result of that program is going to be piped into another program "grep TCP".

Result of "grep TCP" is returned to the client program. At the same time this an example of using pipes in linux environment.

Define an extra struct in your prog.x file.
/* prog.x file                                                                
 * run_progs_1(program_names) returns the output to the client(struct param)
*/   

const MAXPARAMNAME = 255;
typedef string prog<MAXPARAMNAME>;
struct prognames
{
 prog program1;
 prog program2;
};
                                                            
program RPC_PROG {                                               
    version RPC_VERS {                                         
 string RUN_PROGS(prognames) = 1; /* procedure numer=1 */
    } = 1; /* version number = 1 */
                                                                  
} = 0x12345678; /* program number = 0x12345678 */


Running rpcgen on prog.x generates four output files:

1-)Header file (prog.h),
2-)Client stub (prog_clnt.c),
3-)Server skeleton (prog_svc.c),
4-)XDR routines in the file (prog_xdr.c).

Additionally, you need to implement that defined function (string RUN_PROGS(prognames)) in your prog.x file.

Create a new file "server_source.c" and add your implementation for
(string RUN_PROGS(prognames)) function into that file.

/* server_source.c file
 * run_progs_1(program_names) returns the output to the client(struct param)
*/   
#include <rpc/rpc.h> /* standard RPC include file */
#include "prog.h" /* this file is generated by rpcgen */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define READ_MAX_SIZE 512

/* Return the result of executed commands function1*/    
char** run_progs_1_svc(prognames *argparams, struct svc_req *arg2)
{
 FILE* fpipe;
 char* operator = " | ";
 char* program1AndProgram2;
 char* prog1 = argparams->program1;
 char* prog2 = argparams->program2;
 static char* readPipeInto;
 readPipeInto = (char*)malloc((READ_MAX_SIZE + 1)*sizeof(char));
 memset(readPipeInto, 0, READ_MAX_SIZE + 1);
 program1AndProgram2=(char*)malloc((strlen(prog1)+strlen(operator)+strlen(prog2)+1)*sizeof(char));
 strcpy(program1AndProgram2, argparams->program1);
 strcat(program1AndProgram2, operator);
 strcat(program1AndProgram2, argparams->program2);
 //execute commands
 if ( !(fpipe = (FILE*)popen(program1AndProgram2,"r")) )
 { 
   perror("Cant open pipe!");
   exit(1);
 }
 //store result in readPipeInto
 fread((char *)readPipeInto, READ_MAX_SIZE, 1, fpipe); 
 pclose(fpipe); 
 free(program1AndProgram2);
 //return output to the client
 return (char **)&readPipeInto;
}


Lastly, prepare your client and call the routine from server. Create a new file client_source.c .


/* client_source.c file */   
#include <stdio.h>
#include <stdlib.h>                                 
#include <rpc/rpc.h> /* standard RPC include file */     
#include "prog.h" /* this file is generated by rpcgen */ 

main(int argc, char *argv[])                               
{ 
   CLIENT *cl; /* RPC handle */

   char *server;

   char** resultStringFromServerExecutePrograms;
   if (argc != 2) {
       fprintf(stderr, "usage: %s hostname\n", argv[0]);
       exit(1);
   }

   server = argv[1];
   /* Create client handle */
   if ((cl = clnt_create(server, RPC_PROG, RPC_VERS, "udp")) == NULL) {
       /* can't establish connection with server */
       clnt_pcreateerror(server);
       exit(2);
   }
  
   prognames args;
   args.program1 = "netstat -s";
   args.program2 = "grep TCP";
   /*remote procedure run_progs */
   resultStringFromServerExecutePrograms = run_progs_1(&args, cl);
   if (resultStringFromServerExecutePrograms == NULL) {
      clnt_perror(cl, server);
      exit(3);
   }
   printf("Function1 Result of Run Programs is: \n\n");
   printf("%s",*resultStringFromServerExecutePrograms);
   printf("\n\n"); 
 
   clnt_destroy(cl); 
   exit(0);
}


In order to create server and client executables by using these 3 different files, issue the following commands from the terminal in the same order. 


Put your (prog.x, client_source.c, server_source.c) files into the same folder.

user@machine:~/Params$ rpcgen prog.x
user@machine:~/Params$ cc -o client client_source.c prog_clnt.c prog_xdr.c 
user@machine:~/Params$ cc -o server server_source.c prog_svc.c prog_xdr.c 
user@machine:~/Params$ ./server localhost

Open a new terminal tab and run the client.
user@machine:~/Params$ ./client localhost

Output of running client is as follows:
Function1 Result of Run Programs is: 

    35 TCP sockets finished time wait in fast timer
    15 other TCP timeouts

Run on ubuntu 9.04.

Friday, April 24, 2009

Distributed Systems Programming with rpcgen

rpcgen utility enables developers to write distributed applications.

It generates stubs for client and server side automatically. Programmer is required to write 3 additional programs to use rpcgen tool.

You need to have rpcgen utility installed on your machine to run the example. Ubuntu synaptic easily installs it for you.

1-) Write the prog.x file which contains definition for required routines. You can give any name for this *.x file ending with .x extension.You need to declare remote procedures in a remote program.

/*  prog.x
                                                                
 * Define your procedure here
 
 * function_1 dir_list_1(string) returns the output to the client 
 * (takes one string argument)

 * function_2 execute_uptime_1() returns the output to the client 
 * (no argument)
*/    
program RPC_PROG {                                               
    version RPC_VERS {                                         
    string DIR_LIST(string) = 1; /* procedure number = 1 */
    string EXECUTE_UPTIME(void) = 2; /* procedure number = 2 */
    } = 1; /* version number = 1 */
} = 0x12345678; /* program number = 0x12345678 */


2-) Write remote procedure implementation in a *.c file.

This file contains implementation of procedures declared in prog.x file.

/*  server_source.c   */  
#include <time.h>
#include <rpc/rpc.h> /* standard RPC include file */
#include "prog.h" /* this file is generated by rpcgen */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define READ_MAX_SIZE 512

/*Return the listing of all files in a directory function1*/
char** dir_list_1_svc(char** location, struct svc_req* arg2)
{
 FILE* fpipe;
 char* command="ls -l ";
 char* commandAndDirName;
 static char* readPipeInto;
 readPipeInto = (char*)malloc((READ_MAX_SIZE + 1)*sizeof(char));
 memset(readPipeInto, 0, READ_MAX_SIZE + 1);
 commandAndDirName=(char*)malloc((strlen(command)+strlen(*location)+1)*sizeof(char));
 strcpy(commandAndDirName, command);
 strcat(commandAndDirName, *location);
 //execute command ls -l
 if ( !(fpipe = (FILE*)popen(commandAndDirName,"r")) )
 {
    perror("Cant open pipe!");
    exit(1);
 }
 //store result in readPipeInto
 fread((char*)readPipeInto, READ_MAX_SIZE, 1, fpipe); 
 pclose(fpipe); 
 free(commandAndDirName);
 //return output to the client
   return (char**)&readPipeInto;
}
/* Return the result of command uptime function2*/
char** execute_uptime_1_svc(void* arg1,struct svc_req* arg2)
{
 FILE* fpipe;
 char* command="uptime";
 static char* readPipeInto;
 readPipeInto = (char*)malloc((READ_MAX_SIZE + 1)*sizeof(char));
 memset(readPipeInto, 0, READ_MAX_SIZE + 1);
 //execute command uptime
 if ( !(fpipe = (FILE*)popen(command,"r")) )
 { 
    perror("Cant open pipe!");
    exit(1);
 }
 //store result in readPipeInto
 fread((char*)readPipeInto, READ_MAX_SIZE, 1, fpipe); 
 pclose(fpipe);
 //return output to the client
 return (char**)&readPipeInto;
}

1st procedure at server program
" char** dir_list_1_svc(char** location, struct svc_req* arg2) " 


corresponds to " string DIR_LIST(string) " procedure declaration in prog.x file.

DIR_LIST procedure takes a string directory location such as "/home" from client program as an input parameter and appends this parameter to "ls -l" program at server procedure.


So, output of "ls -l /home" is returned back to the client from the server.

For return values of procedures, one more level of indirection is added to each procedure signature and parameters at server side.


For the 1st procedure at client program "string DIR_LIST(string) ", return value is changed from (char*) to (char**) .

2nd procedure at server program
" char** execute_uptime_1_svc(void* arg1,struct svc_req* arg2) "
corresponds to " string EXECUTE_UPTIME(void) " procedure declaration in prog.x file.

EXECUTE_UPTIME(void) procedure does not take any parameter from the client and executes the external program "uptime". 


Result of execution is sent back to the client.

At both of the above procedure implementations, it is common that programs (ls -l and uptime) are executed at server side and the result is written into a pipe. 


Then command output is read from that pipe and returned back to the client side.

3-) Write the main client program that calls the remote procedures from server.




/* client_source.c file */

#include <stdio.h>
#include <stdlib.h>                                       
#include <rpc/rpc.h> /* standard RPC include file */     
#include "prog.h" /* this file is generated by rpcgen */ 
                    
main(int argc, char *argv[])                               
{ 
   CLIENT *cl; /* RPC handle */

   char *server;
   /* return value from dir_list_1() function1*/
   char** resultStringFromServerDirList;
   /* return value from execute_uptime_1 function2*/ 
   char** resultStringFromServerUptime; 
   if (argc != 2) {
       fprintf(stderr, "usage: %s hostname\n", argv[0]);
       exit(1);
   }

   server = argv[1];
   /* Create client handle */
   if ((cl = clnt_create(server, RPC_PROG, RPC_VERS, "udp")) == NULL) {
       /* can't establish connection with server */
       clnt_pcreateerror(server);
       exit(2);
   }
   char* directory = "/home";

   /* call remote procedure dir_list */
   resultStringFromServerDirList = dir_list_1((char**)&directory, cl);
   if (resultStringFromServerDirList == NULL) {
      clnt_perror(cl, server);
      exit(3);
   }
   printf("\n\n");
   printf("Function1 Result of Directory Listing is: \n\n");
   printf("%s",*resultStringFromServerDirList);
   printf("\n\n");

   /* call remote procedure execute_uptime */
   resultStringFromServerUptime = execute_uptime_1(NULL, cl);
   if (resultStringFromServerUptime == NULL) {
      clnt_perror(cl, server);
      exit(4);
   }
   printf("Function2 Result of Command uptime is: \n\n");
   printf("%s",*resultStringFromServerUptime);
   printf("\n\n");
   
   clnt_destroy(cl); 
   exit(0);
}

After writing those 3 files, you can use rpcgen tool to generate related client and server programs.


Put "client_source.c" "prog.x" and "server_source.c" files into the same folder. 

Then by using cc -o from the command line create client and server executables separately.


Sample Run:
------------------------------------------------------------
Prepare server and run from the console: Open your console to run following commands.

user@machine:~/Desktop/user$ rpcgen prog.x
user@machine:~/Desktop/user$ cc -o client client_source.c prog_clnt.c 
user@machine:~/Desktop/user$ cc -o server server_source.c prog_svc.c 
user@machine:~/Desktop/user$ ./server 


"rpcgen prog.x" command creates prog_clnt.c(client stub) prog_svc.c(server stub) and prog.h header file. 


At the end of successful compilation client and server executables are located in the same directory. Then open a new terminal tab and Run client:

user@machine:~/Desktop/user$ ./client localhost


Server returns the output of executed programs to the client.

user@machine:~/Desktop/user$ ./client localhost


Function1 Result of Directory Listing is: 

total 4
drwxr-xr-x 52 user user 4096 2009-04-25 01:04 user


Function2 Result of Command uptime is: 

 01:05:49 up  4:08,  3 users,  load average: 0.17, 0.36, 0.25


user@machine:~/Desktop/user$