/****************************************/ /* */ /* Author: */ /* */ /* Ira Winston */ /* Computer Science Department */ /* University of Pennsylvania */ /* */ /****************************************/ /*************************************************************************/ /* */ /* Notice Utitilty Program */ /* ----------------------- */ /* */ /* This program allows the user to maintain the 'MESSAGE' file. New */ /* messages may be added to the file, old messages may be deleted, a */ /* list of all messages may be displayed and messages may be displayed */ /* briefly or in detail. */ /* */ /* This program requires SYSPRV to access the 'MESSAGE' and 'USER' */ /* files. Access to these files is SHARED_READ, SHARED_WRITE and */ /* record-lock checks are performed. */ /* */ /* The 'MESSAGE' file contains information about each message. Each */ /* message has one header record and one or more text records. The */ /* primary key of the file consists of an 8 byte timestamp and a 1 */ /* byte sequence number. The headers have sequence number 0 and the */ /* text records have a sequence number between 1 and 127. The 1 byte */ /* sequence number is Alternate key #1; this allows all headers to be */ /* retrieved easily. */ /* */ /* The header records contain the date the message becomes effective, */ /* the date the message terminates, the repetition type of the message, */ /* the terminals the message applies to, and the users the message */ /* applies to. */ /* */ /* The text records contain a length and some text. The last text */ /* record of a message has length of 1 and a single CTRL/Z as text. */ /* */ /* The 'USER' file contains information about each user who uses the */ /* NOTICE system. Users are added to this file, the first time they */ /* try to read there notices. The primary key of the file is a 12 */ /* byte username. The user records also contain information about */ /* each notice that applies to the user. The timestamp of the message, */ /* the start date, the termination date, the repetition type, the date */ /* last read, and the number of times read are included. */ /* */ /*************************************************************************/ NoticeUtl: Procedure Options(Main); /* Subroutines */ %Include Scan_Tree; %Include Time_less; %Include Get_messno; %Include Index_Access; %Include Read_repeat_type; %Include Get_command; %Include Scan_Index; %Include readtree; %Include readtext; %Include read_line; %Include set_ctrlc; %Include Critical_Section; %Include Sys$Gettim; %Include Locked_Record; %Include Sys$Asctim; %Include Sys$Bintim; %Include Read_Date; %Include Add_to_Index; %Include Write_Request; %Include Write_List; %Include Lib_Day_to_time; %Include Lib$day; %Include Sys$Getjpi; %Include Lib$Get_Foreign; Declare Null Builtin; /* Data Structure Definitions */ %Include commands; %Include $Stsdef; %Include treedef; %Include indexdef; %Include reptypes; %Include $Prvdef; /* File Definitions */ %Replace message_filename By 'SYS$SYSDISK:[SYSMGR]MESSAGE.DAT'; Declare Message_File_input File Input Record Keyed Environment (Shared_read,Shared_write, Maximum_record_size(2048), Multibuffer_count(5)); Declare Message_file_update File Update Record Keyed Environment (Shared_read,Shared_write, Maximum_record_size(2048), Multibuffer_count(5)); Declare messfptr pointer, 1 messf Based(messfptr), 2 timestamp bit(64) aligned, 2 sequence_no fixed binary(7), 2 start_date fixed binary(31), 2 termination_date fixed binary(31), 2 repetition_type fixed binary(7), 2 number_of_terminals fixed binary(15), 2 terminals(messf.number_of_terminals) character(5), 2 number_of_users fixed binary(15), 2 users(messf.number_of_users) character(12); Declare messfkey character(9) Based(messfptr), messfseckey character(1) Defined(messf.sequence_no); Declare messnptr pointer, (ext_number_of_terminals,ext_number_of_users) Fixed Binary(15), 1 messn Based(messnptr), 2 timestamp bit(64) aligned, 2 sequence_no fixed binary(7), 2 start_date fixed binary(31), 2 termination_date fixed binary(31), 2 repetition_type fixed binary(7), 2 number_of_terminals fixed binary(15), 2 terminals(ext_number_of_terminals) character(5), 2 number_of_users fixed binary(15), 2 users(ext_number_of_users) character(12); Declare messnkey character(9) Based(messnptr); Declare textfptr Pointer, 1 textf Based(textfptr), 2 timestamp Bit(64) Aligned, 2 sequence_no Fixed Binary(7), 2 text_length Fixed Binary(15), 2 text Character(textf.text_length); Declare 1 Key_Record, 2 timestamp Bit(64) Aligned, 2 sequence_no Fixed Binary(7), messkey Character(9) Based(Addr(Key_Record)); %Replace user_filename By 'SYS$SYSDISK:[SYSMGR]USER.DAT'; Declare User_File_update File Update Record Keyed Environment (Shared_read,Shared_write, Maximum_record_size(2048), Multibuffer_count(5)); Declare User_File_input File Input Record Keyed Environment (Shared_read,Shared_write, Maximum_record_size(2048), Multibuffer_count(5)); Declare userfptr pointer, 1 userf Based(userfptr), 2 name character(12), 2 number_of_messages fixed binary(15), 2 messages(userf.number_of_messages), 3 timestamp bit(64) aligned, 3 start_date fixed binary(31), 3 termination_date fixed binary(31), 3 repetition_type fixed binary(7), 3 date_last_read fixed binary(31), 3 access_count fixed binary(15); Declare usernptr pointer, ext_number_of_messages fixed binary(15), 1 usern Based(usernptr), 2 name character(12), 2 number_of_messages fixed binary(15), 2 messages(ext_number_of_messages), 3 timestamp bit(64) aligned, 3 start_date fixed binary(31), 3 termination_date fixed binary(31), 3 repetition_type fixed binary(7), 3 date_last_read fixed binary(31), 3 access_count fixed binary(15); /* Variables */ Declare 1 Jpi_List, 2 Jpi_username, 3 Jpi_Length Fixed Binary(15) Init(12), 3 Code Fixed Binary(15) Init(Jpi$_username), 3 Address Pointer Init(Addr(username)), 3 Return_length Pointer Init(Null), 2 Jpi_procpriv, 3 Jpi_Length Fixed Binary(15) Init(8), 3 Code Fixed Binary(15) Init(Jpi$_Procpriv), 3 Address Pointer Init(Addr(procpriv)), 3 Return_length Pointer Init(Null), 2 Jpi_listend Fixed Binary(31) Init(Jpi$C_listend), username Character(12) Static, procpriv Bit(64) Aligned Static; Declare Line_count Fixed Binary(31); Declare command Fixed Binary(7); Declare input_buffer Character(255) Varying, arg_start Fixed Binary; Declare ind Fixed Binary(31); Declare indextree Pointer, index_count Fixed Binary(31), index_generated Bit(1) Init('0'b); Declare remind_mode Bit(1); /* Main program */ /* Open message and user files */ Open File(user_file_input) Title(user_filename); Open File(message_file_input) Title(message_filename); /* Set Control-C AST */ On VaxCondition(C_Interrupt) Goto Exit; Sts$Value = Set_Ctrlc(); /* Check for remind mode */ Sts$Value = Lib$Get_Foreign(Command_Line_Descriptor); Sts$Value = Sys$Getjpi(,,,Jpi_list,,,); If Command_line = 'REMIND' Then remind_mode = '1'b; Else If Index(procpriv & prv$v_sysprv,'1'b) = 0 Then remind_mode = '1'b; Else remind_mode = '0'b; /* Command dispatcher */ Call get_command(command,input_buffer,arg_start); Do While (command ^= halt_command); If command = add_command Then Call addproc; Else If command = delete_command Then Call deleteproc; Else If command = show_command Then Call showproc(0); Else If command = list_command Then Call listproc; Else If command = stat_command Then Call showproc(1); Else If command = edit_command Then Call editproc; Else If command = help_command Then Call helpproc; Else If command = invalid_command Then Do; put skip list('Invalid Command'); End; Call get_command(command,input_buffer,arg_start); End; /* Do While */ Exit: Close File(message_file_input); Close File(user_file_input); Return; /* Add a message to the message file and update appropriate user records */ addproc: Procedure; Declare user_tree pointer Init(Null), /* list of users */ term_tree pointer Init(Null), /* list of terminals */ text_list pointer Init(Null), /* text of message */ start_day Fixed Binary(31), /* start date of message */ reptype Fixed Binary(7), /* repetition type */ termination_day Fixed Binary(31); /* termination date */ Declare datbuf Character(11), time Bit(64) Aligned, /* timestamp of message */ count Fixed Binary(7), retries Fixed Binary(31), (today,daytime) Fixed Binary(31), effective_date Bit(64) Aligned, eof Bit(1), done Bit(1); /* Setup Control-C On-Unit */ On VaxCondition(C_Interrupt) Begin; Sts$Value = Set_Ctrlc(); GoTo Exit; End; /* Get distribution list */ If remind_mode Then Do; Call Start_Critical_Section; Allocate nametree Set(user_tree); user_tree->nametree.name = username; user_tree->nametree.left = null; user_tree->nametree.right = null; Call End_Critical_Section; ext_number_of_users = 1; End; Else Call readtree('Send to: ',user_tree,ext_number_of_users,0); /* Get terminal list */ Call readtree('Terminals: ',term_tree,ext_number_of_terminals,1); /* Display current effective date */ Call Lib$day(today,,daytime); If daytime < 1440000 Then Do; /* between midnight and 4AM? */ Call Lib_day_to_time(today-1,effective_date); Sts$Value = Sys$asctim(,datbuf,effective_date,0); Put Skip(2) Edit('Today''s effective date is ',datbuf) (A,A); End; Else Do; Sts$Value = Sys$Asctim(,datbuf,,0); Put Skip(2) Edit('Today''s date is ',datbuf) (A,A); End; /* Get start and termination dates */ Put Skip; done = '0'b; Do While(^done); Call read_date('Enter start date (default today): ','1'b,start_day); Call read_date('Enter termination date: ','0'b,termination_day); If start_day <= termination_day Then done = '1'b; Else Put Skip List('?Start date is after termination date.'); End; /* Get message type */ Call Read_repeat_type(reptype); If remind_mode Then reptype = reptype + rpt_remind_offset; /* Indicate terminals present in user record */ If ext_number_of_terminals > 0 Then reptype = -reptype; /* Get message */ Put Skip; Call Readtext(text_list); /* Write header and text to message file */ Call Start_Critical_Section; /* Only complete update */ On Key(Message_file_update) Begin; Put Skip List('?Duplicate key in ADDPROC'); Call Resignal(); End; Sts$Value = Sys$Gettim(time); /* Get key value */ Open File(Message_File_Update) Title(message_filename); /* Write text to message file */ count = 0; Do text_ptr = text_list Repeat text_ptr->text_list_entry.link While (text_ptr ^= Null); count = count + 1; text_ptr->text_list_entry.timestamp = time; text_ptr->text_list_entry.sequence_no = count; Write File(message_file_update) From(text_ptr->text_list_entry.textf) Keyfrom(textfkey); End; /* Write header to message file */ Allocate messn Set(messnptr); messn.timestamp = time; messn.sequence_no = 0; messn.start_date = start_day; messn.termination_date = termination_day; messn.repetition_type = reptype; messn.number_of_terminals = ext_number_of_terminals; messn.number_of_users = ext_number_of_users; ind = 0; Call Scan_tree(user_tree,copy_user); ind = 0; Call Scan_tree(term_tree,copy_term); Write File(message_file_update) From(messn) Keyfrom(messnkey); Free messn; If index_generated Then Call Add_to_Index(indextree,time,index_count); Close File(message_file_update); /* Add message to each eligible user's message list */ Open File(User_File_Update) Title(User_filename); If user_tree ^= Null Then Call Scan_tree(user_tree,assign_message); Else Do; On Endfile(User_File_Update) eof = '1'b; eof = '0'b; On error Begin; If Locked_Record(retries,Oncode()) Then Goto Retry; Else Call Resignal(); End; Do While(^eof); retries = 0; Retry: Read File(User_File_Update) Set(userfptr); If ^eof Then Call Add_Message_to_User; End; Revert Error; End; Close File(User_File_Update); Call End_Critical_Section; /* Clean up */ Exit: Revert VaxCondition(C_interrupt); Call Start_Critical_Section; Call Scan_Tree(user_tree,free_entry); Call Scan_Tree(term_tree,free_entry); text_ptr = text_list; Do While (text_ptr ^= Null); text_list = text_ptr->text_list_entry.link; Free text_ptr->text_list_entry; text_ptr = text_list; End; Call End_Critical_Section; Return; /* Add message to a user's message. User is an element of the user tree */ Assign_message: Procedure (tree_entry); Declare tree_entry Pointer; Declare nonexistent_user Bit(1) Init('0'b); On Key(User_File_Update) nonexistent_user = '1'b; Retries = 0; On Error Begin; If Locked_Record(Retries,Oncode()) Then Goto Retry; Else Call Resignal(); End; Retry: Read File(User_File_Update) Set(userfptr) Key(tree_entry->nametree.name); Revert Error; If ^nonexistent_user Then Call Add_message_to_user; End Assign_message; /* Add a message to the user record just read into 'userf' */ Add_message_to_user: Procedure; Declare i Fixed Binary; ext_number_of_messages = userf.number_of_messages + 1; Allocate Usern Set(usernptr); usern.name = userf.name; usern.number_of_messages = ext_number_of_messages; Do i = 1 To userf.number_of_messages; usern.messages(i) = userf.messages(i); End; usern.messages(ext_number_of_messages).timestamp = time; usern.messages(ext_number_of_messages).start_date = start_day; usern.messages(ext_number_of_messages).termination_date = termination_day; usern.messages(ext_number_of_messages).repetition_type = reptype; usern.messages(ext_number_of_messages).date_last_read = 0; usern.messages(ext_number_of_messages).access_count = 0; Rewrite File(User_File_Update) From(usern); Free Usern; End Add_message_to_user; End addproc; /* copy a username to the message file record buffer */ Copy_user: Procedure (tree_entry); Declare tree_entry Pointer; ind = ind + 1; messn.users(ind) = tree_entry->nametree.name; End Copy_user; /* copy a terminal name to the message file record buffer */ Copy_term: Procedure (tree_entry); Declare tree_entry Pointer; ind = ind + 1; messn.terminals(ind) = tree_entry->nametree.name; End Copy_term; /* free a tree entry */ Free_entry: Procedure (tree_entry); Declare tree_entry Pointer; Free tree_entry->nametree; End Free_entry; editproc: Procedure; Declare messno Fixed Binary(31), (new_termlist,new_days,new_reptype) Bit(1) Init('0'b), (start_day,termination_day) Fixed Binary(31), term_tree Pointer Init(Null), i Fixed Binary(31), nonexistent_user Bit(1), eof Bit(1), done Bit(1), retries Fixed Binary(31), ind_entry Pointer, reptype Fixed Binary(7); On VaxCondition(C_interrupt) Begin; Sts$value = Set_Ctrlc(); Goto Exit; End; /* Generate index if necessary */ If ^index_generated Then Call Generate_Index; /* Get message number from command line */ If get_messno(input_buffer,arg_start,index_count,messno) Then Do; Call Index_Access(indextree,messno,ind_entry); Key_record.timestamp = ind_entry->index_entry.timestamp; Key_record.sequence_no = 0; On Key(message_file_input) messfptr = Null; On Error Begin; If Locked_Record(retries,oncode()) Then Goto Retry; Else Call Resignal(); End; retries = 0; Retry: Read File(message_file_input) Set(messfptr) Key(messkey); Revert Error; Revert Key(message_file_input); If messfptr ^= Null Then If messf.repetition_type = rpt_marked_for_delete Then messfptr = Null; If messfptr ^= Null Then Do; /* Get terminal list */ If Read_line('Do you want to change the terminal list[Y,N]?', input_buffer,cvt2upper) Then If Substr(Input_buffer,1,1) = 'Y' Then Do; new_termlist = '1'b; Call readtree('Terminals: ',term_tree,ext_number_of_terminals,1); End; /* Get new start and end dates */ If Read_line('Do you want to change the start and termination dates[Y,N]? ', input_buffer,cvt2upper) Then If Substr(input_buffer,1,1) = 'Y' Then Do; new_days = '1'b; done = '0'b; Do While(^done); Call read_date('Enter start date (default today): ','1'b,start_day); Call read_date('Enter termination date: ','0'b,termination_day); If start_day <= termination_day Then done = '1'b; Else Put Skip List('?Start date is after termination date.'); End; End; /* Get message type */ If Read_line('Do you want to change the repetition type[Y,N]? ', input_buffer,cvt2upper) Then If Substr(input_buffer,1,1) = 'Y' Then Do; new_reptype = '1'b; Call read_repeat_type(reptype); If remind_mode Then reptype = reptype + rpt_remind_offset; End; On Error Begin; If Locked_Record(retries,oncode()) Then Goto Retry1; Else Call Resignal(); End; On Key(message_file_update) messfptr = Null; Call Start_Critical_Section; Open File(message_file_update) Title(message_filename); retries = 0; Retry1: Read File(message_file_update) Set(messfptr) Key(messkey); Revert Error; Revert Key(message_file_update); If ^new_termlist Then ext_number_of_terminals = messf.number_of_terminals; ext_number_of_users = messf.number_of_users; Allocate messn Set(messnptr); messn.users = messf.users; messn.timestamp = messf.timestamp; messn.sequence_no = 0; messn.number_of_terminals = ext_number_of_terminals; messn.number_of_users = ext_number_of_users; If new_termlist Then Do; ind = 0; Call Scan_tree(term_tree,copy_term); End; Else messn.terminals = messf.terminals; If new_days Then Do; messn.start_date = start_day; messn.termination_date = termination_day; End; Else Do; messn.start_date = messf.start_date; messn.termination_date = messf.termination_date; End; If ^new_reptype Then reptype = abs(messf.repetition_type); If messn.number_of_terminals = 0 Then messn.repetition_type = reptype; Else messn.repetition_type = -reptype; Rewrite File(message_file_update) From(messn); Close File(message_file_update); Open File(user_file_update) Title(user_filename); If ext_number_of_users > 0 Then Do; On Key(user_file_update) nonexistent_user = '1'b; On Error Begin; If Locked_record(retries,oncode()) Then Goto Retry2; Else Call Resignal(); End; Do i = 1 to ext_number_of_users; nonexistent_user = '0'b; retries = 0; Retry2: Read File(user_file_update) Set(userfptr) Key(messn.users(i)); If ^nonexistent_user Then Call Edit_user; End; Revert Key(user_file_update); Revert Error; End; Else Do; On Endfile(user_file_update) eof = '1'b; On Error Begin; If Locked_record(retries,oncode()) Then Goto Retry3; Else Call Resignal(); End; eof = '0'b; Do While(^Eof); retries = 0; Retry3: Read File(user_file_update) Set(userfptr); If ^eof Then Call Edit_user; End; Revert Error; End; Close File(user_file_update); Free messn; Call End_Critical_Section; End; Else Put Skip List('?No such message'); End; Exit: Call Start_Critical_Section; Call Scan_tree(term_tree,free_entry); Call End_Critical_Section; Return; Edit_user: Procedure; Declare result_index Fixed Binary(31); Call Find_message(messn.timestamp,result_index); If result_index ^= 0 Then Do; userf.messages(result_index).start_date = messn.start_date; userf.messages(result_index).termination_date = messn.termination_date; userf.messages(result_index).repetition_type = messn.repetition_type; Rewrite File(User_file_update) From(userf); End; End Edit_user; End Editproc; Deleteproc: Procedure; Declare messno Fixed Binary(31), ind_entry Pointer, i Fixed Binary(31), nonexistent_user Bit(1), retries Fixed Binary(31), (eof,done) Bit(1); On VaxCondition(C_interrupt) Begin; Sts$value = Set_Ctrlc(); Goto Exit; End; /* Generate index if necessary */ If ^index_generated Then Call Generate_Index; /* Get message number from command line */ If get_messno(input_buffer,arg_start,index_count,messno) Then Do; Call Start_Critical_Section; /* Set repetition type of message to marked for delete */ Open File(message_file_update) Title(message_filename); /* Get timestamp of message */ Call Index_Access(indextree,messno,ind_entry); /* Read header record */ Key_record.timestamp = ind_entry->index_entry.timestamp; Key_record.sequence_no = 0; On Key(message_file_update) messfptr = Null; On Error Begin; If Locked_record(retries,oncode()) Then Goto retry; Else Call Resignal(); End; retries = 0; Retry: Read File(message_file_update) Set(messfptr) Key(messkey); Revert Error; If messfptr ^= null Then If messf.repetition_type = rpt_marked_for_delete Then If read_line('Message is marked for delete. Delete anyway[Y,N]? ', input_buffer,cvt2upper) Then Do; If Substr(input_buffer,1,1) ^= 'Y' Then messfptr = Null; End; Else messfptr = Null; If messfptr ^= Null Then Do; messf.repetition_type = rpt_marked_for_delete; Rewrite File(message_file_update) From(messf); ext_number_of_users = messf.number_of_users; ext_number_of_terminals = messf.number_of_terminals; Allocate messn Set(messnptr); messn = messf; Close File(message_file_update); /* Rewind message file */ Open File(message_file_update) Title(message_filename); Open File(user_file_update) Title(user_filename); If ext_number_of_users > 0 Then Do; On Key(User_file_update) nonexistent_user = '1'b; On Error Begin; If Locked_record(retries,oncode()) Then Goto Retry1; Else Call Resignal(); End; Do i = 1 To ext_number_of_users; nonexistent_user = '0'b; retries = 0; Retry1: Read File(user_file_update) Set(userfptr) Key(messn.users(i)); If ^nonexistent_user Then Call delete_message_from_user; End; Revert Key(user_file_update); Revert Error; End; Else Do; On Endfile(user_file_update) eof = '1'b; On Error Begin; If Locked_record(retries,oncode()) Then Goto Retry2; Else Call Resignal(); End; eof = '0'b; Do While(^eof); retries = 0; Retry2: Read File(user_file_update) Set(userfptr); If ^eof Then Call delete_message_from_user; End; Revert Error; Revert Endfile(user_file_update); End; Close File(User_file_update); Free messn; On Error Begin; If Locked_record(retries,oncode()) Then Goto Retry3; Else Call Resignal(); End; retries = 0; Retry3: Read File(message_file_update) Set(messfptr) Key(messkey); Delete File(message_file_update); On Error Begin; If Locked_Record(Retries,oncode()) Then Goto Retry4; Else Call Resignal(); End; key_record.sequence_no = 1; done = '0'b; Do While(^done); retries = 0; Retry4: Read File(message_file_update) Set(textfptr) Key(messkey); If textf.text = Byte(26) Then done = '1'b; Else Key_record.sequence_no = key_record.sequence_no + 1; Delete File(message_file_update); End; Revert Error; End; Else Put Skip List('?No such message'); Close File(message_file_update); Call End_Critical_Section; End; Exit: Return; End Deleteproc; Delete_message_from_user: Procedure; Declare result_index Fixed Binary(31), (i,j) Fixed Binary; Call Find_message(messn.timestamp,result_index); If result_index ^= 0 Then Do; ext_number_of_messages = userf.number_of_messages - 1; Allocate usern Set(usernptr); usern.name = userf.name; usern.number_of_messages = ext_number_of_messages; j = 0; Do i = 1 To userf.number_of_messages; If i ^= result_index Then Do; j = j + 1; usern.messages(j) = userf.messages(i); End; End; Rewrite File(user_file_update) From(usern); End; End Delete_message_from_user; showproc: Procedure (type); Declare type Fixed Binary(7); Declare count Fixed Binary(7), ind_entry Pointer, nonexistent_user Bit(1), eof Bit(1) Init('0'b), retries Fixed Binary(31), i Fixed Binary(31), buffer Character(255) Varying, messno Fixed Binary(31) Init(0); On VaxCondition(C_interrupt) Begin; Sts$Value = Set_Ctrlc(); Goto Exit; End; If ^index_generated Then Call Generate_index; If Get_messno(input_buffer,arg_start,index_count,messno) Then Do; Call Index_access(indextree,messno,ind_entry); line_count = 0; Call Display_header(ind_entry,messno); If messfptr ^= Null Then Do; /* deleted message check */ If type = 1 Then Do; /* Stat command */ Call Write_Request(1,line_count); Put Skip; If messf.number_of_users > 0 Then Do; On Key(User_file_input) nonexistent_user = '1'b; On Error Begin; If Locked_Record(retries,Oncode()) Then Goto Retry; Else Call Resignal(); End; Do i = 1 To messf.number_of_users; nonexistent_user = '0'b; retries = 0; Retry: Read File(user_file_input) Set(Userfptr) Key(messf.users(i)); If ^nonexistent_user Then Call Display_stats(messf.users(i),'1'b); End; /* once for each user on distribution list */ Revert Key(User_file_input); Revert Error; End; /* User list specified */ Else Do; On Endfile(user_file_input) eof = '1'b; On Error Begin; If Locked_Record(retries,Oncode()) Then Goto Retry1; Else Call Resignal(); End; /* Rewind user file */ Call Start_Critical_Section; Close File(user_file_input); Open File(user_file_input) Title(user_filename); Call End_Critical_Section; Do While(^eof); retries = 0; Retry1: Read File(user_file_input) Set(userfptr); If ^eof Then Call Display_stats(userf.name,'0'b); End; Revert Error; End; /* User file scan */ End; /* Stat command */ Call Write_request(1,line_count); Put Skip; If type = 1 Then Do; Call Write_Request(1,line_count); Put Skip List('Text:'); End; count = 1; buffer = get_text(ind_entry->index_entry.timestamp,count); Do While (buffer ^= Byte(26)); Call Write_Request(Divide(Length(buffer)+1,80,31)+1,line_count); Put Skip Edit(buffer) (A); count = count + 1; buffer = get_text(ind_entry->index_entry.timestamp,count); End; End; /* message not deleted */ End; /* valid message number */ Exit: Put Skip; Return; End showproc; Display_stats: Procedure(username,key_mode); Declare mess_index Fixed Binary(31), key_mode Bit(1), username Character(12), name Character(13) Varying, access_count_str Character(80) Varying, time Bit(64) Aligned, datbuf Character(11); Call Write_Request(1,line_count); name = username || ' '; name = Substr(name,1,index(name,' ')); Call Find_message(messf.timestamp,mess_index); If mess_index = 0 Then Do; If key_mode Then Put Skip Edit(name,'doesn''t have the message in his/her list') (A,A); End; Else If userf.messages(mess_index).access_count = 0 Then Put Skip Edit(name,'has never seen the message') (A,A); Else Do; Put String(access_count_str) List(userf.messages(mess_index).access_count); access_count_str = Substr(access_count_str, Verify(access_count_str,' ')); If access_count_str = '1' Then Access_count_str = 'once'; Else If access_count_str = '2' Then Access_count_str = 'twice'; Else access_count_str = access_count_str || 'times'; Call Lib_day_to_time(userf.messages(mess_index).date_last_read, time); Sts$Value = Sys$Asctim(,datbuf,time,0); Put Skip Edit(name,'has seen the message ',access_count_str, '. The last time was on ',datbuf,'.') (6(A)); End; End Display_stats; Find_message: Procedure(timestamp,result_index); Declare timestamp Bit(64) Aligned, result_index Fixed Binary(31), i Fixed Binary(31); Do i = 1 To userf.number_of_messages; If userf.messages(i).timestamp = timestamp Then Do; result_index = i; Return; End; End; result_index = 0; End Find_message; listproc: Procedure; Declare count Fixed Binary(31); /* message number */ Declare list_time Bit(64) Aligned; On VaxCondition(C_interrupt) Begin; Sts$value = Set_Ctrlc(); Goto Exit; End; If ^index_generated Then Call generate_index; line_count = 0; count = 0; If arg_start > Length(input_buffer) Then Call Scan_Index(indextree,display_header,count); Else Do; Sts$Value = Sys$Bintim(Substr(input_buffer,arg_start),list_time); If Sts$Success Then Call Scan_index(indextree,display_header_for_list,count); Else Put Skip List('?Invalid start time'); End; Exit: Put Skip; Return; Display_header_for_list: Procedure(indextree,count); Declare indextree Pointer, count Fixed Binary(31); If time_less(list_time,indextree->index_entry.timestamp) Then Call Display_header(indextree,count); End Display_header_for_list; End listproc; Display_header: Procedure(indextree,count); Declare indextree Pointer, count Fixed Binary(31); Declare retries Fixed Binary(31), (start_date_str,termination_date_str) Character(11), creation_time_str Character(23), (start_date,termination_date) Bit(64) Aligned, reptype Fixed Binary(7), flag Character(1) varying, (buffer,messno) Character(80) Varying; Declare repetition_text(5) Character(32) Varying Static Readonly Initial ('once', 'daily', 'always', 'once except always on last day', 'daily except always on last day'); /* ON unit in case message was deleted after index generation */ On Key(message_file_input) messfptr = Null; /* Build key */ Key_record.timestamp = Indextree->index_entry.timestamp; Key_record.sequence_no = 0; /* Set up On unit for Record lock */ On Error Begin; If Locked_Record(retries,oncode()) Then Goto Retry; Else Call Resignal(); End; /* read header record */ retries = 0; retry: Read File(message_file_input) Set(messfptr) Key(messkey); Revert Error; If messfptr ^= Null Then If messf.repetition_type = rpt_marked_for_delete Then messfptr = Null; /* Display header */ Put String(messno) List(count); messno = Substr(messno,Verify(messno,' ')); Sts$Value = Sys$Asctim(,creation_time_str,Key_record.timestamp,0); Call Write_Request(2,line_count); Put Skip(2) Edit('Message #',messno,'Created:',creation_time_str) (2(A),Column(15),A,Column(24),A); If messfptr ^= Null Then Do; Call Lib_day_to_time(messf.start_date,start_date); Sts$Value = Sys$Asctim(,start_date_str,start_date,0); Call Lib_day_to_time(messf.termination_date,termination_date); Sts$Value = Sys$Asctim(,termination_date_str,termination_date,0); Put Edit('Starts:',start_date_str) (Column(57),A,Column(66),A); Call Write_Request(1,line_count); reptype = abs(messf.repetition_type); flag = ''; If reptype > rpt_remind_offset Then Do; reptype = reptype - rpt_remind_offset; If ^remind_mode Then flag = '*'; End; Put Skip Edit('Repeat:',flag,repetition_text(reptype), 'Expires:',Termination_date_str) (Column(15),A,Column(24),2(A),Column(57),A,Column(66),A); If messf.number_of_users = 0 Then Do; Call Write_request(1,line_count); Put Skip Edit('Users: *ALL') (Column(4),A); End; Else Call write_list(messf.users,'Users: ',line_count); Call Write_list (messf.terminals,'Terminals: ',line_count); If command = list_command Then Do; buffer = ' Text: ' || get_text(messf.timestamp,1); Call Write_request(1,line_count); Put Skip Edit(buffer) (A); End; End; Else Do; Put Edit('***Deleted***') (Column(57),A); End; End Display_header; helpproc: Procedure; Put Skip(2) List('The legal commands are: '); Put Skip(2) List(' A[dd] - Add a new message'); Put Skip List(' D[elete] - Delete message '); Put Skip List(' E[dit] n - Edit message '); Put Skip List(' Ex[it] - Exit program'); Put Skip List(' H[elp] - Display this list'); Put Skip List(' L[ist] [] - List all messages [created after ]'); Put Skip List(' S[how] - Display message '); Put Skip List(' St[at] - Display message and statistics'); Put Skip; End helpproc; Generate_index: Procedure; Declare retries Fixed Binary(31); /* Index generation should not be interrupted */ Call Start_Critical_Section; indextree = null; index_count = 0; /* Record locking ON unit for first(keyed) read */ On Error Begin; If Locked_record(retries,Oncode()) Then Goto retry1; Else Call Resignal(); End; /* Non-existent key ON unit for first(keyed) read */ On Key(message_file_input) Goto Exit; /* Read first header record */ Retries = 0; Retry1: Read File(message_file_input) Set(messfptr) Key(Byte(0)) Options(Index_number(1)); Revert Key(message_file_input); /* Record locking ON unit for sequential reads */ On Error Begin; If Locked_record(retries,Oncode()) Then Goto retry2; Else Call Resignal(); End; /* Read the rest of the headers */ Do While(messf.sequence_no = 0); If messf.repetition_type ^= rpt_marked_for_delete Then If remind_mode Then Do; If abs(messf.repetition_type) > rpt_remind_offset & messf.number_of_users = 1 Then If messf.users(1) = username Then Call Add_to_index(indextree,messf.timestamp,index_count); End; Else Call Add_to_Index(indextree,messf.timestamp,index_count); Retries = 0; Retry2: Read File(message_file_input) Set(messfptr); End; Exit: index_generated = '1'b; Close File(message_file_input); Open File(message_file_input) Title(message_filename); Call End_Critical_Section; Return; End Generate_Index; Get_text: Procedure (timestamp,sequence_no) Returns(Character(*)); Declare timestamp Bit(64) Aligned, sequence_no Fixed Binary(7); Declare retries Fixed Binary(31); key_record.timestamp = timestamp; key_record.sequence_no = sequence_no; On Key(message_file_input) Goto text_gone; On Error Begin; If Locked_record(retries,oncode()) Then Goto retry; Else Call Resignal(); End; retries = 0; retry: Read File(message_file_input) Set(textfptr) Key(messkey); Revert Error; Return(textf.text); text_gone: Return(Byte(26)); End Get_text; End NoticeUtl;