Skip to content
Snippets Groups Projects
Commit 2f218d34 authored by Roman Lacko's avatar Roman Lacko
Browse files

Refactor proper module wrapper into Breeze::Instances

parent a2687926
No related branches found
No related tags found
No related merge requests found
...@@ -11,7 +11,6 @@ no warnings qw(experimental::signatures); ...@@ -11,7 +11,6 @@ no warnings qw(experimental::signatures);
use Carp; use Carp;
use Data::Dumper; use Data::Dumper;
use JSON::XS; use JSON::XS;
use Module::Load;
use Time::HiRes; use Time::HiRes;
use Time::Out qw(timeout); use Time::Out qw(timeout);
use Try::Tiny; use Try::Tiny;
...@@ -19,6 +18,7 @@ use Try::Tiny; ...@@ -19,6 +18,7 @@ use Try::Tiny;
# Wild Breeze modules # Wild Breeze modules
use Breeze::Cache; use Breeze::Cache;
use Breeze::Counter; use Breeze::Counter;
use Breeze::Instances;
use Breeze::Logger; use Breeze::Logger;
use Breeze::Logger::File; use Breeze::Logger::File;
use Breeze::Logger::StdErr; use Breeze::Logger::StdErr;
...@@ -82,33 +82,6 @@ sub u8($self, $what) { ...@@ -82,33 +82,6 @@ sub u8($self, $what) {
return $t; return $t;
} }
# timer manipulation
sub get_or_set_timer($self, $key, $timer, $ticks) {
my $timers = $self->mod($key)->{tmrs};
# nothing to do if no timer set and ticks is undefined
return if !defined $timers->{$timer} && !defined $ticks;
# create timer if there is none
if (!defined $timers->{$timer}) {
# optimization: do not store counter for only one cycle,
# use local variable instead
if ($ticks == 0) {
my $temp = 0;
return \$temp;
}
$timers->{$timer} = Breeze::Counter->new(current => $ticks);
}
# return a reference to the timer
return \$timers->{$timer};
}
sub delete_timer($self, $key, $timer) {
delete $self->mod($key)->{tmrs}->{$timer};
}
# initializers # initializers
sub resolve_defaults($self) { sub resolve_defaults($self) {
...@@ -145,95 +118,53 @@ sub init_modules($self) { ...@@ -145,95 +118,53 @@ sub init_modules($self) {
if (ref $modcfg eq "") { if (ref $modcfg eq "") {
# it might be a separator # it might be a separator
if ($modcfg eq "separator") { if ($modcfg eq "separator") {
push $self->{mods}->@*, { separator => undef }; push $self->{mods}->@*, Breeze::Separator->new();
# otherwise it's an error # otherwise it's an error
} else { } else {
$self->log->fatal("scalar '$modcfg' found instead of module description"); $self->log->fatal("scalar '$modcfg' found instead of module description");
} }
} else { } else {
# check for required parameters my ($name, $driver) = ($modcfg->{-name} // "<undef>", $modcfg->{-driver} // "<undef>");
foreach my $key (qw(-name -driver)) {
$self->log->fatal("missing '$key' in module description")
unless defined $modcfg->{$key};
}
# check that only known keys begin with '-'
foreach my $key (grep { $_ =~ m/^-/ } (keys $modcfg->%*)) {
$self->log->warn("unknown '$key' in module description")
unless $key =~ m/^-(name|refresh|driver|timeout)$/;
}
my ($moddrv, $modname) = $modcfg->@{qw(-driver -name)};
$self->log->info("trying to load '$moddrv' as '$modname'");
# initialize the module instance
my $modlog = $self->log->clone(category => "$modname($moddrv)");
my $module = try { my $inst = try {
load $moddrv; if (!defined $modcfg->{-timeout}) {
$modcfg->{-timeout} = $self->cfg->{timeout};
# pass only the '-name' parameter and those that } elsif ($modcfg->{-timeout} <= 0) {
# do not begin with '-' $self->log->fatal("invalid timeout for '$name'($driver): $modcfg->{-timeout}");
my @keys = grep { $_ !~ m/^-/ } (keys $modcfg->%*); }
my %args = $modcfg->%{-name, @keys};
$args{-log} = $modlog;
$args{-refresh} = $modcfg->{-refresh} // 0;
$args{-theme} = $self->theme;
# create instance Breeze::Module->new($modcfg, {
return $moddrv->new(%args); log => $self->log,
theme => $self->theme,
timeouts => $self->cfg->{timeouts},
failures => $self->cfg->{failures},
});
} catch { } catch {
chomp $_; $self->log->error("failed to instantiate '$name'($driver)");
$self->log->error("failed to initialize '$modname'");
$self->log->error($_); $self->log->error($_);
return;
}; };
# module failed to initialize? my $error_msg = $_;
if (!defined $module && $moddrv ne "Leaf::Fail") { # if module failed
# replace module description with dummy text and redo if (!defined $inst) {
$self->log->info("replacing '$moddrv' with failed placeholder"); # if it was Leaf::Fail, there is nothing else to do
if ($driver eq "Leaf::Fail") {
$self->log->fatal("Leaf::Fail failed, something's gone terribly wrong");
}
$self->log->info("replacing with failed placeholder");
$modcfg = { $modcfg = {
-name => $modname, -name => "__failed_" . $self->{mods}->$#*,
-driver => "Leaf::Fail", -driver => "Leaf::Fail",
text => "'$modname' ($moddrv)", text => "'$name'($driver)",
}; };
redo; redo;
} elsif (!defined $module) {
# Leaf::Fail failed, well, fuck
$self->log->fatal("Leaf::Fail failed");
}
my %counterconf = (from => 0, step => 1, cycle => 0);
# if module uses custom timeout, notify log
if (defined $modcfg->{-timeout} && $modcfg->{-timeout} >= 1) {
$self->log->info("$modname has custom timeout '$modcfg->{-timeout}'");
} elsif (defined $modcfg->{-timeout}) {
$self->log->info("refusing to use timeout '$modcfg->{-timeout}' as it is invalid");
delete $modcfg->{-timeout};
} }
# got here so far, save all push $self->{mods}->@*, $inst;
my $entry = { $self->{mods_by_name}->{$name} = $inst;
conf => $modcfg,
mod => $module,
log => $modlog,
tmrs => {
timeout => Breeze::Counter->new(%counterconf,
current => $self->cfg->{timeouts},
to => $self->cfg->{timeouts},
),
fail => Breeze::Counter->new(%counterconf,
current => $self->cfg->{failures},
to => $self->cfg->{failures},
),
},
};
push $self->{mods}->@*, $entry;
$self->{mods_by_name}->{$modname} = $entry;
} }
} }
} }
...@@ -253,25 +184,21 @@ sub init_events($self) { ...@@ -253,25 +184,21 @@ sub init_events($self) {
]; ];
} }
sub fail_module($self, $entry, $timer) { sub fail_module($self, $module, $timer) {
my $tmr = \$entry->{tmrs}->{$timer}; my $tmr = $module->get_timer($timer);
if (!($$tmr--)) { if (!($$tmr--)) {
$self->log->error("module depleted timer '$timer' for the last time, disabling"); $self->log->error("module depleted timer '$timer' for the last time, disabling");
$entry->{mod} = Leaf::Fail->new( $module->fail;
-entry => $entry->{conf}->{-name}, return $module->invoke;
-log => $entry->{log},
text => "$entry->{conf}->{-name}($entry->{conf}->{-driver})",
);
return $entry->{mod}->invoke;
} else { } else {
# temporarily disable module # temporarily disable module
return { return {
text => "$entry->{conf}->{-name}($entry->{conf}->{-driver})", text => $module->name_canon,
blink => $self->cfg->{cooldown}, blink => $self->cfg->{cooldown},
cache => $self->cfg->{cooldown}, cache => $self->cfg->{cooldown},
background => "b58900", background => "%{core.fail.bg,orange,yellow",
color => "002b36", color => "%{core.fail.fg,fg,white}",
icon => ($timer eq "fail" ? "" : ""), icon => ($timer eq "fail" ? "" : ""),
}; };
} }
...@@ -280,49 +207,38 @@ sub fail_module($self, $entry, $timer) { ...@@ -280,49 +207,38 @@ sub fail_module($self, $entry, $timer) {
sub run($self) { sub run($self) {
my $ret = []; my $ret = [];
foreach my $entry ($self->mods->@*) { foreach my $module ($self->mods->@*) {
# separators will be handled in postprocessing # separators will be handled in postprocessing
if (exists $entry->{separator}) { if ($module->is_separator) {
push @$ret, { %$entry }; push @$ret, { separator => 1 };
next; next;
} }
# try to get cached output first # try to get cached output first
my $data = $self->cache->get($entry->{conf}->{-name}); my $data = $self->cache->get($module->name);
if (!defined $data) { if (!defined $data) {
$data = try { # run module
my $to = $entry->{conf}->{-timeout} // $self->cfg->{timeout}; $data = $module->run("invoke");
my $tmp = timeout($to => sub {
return $entry->{mod}->invoke; if ($data->{fatal}) {
}); $data = $self->fail_module($module, "fail");
} elsif ($data->{timeout}) {
if ($@) { $data = $self->fail_module($module, "timeout");
$self->log->error("module '$entry->{conf}->{-name}' timeouted"); } elsif ($data->{ok}) {
return $self->fail_module($entry, "timeout"); $data = $data->{content};
} elsif (!defined $tmp || ref $tmp ne "HASH") { } else {
$self->log->fatal("module '$entry->{conf}->{-name}' returned ", (ref($tmp) || "undef")); $self->log->fatal("module wrapper did not return 'ok'");
} }
return $tmp;
} catch {
chomp $_;
$self->log->error("error in '$entry->{conf}->{-name}' ($entry->{conf}->{-driver})");
$self->log->error($_);
return $self->fail_module($entry, "fail");
};
} else { } else {
# ignore cached 'blink', 'invert', 'reset_blink' and 'reset_invert' # ignore cached 'blink', 'invert', 'reset_blink' and 'reset_invert'
delete $data->@{qw(blink invert cache reset_blink reset_invert reset_all)}; delete $data->@{qw(blink invert cache reset_blink reset_invert reset_all)};
} }
# set entry and instance if (($module->refresh // 0) >= 1) {
$data->@{qw(entry instance)} = $entry->{conf}->@{qw(-name -name)}; $self->cache->set($module->name, $data, $module->refresh);
if (($entry->{conf}->{-refresh} // 0) >= 1) {
$self->cache->set($entry->{conf}->{-name}, $data, $entry->{conf}->{-refresh});
} elsif (defined $data->{cache} && $data->{cache} >= 0) { } elsif (defined $data->{cache} && $data->{cache} >= 0) {
$self->cache->set($entry->{conf}->{-name}, $data, $data->{cache}); $self->cache->set($module->name, $data, $data->{cache});
} }
push @$ret, $data; push @$ret, $data;
...@@ -373,10 +289,11 @@ sub post_process_inversion($self, $ret) { ...@@ -373,10 +289,11 @@ sub post_process_inversion($self, $ret) {
# separators are not supposed to blink # separators are not supposed to blink
next if exists $seg->{separator}; next if exists $seg->{separator};
$self->delete_timer($seg->{entry}, "invert") my $module = $self->mod($seg->{name});
$module->delete_timer("invert")
if $seg->{reset_invert} || $seg->{reset_all}; if $seg->{reset_invert} || $seg->{reset_all};
my $timer = $self->get_or_set_timer($seg->{entry}, "invert", $seg->{invert}); my $timer = $module->get_or_set_timer("invert", $seg->{invert});
next if !defined $timer; next if !defined $timer;
# advance timer, use global if evaluates to inf # advance timer, use global if evaluates to inf
...@@ -387,7 +304,7 @@ sub post_process_inversion($self, $ret) { ...@@ -387,7 +304,7 @@ sub post_process_inversion($self, $ret) {
$seg->{invert} = 1 if $tick >= 0; $seg->{invert} = 1 if $tick >= 0;
# remove timer if expired # remove timer if expired
$self->delete_timer($seg->{entry}, "invert") unless $$timer--; $module->delete_timer("invert") unless $$timer--;
} }
} }
...@@ -396,10 +313,11 @@ sub post_process_blinking($self, $ret) { ...@@ -396,10 +313,11 @@ sub post_process_blinking($self, $ret) {
# separators are not supposed to blink # separators are not supposed to blink
next if exists $seg->{separator}; next if exists $seg->{separator};
$self->delete_timer($seg->{entry}, "blink") my $module = $self->mod($seg->{name});
$module->delete_timer("blink")
if $seg->{reset_blink} || $seg->{reset_all}; if $seg->{reset_blink} || $seg->{reset_all};
my $timer = $self->get_or_set_timer($seg->{entry}, "blink", $seg->{blink}); my $timer = $module->get_or_set_timer("blink", $seg->{blink});
next if !defined $timer; next if !defined $timer;
# advance timer, use global if evaluates to inf # advance timer, use global if evaluates to inf
...@@ -410,7 +328,7 @@ sub post_process_blinking($self, $ret) { ...@@ -410,7 +328,7 @@ sub post_process_blinking($self, $ret) {
$seg->{invert} = ($seg->{invert} xor ($tick % 2 == 0)); $seg->{invert} = ($seg->{invert} xor ($tick % 2 == 0));
# remove timer if expired # remove timer if expired
$self->delete_timer($seg->{entry}, "blink") unless $$timer--; $module->delete_timer("blink") unless $$timer--;
} }
} }
...@@ -455,7 +373,7 @@ sub post_process_sep($self, $ret) { ...@@ -455,7 +373,7 @@ sub post_process_sep($self, $ret) {
$sep->{background} = $ret->[$ix - 1]->{background} // $default_bg; $sep->{background} = $ret->[$ix - 1]->{background} // $default_bg;
} }
$sep->{entry} = $sep->{instance} = "__separator_$counter"; $sep->{name} = $sep->{instance} = "__separator_$counter";
++$counter; ++$counter;
} }
} }
...@@ -473,7 +391,7 @@ sub post_process_attr($self, $ret) { ...@@ -473,7 +391,7 @@ sub post_process_attr($self, $ret) {
$seg->{full_text} =~ s/%utf8\{(.*?)\}/$self->u8($1)/ge; $seg->{full_text} =~ s/%utf8\{(.*?)\}/$self->u8($1)/ge;
# add padding if requested # add padding if requested
if ($self->cfg->{padding} && $seg->{entry} !~ m/^__separator_/) { if ($self->cfg->{padding} && $seg->{name} !~ m/^__separator_/) {
my $pad = " " x $self->cfg->{padding}; my $pad = " " x $self->cfg->{padding};
$seg->{full_text} =~ s/^(\S)/$pad$1/; $seg->{full_text} =~ s/^(\S)/$pad$1/;
$seg->{full_text} =~ s/(\S)$/$1$pad/; $seg->{full_text} =~ s/(\S)$/$1$pad/;
...@@ -516,24 +434,22 @@ sub event_button($self, $code) { ...@@ -516,24 +434,22 @@ sub event_button($self, $code) {
} }
sub process_event($self, $mod, $data) { sub process_event($self, $mod, $data) {
my $entry = $mod->{conf}->{-name};
# module might want to redraw after event # module might want to redraw after event
$self->cache->flush($entry) $self->cache->flush($mod->name)
if $data->{flush}; if $data->{flush};
# stop timers # stop timers
$self->delete_timer($entry, "blink") $mod->delete_timer("blink")
if $data->{reset_blink} || $data->{reset_all}; if $data->{reset_blink} || $data->{reset_all};
$self->delete_timer($entry, "invert") $mod->delete_timer("invert")
if $data->{reset_invert} || $data->{reset_all}; if $data->{reset_invert} || $data->{reset_all};
# reset timers on demand # reset timers on demand
$self->get_or_set_timer($entry, "blink", $data->{blink}) $mod->get_or_set_timer("blink", $data->{blink})
if defined $data->{blink}; if defined $data->{blink};
$self->get_or_set_timer($entry, "invert", $data->{invert}) $mod->get_or_set_timer("invert", $data->{invert})
if defined $data->{invert}; if defined $data->{invert};
} }
...@@ -549,46 +465,25 @@ sub event($self, $event) { ...@@ -549,46 +465,25 @@ sub event($self, $event) {
} }
my $button = $self->event_button($event->{button}); my $button = $self->event_button($event->{button});
my $data = try { if (!defined $button) {
if ($mod->{mod}->refresh_on_event) { $self->log->error("got unknown event '$event->{button}' for '$target'");
$self->cache->flush($target); return;
} }
my $to = $mod->{conf}->{-timeout} // $self->cfg->{timeout};
my $tmp = timeout($to => sub {
if (!defined $button) {
$self->log->error("got unknown event '$event->{button}' for '$target'");
return $mod->{mod}->on_event;
} else {
my $method = "on_$button";
return $mod->{mod}->$method;
}
});
if ($@) { $self->cache->flush($mod->name)
$self->log->error("event for '$target' timeouted"); if ($mod->refresh_on_event);
# flush cache and replace entry with failed placeholder my $data = $mod->run("on_$button");
$self->cache->flush($target);
my $ret = $self->fail_module($mod, "timeout");
$ret->{text} .= "[event]";
$self->cache->set($target, $ret, $self->cfg->{cooldown});
return $ret;
}
return $tmp; if ($data->{fatal}) {
} catch { $data = $self->fail_module($mod, "fail");
chomp $_; } elsif ($data->{timeout}) {
$self->log->error("event for '$target' failed"); $data = $self->fail_module($mod, "timeout");
$self->log->error($_); } elsif ($data->{ok}) {
$data = $data->{content};
# flush cache and replace entry with failed placeholder } else {
$self->cache->flush($target); $self->log->fatal("module wrapper did not return 'ok'");
my $ret = $self->fail_module($mod, "fail"); }
$ret->{text} .= "[event]";
$self->cache->set($target, $ret, $self->cfg->{cooldown});
return $ret;
};
if (defined $data and ref $data eq "HASH") { if (defined $data and ref $data eq "HASH") {
$self->process_event($mod, $data); $self->process_event($mod, $data);
......
use v5.26;
use utf8;
use strict;
use warnings;
use feature qw(signatures);
no warnings qw(experimental::signatures);
package Breeze::Instances;
# empty
#-------------------------------------------------------------------------------
package Breeze::Module;
use Breeze::Counter;
use Carp;
use Module::Load;
use Time::Out;
sub new($class, $def, $attrs) {
my $self = bless {}, $class;
# check for required parameters
foreach my $key (qw(-name -driver)) {
croak "missing '$key' in module description"
unless defined $def->{$key};
}
# check that only known keys begin with '-'
foreach my $key (grep { $_ =~ m/^-/ } (keys $def->%*)) {
croak "unknown control key '$key' in module instance"
unless $key =~ m/^-(name|driver|refresh|timeout)$/;
}
$self->{modcfg} = $def;
$self->{log} = $attrs->{log}->clone(category => $self->name_canon);
$self->initialize($attrs->{theme});
$self->init_timers($attrs);
return $self;
}
sub initialize($self, $theme) {
load $self->driver;
# pass only the '-name' parameter and those that do not begin with '-'
my @keys = grep { $_ !~ m/^-/ } (keys $self->{modcfg}->%*);
my %args = $self->{modcfg}->%{-name, @keys};
$args{-log} = $self->log;
$args{-refresh} = $self->refresh // 0;
$args{-theme} = $theme;
# create instance
$self->{module} = (scalar $self->driver)->new(%args);
}
sub init_timers($self, $attrs) {
$self->{timers} = {
timeout => Breeze::Counter->countdown($attrs->{timeouts}),
fail => Breeze::Counter->countdown($attrs->{failures}),
};
}
sub log($s) { $s->{log}; }
sub name($s) { $s->{modcfg}->{-name}; }
sub driver($s) { $s->{modcfg}->{-driver}; }
sub timeout($s) { $s->{modcfg}->{-timeout}; }
sub refresh($s) { $s->{modcfg}->{-refresh}; }
sub timers($s) { $s->{timers}; }
sub module($s) { $s->{module}; }
sub name_canon($s) { sprintf "'%s'(%s)", $s->name, $s->driver; }
sub refresh_on_event($s) { $s->module->refresh_on_event; }
# timer manipulation
sub get_timer($self, $key) {
my $tmrref = \$self->timers->{$key};
return $tmrref if defined $$tmrref;
return;
}
sub set_timer($self, $timer, $ticks) {
croak "Ticks or timer invalid"
unless defined $timer && defined $ticks && $ticks >= 0;
# optimization: do not store counter for only one cycle,
# use local variable instead
if ($ticks == 0) {
my $temp = 0;
return \$temp;
}
$self->timers->{$timer} = Breeze::Counter->countdown($ticks);
# return a reference to the timer
return \$self->timers->{$timer};
}
sub get_or_set_timer($self, $timer, $ticks = undef) {
my $tmref = $self->get_timer($timer);
if (!defined $$tmref && defined $ticks && $ticks >= 0) {
$tmref = $self->set_timer($timer, $ticks);
}
return $tmref if defined $$tmref;
return;
}
sub delete_timer($self, $timer) {
delete $self->timers->{$timer};
}
sub fail($self) {
$self->{module} = Leaf::Fail->new(
-name => $self->name,
-log => $self->log,
text => sprintf("'%s'(%s)", $self->name, $self->driver),
);
}
sub run($self, $event) {
return try {
my $tmp = Time::Out::timeout($self->timeout => sub {
$self->module->$event;
});
if ($@) {
$self->log->error("timeouted");
return { timeout => 1 };
} elsif ($event eq "invoke") {
if (!defined $tmp || ref $tmp ne "HASH") {
$self->log->error(sprintf("returned '%s' instead of HASH", (ref($tmp) || "undef")));
return { fatal => 1 };
}
$tmp->@{qw(name instance)} = ($self->name, $self->name);
}
return { ok => 1, content => $tmp };
} catch {
chomp $_;
$self->log->error($_);
return { fatal => 1 };
};
}
sub is_separator($) { 0; }
#-------------------------------------------------------------------------------
package Breeze::Separator;
sub new($class) {
return bless {}, $class;
}
sub is_separator($) { 1; }
#-------------------------------------------------------------------------------
# vim: syntax=perl5-24
1;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment