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 )