App-RoboBot
view release on metacpan or search on metacpan
lib/App/RoboBot/Plugin/Bot/Alarm.pm view on Meta::CPAN
sub suspend_alarm {
my ($self, $message, $command, $rpl, $name) = @_;
return unless $message->has_channel && defined $name;
my $res = $self->bot->config->db->do(q{
select id, is_suspended
from alarms_alarms
where channel_id = ?
and lower(name) = lower(?)
}, $message->channel->id, $name);
unless ($res && $res->next) {
$message->response->raise('There is no alarm named "%s" in this channel.', $name);
return;
}
if ($res->{'is_suspended'}) {
$message->response->raise('The alarm "%s" is already suspended.', $name);
return;
}
# Remove from the active alarm timers.
delete $self->alarms->{$res->{'id'}};
# Update in the database.
$self->bot->config->db->do(q{
update alarms_alarms set is_suspended = true where id = ?
}, $res->{'id'});
$message->response->push(sprintf('The alarm "%s" has been suspended.', $name));
return;
}
sub _get_alarm {
my ($self, $bot, $alarm_id) = @_;
# We buffer the comparison of the current next_emit to now() by a little bit
# into the future to account for the possibility of timer drift, and any
# other delays that may occur between when the timer is schedule to fire and
# when our bot's single execution thread finally reaches this point. One
# minute is a major buffer, but alarms cannot be scheduled to trigger more
# than a few times each hour anyway.
my $res = $bot->config->db->do(q{
select a.*,
case
when a.next_emit <= (now() + interval '1 minute') then 1
else 0
end as do_recalc
from alarms_alarms a
where a.id = ?
}, $alarm_id);
return unless $res && $res->next;
$res->{'exclusions'} = decode_json($res->{'exclusions'});
if ($res->{'do_recalc'} && $res->{'recurrence'}) {
# Alarm's recorded next occurrence has expired, so we need to recalc
# and updated the database before we send the alarm back to the caller.
# Pad the clause so a lack of exclusions doesn't generate bad SQL.
my @where = qw( false );
my @binds;
foreach my $excl (@{$res->{'exclusions'}}) {
push(@where, 'to_char(s.new_emit, ?) ~* ?');
push(@binds, $excl->{'format'}, $excl->{'pattern'});
}
# TODO: Alarms which have been suspended for a long time (where "long"
# is defined by the scale of their recurrence rate), will not get
# a new_emit properly from this query. I.e. a daily alarm that
# has been suspended for more than 100 days will get an empty
# resultset. Fix this in a way that is better than simply using
# larger and larger multiples on the interval for the stop in
# generate_series().
my $new_emit = $self->bot->config->db->do(q{
select date_trunc('seconds', s.new_emit) as new_emit
from alarms_alarms a,
generate_series(a.next_emit, a.next_emit + (a.recurrence * 100), a.recurrence) s(new_emit)
where a.id = ? and a.recurrence is not null and not (} . join(' or ', @where) . q{)
and s.new_emit > (now() + (a.recurrence / 2))
order by s.new_emit asc
limit 1
}, $res->{'id'}, @binds);
if ($new_emit && $new_emit->next) {
# We have a properly calculated next_emit, so update the alarm in
# the database with a returning * clause so we can get the alarm
# back to our caller.
$res = $self->bot->config->db->do(q{
update alarms_alarms set next_emit = ? where id = ? returning *
}, $new_emit->{'new_emit'}, $res->{'id'});
unless ($res && $res->next) {
return;
}
} else {
# Something has gone wrong, but we can't really send a message out.
# Suspend the alarm to prevent further problems.
$self->bot->config->db->do(q{
update alarms_alarms set is_suspended = true where id = ?
}, $res->{'id'});
return;
}
} elsif ($res->{'do_recalc'}) {
# The current value of next_emit is in the past, but we have a NULL
# recurrence, which means this alarm should be deleted so it doesn't
# ever fire again.
$self->bot->config->db->do(q{
delete from alarms_alarms where id = ?
}, $res->{'id'});
}
my %alarm = ( map { $_ => $res->{$_} } $res->columns );
return \%alarm;
}
( run in 1.068 second using v1.01-cache-2.11-cpan-39bf76dae61 )