use fmt;
use log;
use strings;
use os::exec;
use os;
use time;
use io;
use unix::signal;
use unix;
use fmt;
use bufio;
use encoding::json;
use errors;
export fn state_forward(
state: *state_machine,
act: action
) void = {
sync_back_wait_child(state, false);
match (act) {
case let act: action_context_changed =>
return state_forward_context_changed(state, act);
case let act: action_event_received =>
state_forward_event_received(state, act);
case let act: action =>
fmt::fatal("Not supported yet");
};
};
fn state_forward_event_received(
state: *state_machine,
act: action_event_received
) void = {
let target_event = act.event_name;
let matched_event = false;
for (let i = 0z; i < len(state.available_transitions); i += 1) {
let event_transition = match(state.available_transitions[i]) {
case let event_transition: transition_event =>
yield event_transition;
case transition =>
continue;
};
if (event_transition.event_name == target_event) {
if (sync_back_wait_child(state, true)) {
return state_forward_event_received(state, act);
};
matched_event = true;
state.available_transitions = event_transition.transitions;
break;
};
};
if (!matched_event) {
return;
};
state_forward_available_transitions(state);
trigger_delay_transition(state);
};
fn state_forward_context_changed(
state: *state_machine,
act: action_context_changed
) void = {
state_update_context(state, act.context_elements);
state_forward_available_transitions(state);
trigger_delay_transition(state);
};
export fn state_forward_available_transitions(state: *state_machine) void = {
for (let i = 0z; i < len(state.available_transitions); i += 1) {
match(state.available_transitions[i]) {
case let context_transition: transition_context =>
if (context_elements_match_all(
state.context_elements,
context_transition.context_elements
)) {
if (sync_back_wait_child(state, true)) {
return state_forward_available_transitions(state);
};
state.available_transitions = context_transition.transitions;
return state_forward_available_transitions(state);
};
case let exec_transition: transition_exec =>
if (run_transition_exec(exec_transition)) {
state.available_transitions = exec_transition.transitions;
return state_forward_available_transitions(state);
};
case transition =>
continue;
};
};
if (state_restart(state)) {
return state_forward_available_transitions(state);
};
};
export fn trigger_delay_transition(state: *state_machine) void = {
match (find_to_wait_transition_delay(state.available_transitions)) {
case let delay_transition: transition_delay =>
run_delay_transition(state, delay_transition);
case void =>
return;
};
};
fn find_to_wait_transition_delay(transitions: []transition) (void | transition_delay) = {
for (let i = 0z; i < len(transitions); i += 1) {
match(transitions[i]) {
case let delay_transition: transition_delay =>
return delay_transition;
case transition =>
continue;
};
};
};
fn run_delay_transition(state: *state_machine, transition: transition_delay) void = {
let wait_pipes = state.wait_pipes as (io::file, io::file);
match (exec::fork()) {
case let err: exec::error =>
fmt::fatal("delay scheduling failed", exec::strerror(err));
case let child_pid: int =>
state.wait_pid = child_pid;
case void =>
signal::unblock(signal::SIGINT, signal::SIGTERM);
time::sleep(transition.delay_duration);
fmt::fprintln(wait_pipes.1, "WOKEUP")!;
state.available_transitions = transition.transitions;
state_forward_available_transitions(state);
write_transitions(wait_pipes.1, state.available_transitions);
fmt::fprintln(wait_pipes.1)!;
os::exit(0);
};
};
fn state_restart(state: *state_machine) bool = {
if (len(state.available_transitions) == 0
&& len(state.all_transitions) != 0
) {
state.available_transitions = state.all_transitions;
return true;
};
return false;
};
// kill wait forked process and get back it results
export fn sync_back_wait_child(state: *state_machine, abort_it: bool) bool = {
if (state.wait_pid == 0) {
return false;
};
let wait_pipes = state.wait_pipes as (io::file, io::file);
for (true) {
match (bufio::scanline(wait_pipes.0)) {
case let buf: []u8 =>
defer free(buf);
const string = strings::fromutf8(buf);
if (string == "WOKEUP") {
break;
};
case errors::again =>
// it never woke up
if (abort_it) {
exec::kill(state.wait_pid)!;
exec::wait(&state.wait_pid)!;
state.wait_pid = 0;
};
return false;
case let err: io::error =>
fmt::fatal(io::strerror(err));
};
};
for (true) {
match(load_transitions(wait_pipes.0)) {
case let transitions: []transition =>
state.available_transitions = transitions;
break;
case errors::again =>
time::sleep(10000000);
case let err: json::error =>
fmt::fatal("Failed to parse the json:", json::strerror(err));
case let err: error =>
fmt::fatal("Failed to parse the json:", strerror(err));
};
};
exec::wait(&state.wait_pid)!;
state.wait_pid = 0;
return true;
};
fn run_transition_exec(transition: transition_exec) bool = {
let program = transition.command[0];
let args = transition.command[1..];
let cmd = match (exec::cmd(program, args...)) {
case let cmd: exec::command =>
yield cmd;
case let err: exec::error =>
fmt::fatal(exec::strerror(err));
};
let proc = match(exec::start(&cmd)) {
case let proc: exec::process =>
yield proc;
case let err: exec::error =>
fmt::fatal(exec::strerror(err));
};
let status = match(exec::wait(&proc)) {
case let status: exec::status =>
yield status;
case let err: exec::error =>
fmt::fatal(exec::strerror(err));
};
return status.status == 0;
};