From 5ee52eb7f38ca9678139c6ec2015e7314761e64b Mon Sep 17 00:00:00 2001 From: Roman Lacko <xlacko1@fi.muni.cz> Date: Tue, 22 Aug 2017 22:31:19 +0200 Subject: [PATCH] Implement caching, inversion and blinking --- Breeze/Cache.pm | 58 +++++++++++++++ Breeze/Core.pm | 175 +++++++++++++++++++++++++++++++++++++--------- Breeze/Counter.pm | 37 ++++++++++ WBM/Fail.pm | 12 ++-- WBM/Time2.pm | 45 ++++++++++++ test.yaml | 21 +++++- 6 files changed, 309 insertions(+), 39 deletions(-) create mode 100644 Breeze/Cache.pm create mode 100644 WBM/Time2.pm diff --git a/Breeze/Cache.pm b/Breeze/Cache.pm new file mode 100644 index 0000000..5709b3b --- /dev/null +++ b/Breeze/Cache.pm @@ -0,0 +1,58 @@ +package Breeze::Cache; + +use v5.26; +use utf8; +use strict; +use warnings; + +use feature qw(signatures); +no warnings qw(experimental::signatures); + +use Breeze::Counter; +use Data::Dumper; + +sub new($class, %args) { + return bless { + storage => {}, + }, $class; +} + +sub get($self, $key) { + my $entry = $self->{storage}->{$key}; + return unless defined $entry; + + # if entry was zero, the key expired, delete it and move on + if (!$entry->[0]--) { + delete $self->{storage}->{$key}; + return; + } + + return { $entry->[1]->%* }; +} + +sub set($self, $key, $value, $recall = 1) { + return if exists $self->{storage}->{$key}; + + $self->{storage}->{$key} = [ + Breeze::Counter->new(current => $recall), + { %$value }, + ]; + + print STDERR "cache stored ", Dumper($value); + # do not return entry + return; +} + +sub flush($self, @keys) { + # on total flush, just recreate storage + if (!@keys) { + $self->{storage} = {}; + # delete selected keys + } else { + delete $self->{storage}->@{@keys}; + } +} + +# vim: syntax=perl5-24 + +1; diff --git a/Breeze/Core.pm b/Breeze/Core.pm index ffa246a..cfcc1b9 100644 --- a/Breeze/Core.pm +++ b/Breeze/Core.pm @@ -8,11 +8,13 @@ use warnings; use feature qw(signatures); no warnings qw(experimental::signatures); +use Breeze::Cache; use Breeze::Counter; use Breeze::Logger; use Breeze::Logger::File; use Breeze::Logger::StdErr; use Carp; +use Data::Dumper; use JSON::XS; use Module::Load; use Time::Out qw(timeout); @@ -20,7 +22,8 @@ use Try::Tiny; sub new($class, $config) { my $self = { - config => $config, + config => $config, + cache => Breeze::Cache->new, }; bless $self, $class; @@ -32,8 +35,9 @@ sub new($class, $config) { } # getters -sub cfg($self) { $self->{config}; } -sub log($self) { $self->{logger}; } +sub cfg($self) { $self->{config}; } +sub log($self) { $self->{logger}; } +sub cache($self) { $self->{cache}; } sub mod($self, $name = undef) { return $self->{mods} if !defined $name; @@ -154,46 +158,57 @@ sub init_modules($self) { }; push $self->{mods}->@*, $entry; - $self->{mods_by_name} = $entry; + $self->{mods_by_name}->{$modname} = $entry; } } } - sub run($self) { my $ret; foreach my $entry ($self->mod->@*) { # separators will be handled in postprocessing if (exists $entry->{separator}) { - push @$ret, $entry; + push @$ret, { %$entry }; next; } - # TODO cache - - # handle module with timeout - my $data = timeout($self->cfg->{timeout} => sub { - return try { - $entry->{mod}->invoke; - } catch { - chomp $_; - $self->log->error("error in '$entry->{conf}->{-name}' ($entry->{conf}->{-driver})"); - $self->log->error($_); - undef; - }; - }); - - # module timeouted? - if ($@) { - $data = $self->timeout_module($entry); - # module failed? - } elsif (!defined $data) { - $data = $self->fail_module($entry); + # try to get cached output first + $self->log->debug("looking for $entry->{conf}->{-name}"); + my $data = $self->cache->get($entry->{conf}->{-name}); + + if (!defined $data) { + $data = timeout($self->cfg->{timeout} => sub { + return try { + $entry->{mod}->invoke; + } catch { + chomp $_; + $self->log->error("error in '$entry->{conf}->{-name}' ($entry->{conf}->{-driver})"); + $self->log->error($_); + undef; + }; + }); + + # module timeouted? + if ($@) { + $data = $self->timeout_module($entry); + # module failed? + } elsif (!defined $data) { + $data = $self->fail_module($entry); + } + } else { + # ignore cached 'blink' and 'invert' + delete $data->@{qw(blink invert)}; } # 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}); + } elsif (defined $data->{cache} && $data->{cache} >= 0) { + $self->cache->set($entry->{conf}->{-name}, $data, $data->{cache}); + } + push @$ret, $data; } @@ -206,7 +221,6 @@ sub run($self) { sub u8($self, $what) { my $t = join("", map { chr(hex) } ($what =~ m/../g)); utf8::decode($t); - $self->log->info("decoded '$what' to be '$t'"); return $t; } @@ -234,19 +248,93 @@ sub post_process_seg($self, $ret) { } elsif (defined $data->{text}) { $data->{full_text} = $data->{text}; } + } +} + +sub get_or_set_timer($self, $key, $timer, $ticks) { + my $timers = $self->mod($key)->{tmrs}; + + print STDERR "timers for '$key'\n"; + print STDERR Dumper($timers); + # nothing to do if no timer set and ticks is undefined + return if !defined $timers->{$timer} && !defined $ticks; + + # create timer if there is none + $timers->{$timer} = Breeze::Counter->new(current => $ticks) + if !defined $timers->{$timer}; + + # return a reference to the timer + return \$timers->{$timer}; +} + +sub delete_timer($self, $key, $timer) { + delete $self->mod($key)->{tmrs}->{$timer}; +} + +sub post_process_inversion($self, $ret) { + foreach my $seg (@$ret) { + # separators are not supposed to blink + next if exists $seg->{separator}; + + print STDERR "invert $seg->{entry}\n"; + my $timer = $self->get_or_set_timer($seg->{entry}, "invert", $seg->{invert}); + next if !defined $timer; + + # advance timer + my $tick = (--$$timer)->current; - # cleanup - delete $data->@{qw(text icon)}; + # use xor to invert blinking if 'invert' flag is already set + $seg->{invert} = 1 if $tick >= 0; + + # remove timer if expired + $self->delete_timer($seg->{entry}, "invert") unless $tick; + } +} + +sub post_process_blinking($self, $ret) { + foreach my $seg (@$ret) { + # separators are not supposed to blink + next if exists $seg->{separator}; + + print STDERR "blink $seg->{entry}\n"; + my $timer = $self->get_or_set_timer($seg->{entry}, "blink", $seg->{blink}); + next if !defined $timer; + + # advance timer + my $tick = (--$$timer)->current; + + # use xor to invert blinking if 'invert' flag is already set + $seg->{invert} = ($seg->{invert} xor ($tick % 2 == 0)); + + # remove timer if expired + $self->delete_timer($seg->{entry}, "blink") unless $tick; + } +} + +sub post_process_inverted($self, $ret) { + foreach my $seg (@$ret) { + # separators are not to be inverted (here) + next if exists $seg->{separator}; + + if ($seg->{invert}) { + # set sane defaults + foreach (qw(color background)) { + $seg->{$_} = $self->cfg->{defaults}->{$_} + unless defined $seg->{$_}; + } + + $seg->@{qw(color background)} = $seg->@{qw(background color)}; + } } } sub post_process_sep($self, $ret) { my $default_bg = $self->cfg->{defaults}->{background}; + my $counter = 0; foreach my $ix (0..$#$ret) { my $sep = $ret->[$ix]; next if !exists $sep->{separator}; - delete $sep->{separator}; # set separator icon $sep->{full_text} = "%utf8{ee82b2}"; @@ -263,6 +351,9 @@ sub post_process_sep($self, $ret) { delete $sep->{border}; $sep->{background} = $ret->[$ix - 1]->{background} // $default_bg; } + + $sep->{entry} = $sep->{instance} = "__separator_$counter"; + ++$counter; } } @@ -275,9 +366,16 @@ sub post_process_attr($self, $ret) { } } - # replace 'utf8{byte}' with utf8 character + # replace '%utf8{byte}' with utf8 character $seg->{full_text} =~ s/%utf8\{(.*?)\}/$self->u8($1)/ge; + # add padding if requested + if ($self->cfg->{padding} && $seg->{entry} !~ m/^__separator_/) { + my $pad = " " x $self->cfg->{padding}; + $seg->{full_text} =~ s/^(\S)/$pad$1/; + $seg->{full_text} =~ s/(\S)$/$1$pad/; + } + # set separator width and distance $seg->{separator} = JSON::XS::false; $seg->{separator_block_width} = 0; @@ -288,11 +386,24 @@ sub post_process($self, $ret) { # process all module segments $self->post_process_seg($ret); - # process separator segments + # process inversion and blinking + # (colors must be figured before computing separators) + $self->post_process_inversion($ret); + $self->post_process_blinking($ret); + + # process inverted elements + $self->post_process_inverted($ret); + + # process separator segment $self->post_process_sep($ret); # fix colors and utf8 characters $self->post_process_attr($ret); + + # cleanup tags + foreach my $seg (@$ret) { + delete $seg->@{qw(separator text icon blink invert)}; + } } # vim: syntax=perl5-24 diff --git a/Breeze/Counter.pm b/Breeze/Counter.pm index 5a88179..5e842bf 100644 --- a/Breeze/Counter.pm +++ b/Breeze/Counter.pm @@ -10,6 +10,14 @@ no warnings qw(experimental::signatures); use Carp; +use overload + "+=" => \&op_peq, + "-=" => \&op_meq, + "=" => \&op_assign, + "bool" => \&op_bool, + '""' => \&op_scalar, + "0+" => \&op_scalar; + sub from($self) { $self->{from}; } sub to($self) { $self->{to}; } sub cycle($self) { $self->{cycle}; } @@ -80,6 +88,35 @@ sub reset($self) { return $self->{current} = $self->{start}; } +sub clone($self) { + my %attrs = %$self; + delete $attrs{start}; + return __PACKAGE__->new(%attrs); +} + +# operators +sub op_bool($self, $, $) { + return $self->{current} != $self->{from}; +} + +sub op_scalar($self, $, $) { + return $self->{current}; +} + +sub op_peq($self, $o, $swap) { + $self->next foreach (1..$o); + return $self; +} + +sub op_meq($self, $o, $swap) { + $self->prev foreach (1..$o); + return $self; +} + +sub op_assign($self, $, $) { + return $self->clone; +} + # vim: syntax=perl5-24 1; diff --git a/WBM/Fail.pm b/WBM/Fail.pm index 6ceb5c5..dde21b5 100644 --- a/WBM/Fail.pm +++ b/WBM/Fail.pm @@ -30,13 +30,17 @@ sub invoke($self) { full_text => $self->{text}, icon => "%utf8{f09f9eaa}", color => "ff5f00", + cache => "+inf", }; + $self->log->info("invoked"); if ($self->{first}) { - $ret->{blink} = 6; - $self->{first} = 0; - } elsif(!$self->{dismissed}) { - $ret->{inverse} = 1; + $ret->{blink} = 6; + $self->{first} = 0; + } + + if (!$self->{dismissed}) { + $ret->{invert} = "+inf"; } return $ret; diff --git a/WBM/Time2.pm b/WBM/Time2.pm new file mode 100644 index 0000000..2a464a9 --- /dev/null +++ b/WBM/Time2.pm @@ -0,0 +1,45 @@ +package WBM::Time2; + +use v5.26; +use utf8; +use strict; +use warnings; + +use parent qw(WBM::Driver); +use feature qw(signatures); +no warnings qw(experimental::signatures); + +use Carp; +use Time::Format qw(%strftime); + +sub new($class, %args) { + my $self = $class->SUPER::new(%args); + + croak "missing 'format' parameter in constructor" + unless defined $args{format}; + + croak "missing 'icon' parameter in constructor" + unless defined $args{icon}; + + $self->{format} = $args{format}; + $self->{icon} = $args{icon}; + $self->{first} = 1; + return $self; +} + +sub invoke($self) { + my $ret = { + text => $strftime{$self->{format}, localtime}, + icon => $self->{icon}, + background => '660033', + color => 'ffffff', + }; + + $ret->{invert} = 8 if $self->{first}; + delete $self->{first}; + return $ret; +} + +# vim: syntax=perl5-24 + +1; diff --git a/test.yaml b/test.yaml index 223b8eb..6e08be9 100644 --- a/test.yaml +++ b/test.yaml @@ -6,8 +6,8 @@ # - %STDERR write to stderr # - null disable logging entirely #logfile: "/tmp/breeze-$$.log" -#logfile: %STDERR -logfile: null +logfile: %STDERR +#logfile: null # Turn debugging on or off. # @@ -44,6 +44,11 @@ timeouts: 3 # Values: positive integers failures: 3 +# Spaces to add around full_text entries. +# +# Values: non-negative integers +padding: 1 + # Default values for output entry. defaults: background: 002b36 @@ -58,9 +63,19 @@ defaults: modules: # leading separator - separator + - -name: fail + -driver: WBM::Fail + text: "FAILURE" + - separator + - -name: time-2 + -driver: WBM::Time2 + -refresh: 0 + format: "%a %y-%m-%d %H:%M:%S" + icon: %utf8{ef809720} + - separator - -name: time -driver: WBM::Time - -refresh: 1 + -refresh: 0 format: "%a %y-%m-%d %H:%M:%S" icon: %utf8{ef809720} # add trailing separator -- GitLab