pbot/lib/PBot/Core/EventQueue.pm

272 lines
7.9 KiB
Perl
Raw Normal View History

# File: EventQueue.pm
#
# Purpose: Provides functionality to manage event subroutines which are invoked
2020-03-06 22:21:44 +01:00
# at a future time, optionally recurring.
#
2021-07-21 07:44:51 +02:00
# Note: PBot::Core::EventQueue has no relation to PBot::Core::EventDispatcher.
2021-07-11 00:00:22 +02:00
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
License project under MPL2 This patch adds the file LICENSE which is the verbatim copy of the Mozilla Public License Version 2.0 as retreived from https://www.mozilla.org/media/MPL/2.0/index.815ca599c9df.txt on 2017-03-05. This patch also places license headers for the MPL2 type A variant of the license header in the following files: PBot/AntiFlood.pm PBot/BanTracker.pm PBot/BlackList.pm PBot/BotAdminCommands.pm PBot/BotAdmins.pm PBot/ChanOpCommands.pm PBot/ChanOps.pm PBot/Channels.pm PBot/Commands.pm PBot/DualIndexHashObject.pm PBot/EventDispatcher.pm PBot/FactoidCommands.pm PBot/FactoidModuleLauncher.pm PBot/Factoids.pm PBot/HashObject.pm PBot/IRCHandlers.pm PBot/IgnoreList.pm PBot/IgnoreListCommands.pm PBot/Interpreter.pm PBot/LagChecker.pm PBot/Logger.pm PBot/MessageHistory.pm PBot/MessageHistory_SQLite.pm PBot/NickList.pm PBot/PBot.pm PBot/Plugins.pm PBot/Plugins/AntiAway.pm PBot/Plugins/AntiKickAutoRejoin.pm PBot/Plugins/AntiRepeat.pm PBot/Plugins/AntiTwitter.pm PBot/Plugins/AutoRejoin.pm PBot/Plugins/Counter.pm PBot/Plugins/Quotegrabs.pm PBot/Plugins/Quotegrabs/Quotegrabs_Hashtable.pm PBot/Plugins/Quotegrabs/Quotegrabs_SQLite.pm PBot/Plugins/UrlTitles.pm PBot/Plugins/_Example.pm PBot/Refresher.pm PBot/Registerable.pm PBot/Registry.pm PBot/RegistryCommands.pm PBot/SQLiteLogger.pm PBot/SQLiteLoggerLayer.pm PBot/SelectHandler.pm PBot/StdinReader.pm PBot/Timer.pm PBot/Utils/ParseDate.pm PBot/VERSION.pm build/update-version.pl modules/acronym.pl modules/ago.pl modules/c11std.pl modules/c2english.pl modules/c2english/CGrammar.pm modules/c2english/c2eng.pl modules/c99std.pl modules/cdecl.pl modules/cfaq.pl modules/cjeopardy/IRCColors.pm modules/cjeopardy/QStatskeeper.pm modules/cjeopardy/Scorekeeper.pm modules/cjeopardy/cjeopardy.pl modules/cjeopardy/cjeopardy_answer.pl modules/cjeopardy/cjeopardy_filter.pl modules/cjeopardy/cjeopardy_hint.pl modules/cjeopardy/cjeopardy_qstats.pl modules/cjeopardy/cjeopardy_scores.pl modules/cjeopardy/cjeopardy_show.pl modules/codepad.pl modules/compiler_block.pl modules/compiler_client.pl modules/compiler_vm/Diff.pm modules/compiler_vm/cc modules/compiler_vm/compiler_client.pl modules/compiler_vm/compiler_server.pl modules/compiler_vm/compiler_server_vbox_win32.pl modules/compiler_vm/compiler_server_watchdog.pl modules/compiler_vm/compiler_vm_client.pl modules/compiler_vm/compiler_vm_server.pl modules/compiler_vm/compiler_watchdog.pl modules/compiler_vm/languages/_c_base.pm modules/compiler_vm/languages/_default.pm modules/compiler_vm/languages/bash.pm modules/compiler_vm/languages/bc.pm modules/compiler_vm/languages/bf.pm modules/compiler_vm/languages/c11.pm modules/compiler_vm/languages/c89.pm modules/compiler_vm/languages/c99.pm modules/compiler_vm/languages/clang.pm modules/compiler_vm/languages/clang11.pm modules/compiler_vm/languages/clang89.pm modules/compiler_vm/languages/clang99.pm modules/compiler_vm/languages/clangpp.pm modules/compiler_vm/languages/clisp.pm modules/compiler_vm/languages/cpp.pm modules/compiler_vm/languages/freebasic.pm modules/compiler_vm/languages/go.pm modules/compiler_vm/languages/haskell.pm modules/compiler_vm/languages/java.pm modules/compiler_vm/languages/javascript.pm modules/compiler_vm/languages/ksh.pm modules/compiler_vm/languages/lua.pm modules/compiler_vm/languages/perl.pm modules/compiler_vm/languages/python.pm modules/compiler_vm/languages/python3.pm modules/compiler_vm/languages/qbasic.pm modules/compiler_vm/languages/scheme.pm modules/compiler_vm/languages/server/_c_base.pm modules/compiler_vm/languages/server/_default.pm modules/compiler_vm/languages/server/c11.pm modules/compiler_vm/languages/server/c89.pm modules/compiler_vm/languages/server/c99.pm modules/compiler_vm/languages/server/clang.pm modules/compiler_vm/languages/server/clang11.pm modules/compiler_vm/languages/server/clang89.pm modules/compiler_vm/languages/server/clang99.pm modules/compiler_vm/languages/server/cpp.pm modules/compiler_vm/languages/server/freebasic.pm modules/compiler_vm/languages/server/haskell.pm modules/compiler_vm/languages/server/java.pm modules/compiler_vm/languages/server/qbasic.pm modules/compiler_vm/languages/server/tendra.pm modules/compiler_vm/languages/sh.pm modules/compiler_vm/languages/tendra.pm modules/compliment modules/cstd.pl modules/define.pl modules/dice_roll.pl modules/excuse.sh modules/expand_macros.pl modules/fnord.pl modules/funnyish_quote.pl modules/g.pl modules/gdefine.pl modules/gen_cfacts.pl modules/gencstd.pl modules/get_title.pl modules/getcfact.pl modules/google.pl modules/gspy.pl modules/gtop10.pl modules/gtop15.pl modules/headlines.pl modules/horoscope modules/horrorscope modules/ideone.pl modules/insult.pl modules/love_quote.pl modules/man.pl modules/map.pl modules/math.pl modules/prototype.pl modules/qalc.pl modules/random_quote.pl modules/seen.pl modules/urban modules/weather.pl modules/wikipedia.pl pbot.pl pbot.sh It is highly recommended that this list of files is reviewed to ensure that all files are the copyright of the sole maintainer of the repository. If any files with license headers contain the intellectual property of anyone else, it is recommended that a request is made to revise this patch or that the explicit permission of the co-author is gained to allow for the license of the work to be changed. I (Tomasz Kramkowski), the contributor, take no responsibility for any legal action taken against the maintainer of this repository for incorrectly claiming copyright to any work not owned by the maintainer of this repository.
2017-03-05 22:33:31 +01:00
2021-07-21 07:44:51 +02:00
package PBot::Core::EventQueue;
use parent 'PBot::Core::Class';
2021-06-19 06:23:34 +02:00
use PBot::Imports;
2019-07-11 03:40:53 +02:00
2021-07-24 04:22:25 +02:00
use PBot::Core::Utils::PriorityQueue;
2021-07-19 02:58:48 +02:00
use Time::HiRes qw/time/;
sub initialize {
2020-02-15 23:38:32 +01:00
my ($self, %conf) = @_;
2021-07-24 04:22:25 +02:00
$self->{event_queue} = PBot::Core::Utils::PriorityQueue->new(pbot => $self->{pbot});
}
# returns seconds until upcoming event.
sub duration_until_next_event {
my ($self) = @_;
2021-07-19 02:58:48 +02:00
return 0 if not $self->{event_queue}->count;
return $self->{event_queue}->get_priority(0) - time;
}
# invokes any current events and then returns seconds until upcoming event.
sub do_events {
my ($self) = @_;
# early-return if no events available
2021-07-19 02:58:48 +02:00
return 0 if not $self->{event_queue}->count;
my $debug = $self->{pbot}->{registry}->get_value('eventqueue', 'debug') // 0;
# repeating events to re-enqueue
my @enqueue;
2021-07-19 02:58:48 +02:00
for (my $i = 0; $i < $self->{event_queue}->entries; $i++) {
# we call time for a fresh time, instead of using a stale $now that
# could be in the past depending on a previous event's duration
2021-07-19 02:58:48 +02:00
if (time >= $self->{event_queue}->get_priority($i)) {
my $event = $self->{event_queue}->get($i);
$self->{pbot}->{logger}->log("Processing event $i: $event->{id}\n") if $debug > 1;
# call event's subref, passing event as argument
$event->{subref}->($event);
# remove event from queue
2021-07-19 02:58:48 +02:00
$self->{event_queue}->remove($i--);
# add event to re-enqueue queue if repeating
push @enqueue, $event if $event->{repeating};
} else {
# no more events ready at this time
if ($debug > 2) {
my $event = $self->{event_queue}->get($i);
2021-07-19 02:58:48 +02:00
$self->{pbot}->{logger}->log("Event not ready yet: $event->{id} (timeout=$event->{priority})\n");
}
last;
}
}
# re-enqueue repeating events
foreach my $event (@enqueue) {
$self->enqueue_event($event->{subref}, $event->{interval}, $event->{id}, 1);
}
return $self->duration_until_next_event;
}
# check if an event is in the event queue.
sub exists {
my ($self, $id) = @_;
2021-07-19 02:58:48 +02:00
return scalar grep { $_->{id} eq $id } $self->{event_queue}->entries;
}
# adds an event to the event queue, optionally repeating
2020-03-06 22:21:44 +01:00
sub enqueue_event {
my ($self, $subref, $interval, $id, $repeating) = @_;
# default values
$id //= "unnamed (${interval}s $subref)";
$repeating //= 0;
$interval //= 0;
# create event structure
2020-03-06 22:21:44 +01:00
my $event = {
id => $id,
subref => $subref,
2020-03-06 22:21:44 +01:00
interval => $interval,
2021-07-19 02:58:48 +02:00
priority => time + $interval,
2020-03-06 22:21:44 +01:00
repeating => $repeating,
};
2021-07-19 02:58:48 +02:00
# add the event to the priority queue
my $position = $self->{event_queue}->add($event);
# debugging noise
my $debug = $self->{pbot}->{registry}->get_value('eventqueue', 'debug') // 0;
2020-03-06 22:21:44 +01:00
if ($debug > 1) {
2021-07-19 02:58:48 +02:00
$self->{pbot}->{logger}->log("Enqueued new event $id at position $position: timeout=$event->{priority} interval=$interval repeating=$repeating\n");
2020-03-06 22:21:44 +01:00
}
}
# convenient alias to add an event with repeating defaulted to enabled.
sub enqueue {
my ($self, $subref, $interval, $id, $repeating) = @_;
$self->enqueue_event($subref, $interval, $id, $repeating // 1);
}
# removes an event from the event queue, optionally invoking it.
# `id` can contain `.*` and `.*?` for wildcard-matching/globbing.
2020-03-06 22:21:44 +01:00
sub dequeue_event {
my ($self, $id, $execute) = @_;
2020-03-06 22:21:44 +01:00
my $result = eval {
# escape special characters
2020-03-06 22:21:44 +01:00
$id = quotemeta $id;
# unescape .* and .*?
2020-03-06 22:21:44 +01:00
$id =~ s/\\\.\\\*\\\?/.*?/g;
$id =~ s/\\\.\\\*/.*/g;
# compile regex
2020-04-06 05:33:14 +02:00
my $regex = qr/^$id$/i;
# count total events before removal
2021-07-19 02:58:48 +02:00
my $count = $self->{event_queue}->count;
# collect events to be removed
2021-07-19 02:58:48 +02:00
my @removed = grep { $_->{id} =~ /$regex/i; } $self->{event_queue}->entries;
# remove events from event queue
2021-07-19 02:58:48 +02:00
@{$self->{event_queue}->queue} = grep { $_->{id} !~ /$regex/i; } $self->{event_queue}->entries;
# set count to total events removed
2021-07-19 02:58:48 +02:00
$count -= $self->{event_queue}->count;
# invoke removed events, if requested
if ($execute) {
foreach my $event (@removed) {
$event->{subref}->($event);
}
}
# nothing removed
return "No matching events." if not $count;
# list all removed events
my $removed = "Removed $count event" . ($count == 1 ? '' : 's') . ': ' . join(', ', map { $_->{id} } @removed);
$self->{pbot}->{logger}->log("EventQueue: dequeued $removed\n");
return $removed;
2020-03-06 22:21:44 +01:00
};
if ($@) {
my $error = $@;
$self->{pbot}->{logger}->log("Error in dequeue_event: $error\n");
$error =~ s/ at PBot.*//;
return "$error";
2020-03-06 22:21:44 +01:00
}
return $result;
2020-03-06 22:21:44 +01:00
}
# alias to dequeue_event, for consistency.
sub dequeue {
my ($self, $id) = @_;
$self->dequeue_event($id);
}
# invoke and remove all events matching `id`, which can
# contain `.*` and `.*?` for wildcard-matching/globbing.
sub execute_and_dequeue_event {
my ($self, $id) = @_;
return $self->dequeue_event($id, 1);
}
# replace code subrefs for matching events. if no events
# were found, then add the event to the event queue.
sub replace_subref_or_enqueue_event {
my ($self, $subref, $interval, $id, $repeating) = @_;
# find events matching id
2021-07-19 02:58:48 +02:00
my @events = grep { $_->{id} eq $id } $self->{event_queue}->entries;
# no events found, enqueue new event
if (not @events) {
$self->enqueue_event($subref, $interval, $id, $repeating);
return;
}
# otherwise update existing events
foreach my $event (@events) {
$event->{subref} = $subref;
}
}
# remove existing events of this id then enqueue new event.
sub replace_or_enqueue_event {
my ($self, $subref, $interval, $id, $repeating) = @_;
# remove event if it exists
$self->dequeue_event($id) if $self->exists($id);
# enqueue new event
$self->enqueue_event($subref, $interval, $id, $repeating);
}
# add event unless it already had been added.
sub enqueue_event_unless_exists {
my ($self, $subref, $interval, $id, $repeating) = @_;
# event already exists, bail out
return if $self->exists($id);
# enqueue new event
$self->enqueue_event($subref, $interval, $id, $repeating);
}
# update the `repeating` flag for all events matching `id`.
sub update_repeating {
my ($self, $id, $repeating) = @_;
2021-07-19 02:58:48 +02:00
foreach my $event ($self->{event_queue}->entries) {
if ($event->{id} eq $id) {
$event->{repeating} = $repeating;
}
}
}
# update the `interval` value for all events matching `id`.
sub update_interval {
2020-03-06 22:21:44 +01:00
my ($self, $id, $interval, $dont_enqueue) = @_;
2021-07-19 02:58:48 +02:00
for (my $i = 0; $i < $self->{event_queue}->count; $i++) {
my $event = $self->{event_queue}->get($i);
2021-07-19 02:58:48 +02:00
if ($event->{id} eq $id) {
2020-03-06 22:21:44 +01:00
if ($dont_enqueue) {
# update interval in-place without moving event to new place in queue
# (allows event to fire at expected time, then updates to new timeout afterwards)
2021-07-19 02:58:48 +02:00
$event->{interval} = $interval;
2020-03-06 22:21:44 +01:00
} else {
# remove and add event in new position in queue
2021-07-19 02:58:48 +02:00
$self->{event_queue}->remove($i);
2020-03-06 22:21:44 +01:00
$self->enqueue_event($event->{subref}, $interval, $id, $event->{repeating});
}
2020-02-15 23:38:32 +01:00
}
}
}
2021-07-21 21:43:30 +02:00
sub count {
my ($self) = @_;
return $self->{event_queue}->count;
}
sub entries {
my ($self) = @_;
return $self->{event_queue}->entries;
}
1;