From 2f218d34c9c8a726c1800a0a110ba99b72e47647 Mon Sep 17 00:00:00 2001 From: Roman Lacko <xlacko1@fi.muni.cz> Date: Sun, 27 Aug 2017 00:37:39 +0200 Subject: [PATCH] Refactor proper module wrapper into Breeze::Instances --- Breeze/Core.pm | 279 ++++++++++++++------------------------------ Breeze/Instances.pm | 167 ++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 192 deletions(-) create mode 100644 Breeze/Instances.pm diff --git a/Breeze/Core.pm b/Breeze/Core.pm index bd62052..092a471 100644 --- a/Breeze/Core.pm +++ b/Breeze/Core.pm @@ -11,7 +11,6 @@ no warnings qw(experimental::signatures); use Carp; use Data::Dumper; use JSON::XS; -use Module::Load; use Time::HiRes; use Time::Out qw(timeout); use Try::Tiny; @@ -19,6 +18,7 @@ use Try::Tiny; # Wild Breeze modules use Breeze::Cache; use Breeze::Counter; +use Breeze::Instances; use Breeze::Logger; use Breeze::Logger::File; use Breeze::Logger::StdErr; @@ -82,33 +82,6 @@ sub u8($self, $what) { 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 sub resolve_defaults($self) { @@ -145,95 +118,53 @@ sub init_modules($self) { if (ref $modcfg eq "") { # it might be a separator if ($modcfg eq "separator") { - push $self->{mods}->@*, { separator => undef }; + push $self->{mods}->@*, Breeze::Separator->new(); # otherwise it's an error } else { $self->log->fatal("scalar '$modcfg' found instead of module description"); } } else { - # check for required parameters - 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 ($name, $driver) = ($modcfg->{-name} // "<undef>", $modcfg->{-driver} // "<undef>"); - my $module = try { - load $moddrv; - - # pass only the '-name' parameter and those that - # do not begin with '-' - my @keys = grep { $_ !~ m/^-/ } (keys $modcfg->%*); - my %args = $modcfg->%{-name, @keys}; - $args{-log} = $modlog; - $args{-refresh} = $modcfg->{-refresh} // 0; - $args{-theme} = $self->theme; + my $inst = try { + if (!defined $modcfg->{-timeout}) { + $modcfg->{-timeout} = $self->cfg->{timeout}; + } elsif ($modcfg->{-timeout} <= 0) { + $self->log->fatal("invalid timeout for '$name'($driver): $modcfg->{-timeout}"); + } - # create instance - return $moddrv->new(%args); + Breeze::Module->new($modcfg, { + log => $self->log, + theme => $self->theme, + timeouts => $self->cfg->{timeouts}, + failures => $self->cfg->{failures}, + }); } catch { - chomp $_; - $self->log->error("failed to initialize '$modname'"); + $self->log->error("failed to instantiate '$name'($driver)"); $self->log->error($_); - return; }; - # module failed to initialize? - if (!defined $module && $moddrv ne "Leaf::Fail") { - # replace module description with dummy text and redo - $self->log->info("replacing '$moddrv' with failed placeholder"); + my $error_msg = $_; + # if module failed + if (!defined $inst) { + # 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 = { - -name => $modname, + -name => "__failed_" . $self->{mods}->$#*, -driver => "Leaf::Fail", - text => "'$modname' ($moddrv)", + text => "'$name'($driver)", }; 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 - my $entry = { - 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; + push $self->{mods}->@*, $inst; + $self->{mods_by_name}->{$name} = $inst; } } } @@ -253,25 +184,21 @@ sub init_events($self) { ]; } -sub fail_module($self, $entry, $timer) { - my $tmr = \$entry->{tmrs}->{$timer}; +sub fail_module($self, $module, $timer) { + my $tmr = $module->get_timer($timer); if (!($$tmr--)) { $self->log->error("module depleted timer '$timer' for the last time, disabling"); - $entry->{mod} = Leaf::Fail->new( - -entry => $entry->{conf}->{-name}, - -log => $entry->{log}, - text => "$entry->{conf}->{-name}($entry->{conf}->{-driver})", - ); - return $entry->{mod}->invoke; + $module->fail; + return $module->invoke; } else { # temporarily disable module return { - text => "$entry->{conf}->{-name}($entry->{conf}->{-driver})", + text => $module->name_canon, blink => $self->cfg->{cooldown}, cache => $self->cfg->{cooldown}, - background => "b58900", - color => "002b36", + background => "%{core.fail.bg,orange,yellow", + color => "%{core.fail.fg,fg,white}", icon => ($timer eq "fail" ? "ďŞ" : ""), }; } @@ -280,49 +207,38 @@ sub fail_module($self, $entry, $timer) { sub run($self) { my $ret = []; - foreach my $entry ($self->mods->@*) { + foreach my $module ($self->mods->@*) { # separators will be handled in postprocessing - if (exists $entry->{separator}) { - push @$ret, { %$entry }; + if ($module->is_separator) { + push @$ret, { separator => 1 }; next; } # try to get cached output first - my $data = $self->cache->get($entry->{conf}->{-name}); + my $data = $self->cache->get($module->name); if (!defined $data) { - $data = try { - my $to = $entry->{conf}->{-timeout} // $self->cfg->{timeout}; - my $tmp = timeout($to => sub { - return $entry->{mod}->invoke; - }); - - if ($@) { - $self->log->error("module '$entry->{conf}->{-name}' timeouted"); - return $self->fail_module($entry, "timeout"); - } elsif (!defined $tmp || ref $tmp ne "HASH") { - $self->log->fatal("module '$entry->{conf}->{-name}' returned ", (ref($tmp) || "undef")); - } - - return $tmp; - } catch { - chomp $_; - $self->log->error("error in '$entry->{conf}->{-name}' ($entry->{conf}->{-driver})"); - $self->log->error($_); - return $self->fail_module($entry, "fail"); - }; + # run module + $data = $module->run("invoke"); + + if ($data->{fatal}) { + $data = $self->fail_module($module, "fail"); + } elsif ($data->{timeout}) { + $data = $self->fail_module($module, "timeout"); + } elsif ($data->{ok}) { + $data = $data->{content}; + } else { + $self->log->fatal("module wrapper did not return 'ok'"); + } } else { # ignore cached 'blink', 'invert', 'reset_blink' and 'reset_invert' delete $data->@{qw(blink invert cache reset_blink reset_invert reset_all)}; } - # set entry and instance - $data->@{qw(entry instance)} = $entry->{conf}->@{qw(-name -name)}; - - if (($entry->{conf}->{-refresh} // 0) >= 1) { - $self->cache->set($entry->{conf}->{-name}, $data, $entry->{conf}->{-refresh}); + if (($module->refresh // 0) >= 1) { + $self->cache->set($module->name, $data, $module->refresh); } 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; @@ -373,10 +289,11 @@ sub post_process_inversion($self, $ret) { # separators are not supposed to blink 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}; - 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; # advance timer, use global if evaluates to inf @@ -387,7 +304,7 @@ sub post_process_inversion($self, $ret) { $seg->{invert} = 1 if $tick >= 0; # 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) { # separators are not supposed to blink 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}; - 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; # advance timer, use global if evaluates to inf @@ -410,7 +328,7 @@ sub post_process_blinking($self, $ret) { $seg->{invert} = ($seg->{invert} xor ($tick % 2 == 0)); # 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) { $sep->{background} = $ret->[$ix - 1]->{background} // $default_bg; } - $sep->{entry} = $sep->{instance} = "__separator_$counter"; + $sep->{name} = $sep->{instance} = "__separator_$counter"; ++$counter; } } @@ -473,7 +391,7 @@ sub post_process_attr($self, $ret) { $seg->{full_text} =~ s/%utf8\{(.*?)\}/$self->u8($1)/ge; # 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}; $seg->{full_text} =~ s/^(\S)/$pad$1/; $seg->{full_text} =~ s/(\S)$/$1$pad/; @@ -516,24 +434,22 @@ sub event_button($self, $code) { } sub process_event($self, $mod, $data) { - my $entry = $mod->{conf}->{-name}; - # module might want to redraw after event - $self->cache->flush($entry) + $self->cache->flush($mod->name) if $data->{flush}; # stop timers - $self->delete_timer($entry, "blink") + $mod->delete_timer("blink") if $data->{reset_blink} || $data->{reset_all}; - $self->delete_timer($entry, "invert") + $mod->delete_timer("invert") if $data->{reset_invert} || $data->{reset_all}; # 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}; - $self->get_or_set_timer($entry, "invert", $data->{invert}) + $mod->get_or_set_timer("invert", $data->{invert}) if defined $data->{invert}; } @@ -549,46 +465,25 @@ sub event($self, $event) { } my $button = $self->event_button($event->{button}); - my $data = try { - if ($mod->{mod}->refresh_on_event) { - $self->cache->flush($target); - } - - 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 (!defined $button) { + $self->log->error("got unknown event '$event->{button}' for '$target'"); + return; + } - if ($@) { - $self->log->error("event for '$target' timeouted"); + $self->cache->flush($mod->name) + if ($mod->refresh_on_event); - # flush cache and replace entry with failed placeholder - $self->cache->flush($target); - my $ret = $self->fail_module($mod, "timeout"); - $ret->{text} .= "[event]"; - $self->cache->set($target, $ret, $self->cfg->{cooldown}); - return $ret; - } + my $data = $mod->run("on_$button"); - return $tmp; - } catch { - chomp $_; - $self->log->error("event for '$target' failed"); - $self->log->error($_); - - # flush cache and replace entry with failed placeholder - $self->cache->flush($target); - my $ret = $self->fail_module($mod, "fail"); - $ret->{text} .= "[event]"; - $self->cache->set($target, $ret, $self->cfg->{cooldown}); - return $ret; - }; + if ($data->{fatal}) { + $data = $self->fail_module($mod, "fail"); + } elsif ($data->{timeout}) { + $data = $self->fail_module($mod, "timeout"); + } elsif ($data->{ok}) { + $data = $data->{content}; + } else { + $self->log->fatal("module wrapper did not return 'ok'"); + } if (defined $data and ref $data eq "HASH") { $self->process_event($mod, $data); diff --git a/Breeze/Instances.pm b/Breeze/Instances.pm new file mode 100644 index 0000000..e5fb762 --- /dev/null +++ b/Breeze/Instances.pm @@ -0,0 +1,167 @@ +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; -- GitLab