?? LEFT := 1, RIGHT := 110 ??
?? FMT (FORMAT := ON, INDENT := 2) ??
?? SET (LIST := ON, LISTCTS := OFF) ??
?? NEWTITLE := 'Batch I/O Station Operator Utility: Network Asynchronous Output Procesor' ??
MODULE nfm$sou_asynchronous_task;

{
{ PURPOSE:
{   This module runs as an asynchronous task for the Operate_Station
{   command utility.  Its purpose is to receive output from CDCNET and
{   SCFS, and manage display output to the station operator.
{

?? NEWTITLE := '  Global declarations' ??
?? PUSH (LISTEXT := ON) ??
*copyc clt$path_display_chunks
*copyc clc$standard_file_names
*copyc nfd$sou_intertask_communication
*copyc nfe$sou_condition_codes
*copyc nft$device_control_resp_codes
*copyc nft$message_kind
*copyc nft$message_sequence
*copyc nft$parameter_value_length
*copyc nft$select_file_response
*copyc nft$sou_message_parameter_types
*copyc oss$job_paged_literal
*copyc ost$i_wait
*copyc ost$status
*copyc pmt$program_parameters
?? POP ??
*copyc clp$build_standard_title
*copyc clp$close_display
*copyc clp$convert_integer_to_string
*copyc clp$new_display_line
*copyc clp$open_display
*copyc clp$put_display
*copyc clp$put_partial_display
*copyc clp$reset_for_next_display_page
*copyc clp$trimmed_string_size
*copyc fsp$close_file
*copyc fsp$open_file
*copyc nap$se_receive_data
*copyc nfp$begin_asynchronous_task
*copyc nfp$end_async_communication
*copyc nfp$get_async_task_message
*copyc nfp$get_parameter_value_length
*copyc nfp$put_async_task_message
*copyc osp$disestablish_cond_handler
*copyc osp$establish_block_exit_hndlr
*copyc osp$generate_message
*copyc osp$i_await_activity_completion
*copyc osp$set_status_abnormal

?? EJECT ??
*copy clv$display_variables

?? TITLE := '  Global variables', EJECT ??

  TYPE
    nft$queued_operator_message = RECORD
      link: ^nft$queued_operator_message,
      station: ost$name,
      device: ost$name,
      text: STRING (* <= nfc$maximum_message_length),
    RECEND,

    nft$parameter_kind = 0 .. 07f(16),

    nft$parameter_type = PACKED RECORD
      length_indicated: BOOLEAN,
      param: nft$parameter_kind,
    RECEND;

  VAR
    message: ^nft$message_sequence,
    message_length: INTEGER,
    msg_byte_count: INTEGER,
    message_type: ^nft$message_kind;

  VAR
    operator_message_list: [STATIC] ^nft$queued_operator_message := NIL;

  VAR
    hold_display: BOOLEAN,
    network_file_open: BOOLEAN,
    network_file: ost$name,
    network_file_id: amt$file_identifier,
    parent_task_id: pmt$task_id,
    local_queue_id: pmt$queue_connection,
    transfer_count: 0 .. nfc$max_transfer_size;

  VAR
    peer_operations: [READ] ARRAY [nat$se_peer_operation_kind] OF STRING (peer_operation_size) :=
        [ {nac$se_send_data} '',
          {nac$se_interrupt} 'Interrupt',
          {nac$se_synchronize} 'Synchronize',
          {nac$se_synchronize_confirm} 'Synchronize Confirm'  ];

    CONST
      peer_operation_size = 19;

?? TITLE := '  [XDCL] nfp$sou_asynchronous_task', EJECT ??

  PROCEDURE [XDCL] nfp$sou_asynchronous_task (parameters: pmt$program_parameters;
    VAR status: ost$status);

?? NEWTITLE := '  abort_handler', EJECT ??

  PROCEDURE abort_handler (condition: pmt$condition;
        condition_descriptor: ^pmt$condition_information;
        save_area: ^ost$stack_frame_save_area;
    VAR handler_status: ost$status);

      end_async_task;
      handler_status.normal := TRUE;

    PROCEND abort_handler;

?? TITLE := '  end_async_task', EJECT ??

{
{   The purpose of this procedure is to clean up intertask
{   communication with the originating task, and end processing.
{

  PROCEDURE end_async_task;

    VAR
      req: nft$sou_intertask_request,
      end_status: ost$status;

    IF network_file_open THEN
      end_status.normal := TRUE;
      fsp$close_file (network_file_id, end_status);
      network_file_open := FALSE;
    IFEND;

    end_status.normal := TRUE;
    nfp$end_async_communication (FALSE, end_status);
    task_ended := TRUE;

    IF NOT status.normal THEN
      end_status.normal := TRUE;
      osp$generate_message (status, end_status);
    IFEND;

  PROCEND end_async_task;

?? OLDTITLE, EJECT ??

    VAR
      wait_list: ^ost$i_wait_list,
      ready_index: INTEGER,
      intertask_request: nft$sou_intertask_request,
      intertask_response: nft$sou_intertask_response,
      hold_task: BOOLEAN,
      task_ended: BOOLEAN,
      local_status: ost$status;


    network_file_open := FALSE;
    task_ended := FALSE;
    hold_task := FALSE;

    nfp$begin_asynchronous_task (parameters, parent_task_id, local_queue_id, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    ALLOCATE message: [[REP nfc$maximum_message_length OF CELL]];
    PUSH wait_list: [1 .. 2];

    osp$establish_block_exit_hndlr (^abort_handler);

 /async_process/
    BEGIN
      wait_list^[1].activity := pmc$i_await_local_queue_message;
      wait_list^[1].qid := local_queue_id;
      wait_list^[2].activity := osc$i_null_activity;
      osp$i_await_activity_completion (wait_list^, ready_index, status);
      IF NOT status.normal THEN
        EXIT /async_process/;
      IFEND;

      nfp$get_async_task_message (parent_task_id, ^intertask_request, #SIZE (intertask_request), 0,
            transfer_count, status);
      IF status.normal AND ((intertask_request.request <> nfc$sou_start_task) OR (transfer_count = 0)) THEN
        osp$set_status_abnormal (nfc$status_id, nfe$sou_invalid_intertask_req, '', status);
      IFEND;
      IF NOT status.normal THEN
        EXIT /async_process/;
      IFEND;

      network_file := intertask_request.file;
      fsp$open_file (network_file, amc$record, NIL, NIL, NIL, NIL, NIL, network_file_id, status);
      IF NOT status.normal THEN
        EXIT /async_process/;
      IFEND;

      intertask_response.response := nfc$sou_complete;
      nfp$put_async_task_message (parent_task_id, ^intertask_response, #SIZE (intertask_response), status);
      IF NOT status.normal THEN
        EXIT /async_process/;
      IFEND;

   /activity_loop/
      WHILE TRUE DO
        IF NOT hold_task THEN
          wait_list^[2].activity := nac$i_await_data_available;
          wait_list^[2].file_identifier := network_file_id;
        ELSE
          wait_list^[2].activity := osc$i_null_activity;
        IFEND;
        osp$i_await_activity_completion (wait_list^, ready_index, status);
        IF NOT status.normal THEN
          EXIT /async_process/;
        IFEND;

        IF ready_index = 1 THEN     {intertask message received}
          nfp$get_async_task_message (parent_task_id, ^intertask_request, #SIZE (intertask_request), 0,
                transfer_count, status);
          IF NOT status.normal THEN
            EXIT /async_process/;
          IFEND;
          IF transfer_count = 0  THEN
            CYCLE /activity_loop/;
          IFEND;

          IF intertask_request.request = nfc$sou_hold THEN
            hold_task := TRUE;
          ELSEIF intertask_request.request = nfc$sou_resume THEN
            hold_task := FALSE;
          ELSEIF intertask_request.request = nfc$sou_end_task THEN
            local_status.normal := TRUE;
            fsp$close_file (network_file_id, local_status);
            network_file_open := FALSE;
          ELSE {invalid request}
            osp$set_status_abnormal (nfc$status_id, nfe$sou_invalid_intertask_req, '', status);
            EXIT /async_process/;
          IFEND;

          intertask_response.response := nfc$sou_complete;
          nfp$put_async_task_message (parent_task_id, ^intertask_response,
                #SIZE (intertask_response), status);
          IF NOT status.normal THEN
            EXIT /async_process/;
          IFEND;

          IF intertask_request.request = nfc$sou_end_task THEN
            EXIT /async_process/;
          IFEND;

        ELSE     {data available on connection}
          IF NOT hold_task THEN
            get_unsolicited_output (status);
            IF NOT status.normal THEN
              EXIT /async_process/;
            IFEND;

            WHILE operator_message_list <> NIL DO
              display_operator_message (status);
              IF NOT status.normal THEN
                EXIT /async_process/;
              IFEND;
            WHILEND;
          IFEND;
        IFEND;

      WHILEND /activity_loop/;

    END /async_process/;

    end_async_task;
    osp$disestablish_cond_handler;

  PROCEND nfp$sou_asynchronous_task;

?? TITLE := '  get_unsolicited_output', EJECT ??

{
{   The purpose of this procedure is to receive a message from
{   SCFS/VE via CDCNET.
{

  PROCEDURE get_unsolicited_output (VAR status: ost$status);

    VAR
      message_received: BOOLEAN;


    get_network_message (message_received, status);
    IF NOT (status.normal AND message_received) THEN
      RETURN;
    IFEND;

    get_message_type;
    IF message_type^ = nfc$operator_message THEN
      queue_operator_message;
    IFEND;

  PROCEND get_unsolicited_output;

?? TITLE := '  get_network_message', EJECT ??

{
{   The purpose of this procedure is to get a the next message
{   from SCFS/VE via CDCNET.
{

  PROCEDURE get_network_message (VAR message_received: BOOLEAN;
    VAR status: ost$status);

    VAR
      data_area: ^nat$data_fragments,
      peer_operation: nat$se_peer_operation,
      activity_status: ost$activity_status;


    message_received := FALSE;

    PUSH data_area: [1 .. 1];
    data_area^ [1].address := message;
    data_area^ [1].length := #SIZE (message^);
    nap$se_receive_data (network_file_id, data_area^, osc$wait, peer_operation, activity_status, status);
    IF status.normal AND NOT activity_status.status.normal THEN
      status := activity_status.status;
    IFEND;
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    IF peer_operation.kind = nac$se_send_data THEN
      message_length := peer_operation.data_length;
      message_received := TRUE;
    ELSE
      osp$set_status_abnormal (nfc$status_id, nfe$sou_unexpected_network_req,
            peer_operations [peer_operation.kind], status);
    IFEND;

  PROCEND get_network_message;

?? TITLE := '  queue_operator_message', EJECT ??

{
{   The purpose of this procedure is to queue an operator
{   message from SCFS in a link list until it can be
{   displayed.
{

  PROCEDURE queue_operator_message;

*copy nft$operator_message

    VAR
      text_string: ^STRING (* <= nfc$maximum_message_length),
      name_string: ^STRING (* <= osc$max_name_size),
      device_name: ost$name,
      io_station_name: ost$name,
      parameter: ^nft$operator_message_parameter,
      value_length: INTEGER,
      queued_msg_pp: ^^nft$queued_operator_message;


    io_station_name := ' ';
    device_name := ' ';
    text_string := NIL;

    get_parameter_type (parameter);

    WHILE (parameter <> NIL) AND (parameter^.param <> nfc$null_parameter) AND (msg_byte_count > 0) DO
      get_parameter_length (parameter^.length_indicated, value_length);

      CASE parameter^.param OF

      = nfc$io_station_name =
        NEXT name_string: [value_length] IN message;
        io_station_name := name_string^;

      = nfc$device_name =
        NEXT name_string: [value_length] IN message;
        device_name := name_string^;

      = nfc$text =
        NEXT text_string: [value_length] IN message;

      ELSE

      CASEND;

      get_parameter_type (parameter);
    WHILEND;

    IF text_string = NIL THEN
    RETURN;
    IFEND;

    queued_msg_pp := ^operator_message_list;
    WHILE queued_msg_pp^ <> NIL DO
      queued_msg_pp := ^queued_msg_pp^^.link;
    WHILEND;
    ALLOCATE queued_msg_pp^: [STRLENGTH (text_string^)];
    queued_msg_pp^^.link := NIL;
    queued_msg_pp^^.station := io_station_name;
    queued_msg_pp^^.device := device_name;
    queued_msg_pp^^.text := text_string^;
    IF operator_message_list = NIL THEN
      operator_message_list := queued_msg_pp^;
    IFEND;

  PROCEND queue_operator_message;

?? TITLE := '  display_operator_message', EJECT ??

{
{   The purpose of this procedure is to display an unsolicited
{   message to the station operator from SCFS/VE.
{

  PROCEDURE display_operator_message (VAR status: ost$status);

*copy clv$display_variables
?? NEWTITLE := '    abort_handler', EJECT ??

    PROCEDURE abort_handler (condition: pmt$condition;
          condition_information: ^pmt$condition_information;
          save_area: ^ost$stack_frame_save_area;
      VAR handler_status: ost$status);

      close_display;

    PROCEND abort_handler;

?? TITLE := '    close_display', EJECT ??

    PROCEDURE close_display;

      VAR
        ignore_status: ost$status;

      IF output_open THEN
        clp$close_display (display_control, ignore_status);
        output_open := FALSE;
      IFEND;

    PROCEND close_display;

?? TITLE := '    put_display_line', EJECT ??

    PROCEDURE put_display_line (label: STRING (*);
          value: STRING (*));

      VAR
        label_str: STRING (label_size);

      label_str := label;
      IF label <> ' ' THEN
        label_str (label_size - 2, 3) := ' : ';
      IFEND;

      clp$put_partial_display (display_control, label_str, clc$no_trim, amc$start, status);
      IF NOT status.normal THEN
        EXIT display_operator_message;
      IFEND;
      clp$put_partial_display (display_control, value, clc$trim, amc$terminate, status);
      IF NOT status.normal THEN
        EXIT display_operator_message;
      IFEND;

    PROCEND put_display_line;


?? OLDTITLE, EJECT ??

    CONST
      device_label = 'Device   : ',
      device_label_size = 11,
      unit_separator = $CHAR (01f(16)),
      label_size = 31;

    VAR
      output_file: [READ] clt$file := [clc$standard_output],
      q_msg: ^nft$queued_operator_message,
      labl: ost$name,
      msg_size: 0 .. osc$max_string_size,
      line_size: 0 .. osc$max_string_size,
      i : 1 .. osc$max_string_size,
      display_control: clt$display_control,
      output_open: BOOLEAN,
      start_pos: 1..80,
      str_length: 0 .. osc$max_name_size,
      text: string (80),
      text_length: 0..80;


    IF operator_message_list <> NIL THEN

  /display/
    BEGIN
      output_open := FALSE;
      osp$establish_block_exit_hndlr (^abort_handler);
      clp$open_display (output_file, NIL, display_control, status);
      IF NOT status.normal THEN
        osp$disestablish_cond_handler;
        RETURN;
      IFEND;
      output_open := TRUE;
      IF display_control.page_width < clc$narrow_page_width THEN
        clv$page_width := clc$narrow_page_width;
      ELSEIF display_control.page_width > clc$wide_page_width THEN
        clv$page_width := clc$wide_page_width;
      ELSE
        clv$page_width := display_control.page_width;
      IFEND;

      q_msg := operator_message_list;

{  Format the line containing the device and the station name. }

      text (1, device_label_size) := device_label;
      text_length := device_label_size;

      start_pos := text_length + 1;
      str_length := clp$trimmed_string_size (q_msg^.device);
      text (start_pos, str_length) := q_msg^.device (1, str_length);
      text_length := text_length + str_length;

      start_pos := text_length + 1;
      text (start_pos, 4) := ' at ';
      text_length := text_length + 4;

      start_pos := text_length + 1;
      str_length := clp$trimmed_string_size (q_msg^.station);
      text (start_pos, str_length) := q_msg^.station (1, str_length);
      text_length := text_length + str_length;

      clp$put_display (display_control, text (1, text_length), clc$trim, status);
      IF NOT status.normal THEN
        EXIT /display/;
      IFEND;

      line_size := clv$page_width - 1;
      msg_size := stringsize (q_msg^.text);

      WHILE msg_size > 0 DO
     /scan_msg/
        FOR i := 1 TO msg_size DO
          IF i >= line_size THEN
            EXIT /scan_msg/;
          ELSEIF q_msg^.text (i) = unit_separator THEN
            q_msg^.text (i) := ' ';
            EXIT /scan_msg/;
          IFEND;
        FOREND /scan_msg/;

        clp$put_display (display_control, q_msg^.text (1, i), clc$trim, status);
        IF NOT status.normal THEN
          EXIT /display/
        IFEND;
        q_msg^.text := q_msg^.text (i+1, *);
        msg_size :=msg_size - i;
      WHILEND;

      operator_message_list := q_msg^.link;
      FREE q_msg;

      put_display_line (' ', ' ');
    END /display/;

      close_display;
      osp$disestablish_cond_handler;
    IFEND;

  PROCEND display_operator_message;

?? TITLE := '  get_parameter_length', EJECT ??

{
{   This procedure obtains the length of the next parameter value
{   in the message buffer from SCFS.  The length field, if
{   indicated, should be the next element in the message sequence.
{

  PROCEDURE get_parameter_length (length_indicated: BOOLEAN;
    VAR length: INTEGER);

    VAR
      param_length: ^nft$parameter_value_length,
      ignore_status: ost$status;


    IF length_indicated THEN
      nfp$get_parameter_value_length (message, msg_byte_count, length, ignore_status);
    ELSE
      length := 1;
    IFEND;
    msg_byte_count := msg_byte_count - length;

  PROCEND get_parameter_length;

?? TITLE := '  [INLINE] get_message_type', EJECT ??

{
{   Inline code to get the message type from the beginning of a
{   message received from SCFS.
{

  PROCEDURE [INLINE] get_message_type;


    RESET message;
    NEXT message_type IN message;
    msg_byte_count := message_length - 1;

  PROCEND get_message_type;

?? TITLE := '  [INLINE] get_parameter_type', EJECT ??

{
{   Inline code to get the next parameter type from a
{   message received from SCFS.
{

  PROCEDURE [INLINE] get_parameter_type (VAR param: ^CELL);


    NEXT param IN message;
    msg_byte_count := msg_byte_count - 1;

  PROCEND get_parameter_type;

?? TITLE := '  stringsize', EJECT ??

{
{   Function to determine the length of a string, excluding trailing blanks.
{

  FUNCTION stringsize (str: string ( * )): integer;

    VAR
      str_length: ost$string_size;

    str_length := STRLENGTH (str);
    WHILE (str_length > 0) AND (str (str_length) = ' ') DO
      str_length := str_length - 1;
    WHILEND;
    stringsize := str_length;

  FUNCEND stringsize;


MODEND nfm$sou_asynchronous_task;


