package HomePanel::Driver; use Moo; use v5.36; use IO::Async::Loop; use IO::Async::Timer::Periodic; use Net::Async::HTTP; use Future::AsyncAwait; use WebService::ForecastIo; use WebService::TFL::Bus; use WebService::TFL::Bus::Response; use WebService::TFL::TubeStatus; use HomePanel::Render; use Types::Path::Tiny qw(AbsFile AbsPath); use Types::Standard qw(ArrayRef Str StrictNum); use Try::Tiny; use curry::weak; use namespace::clean; use Data::Dumper; has loop => ( is => 'lazy' ); sub _build_loop { IO::Async::Loop->new() } has user_agent => ( is => 'lazy' ); sub _build_user_agent { my ($self) = @_; my $ua = Net::Async::HTTP->new( max_connections_per_host => 1, stall_timeout => 10, decode_content => 1, ); $self->loop->add($ua); return $ua; } has [qw(forecast_latitude forecast_longitude)] => ( isa => StrictNum, is => 'ro', required => 1, ); has forecast_key => ( isa => Str, is => 'ro', required => 1, ); has forecast => ( is => 'lazy' ); sub _build_forecast { my ($self) = @_; WebService::ForecastIo->new({ api_key => $self->forecast_key, user_agent => $self->user_agent, }); } has forecast_response => ( is => 'rw' ); has forecast_timer => ( is => 'lazy' ); sub _build_forecast_timer { my ($self) = @_; $self->_new_timer( 900, $self->curry::weak::forecast_timer_cb(), ); } sub forecast_timer_cb { my ($self) = @_; $self->forecast->request({ latitude => $self->forecast_latitude, longitude => $self->forecast_longitude, exclude => ['flags','sources'], })->then(sub { $self->forecast_response(shift) })->retain; } has bus_stop_ids => ( is => 'ro', required => 1, isa => ArrayRef->of(Str)->plus_coercions(Str, sub { [ $_ ] }), coerce => 1, ); has bus => ( is => 'lazy', ); sub _build_bus { WebService::TFL::Bus->new({ user_agent => $_[0]->user_agent, }); } has bus_response => ( is => 'rw' ); has bus_timer => ( is => 'lazy' ); sub _build_bus_timer { my ($self) = @_; $self->_new_timer( 30, $self->curry::weak::bus_timer_cb(), ); } sub bus_timer_cb { my ($self) = @_; Future->wait_all( map { $self->bus->request($_) } $self->bus_stop_ids->@* )->then( sub { my @done_results = map { $_->result } grep { $_->is_done } @_; $self->bus_response( WebService::TFL::Bus::Response->new_merged(@done_results) ); } )->retain; }; has tube => ( is => 'lazy', ); sub _build_tube { WebService::TFL::TubeStatus->new({ user_agent => $_[0]->user_agent, }); } has tube_response => ( is => 'rw' ); has tube_timer => ( is => 'lazy' ); sub _build_tube_timer { my ($self) = @_; $self->_new_timer( 300, $self->curry::weak::tube_timer_cb(), ); } sub tube_timer_cb { my ($self) = @_; $self->tube->request( )->then(sub { $self->tube_response(shift) })->retain; } has writer_timer => ( is => 'lazy' ); sub _build_writer_timer { my ($self) = @_; $self->_new_timer( 5, $self->curry::weak::write_page, ); } sub _new_timer { my ($self,$interval,$code) = @_; my $timer = IO::Async::Timer::Periodic->new( interval => $interval, first_interval => 0, on_tick => sub { try { $code->(); } catch { warn "Problem: $_; ignoring" } }, reschedule => 'drift', ); $self->loop->add($timer); return $timer; } has template_file => ( is => 'ro', isa => AbsFile, coerce => AbsFile->coercion, required => 1, ); has output_file => ( is => 'ro', isa => AbsPath, coerce => AbsPath->coercion, required => 1, ); has render => ( is => 'lazy', ); sub _build_render { return HomePanel::Render->new({ template_file => $_[0]->template_file, }); } sub write_page { my ($self) = @_; return unless $self->forecast_response; my $output = $self->render->render({ forecast => $self->forecast_response, bus => $self->bus_response, tube => $self->tube_response, }); $self->output_file->spew_utf8($output); } sub start { my ($self) = @_; $self->forecast_timer->start; $self->bus_timer->start; $self->tube_timer->start; $self->writer_timer->start; } 1;