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;