aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Watson <urchlay@slackware.uk>2024-12-29 00:58:47 -0500
committerB. Watson <urchlay@slackware.uk>2024-12-29 00:58:47 -0500
commit71a4fad6b779a76bf622cb3c0d77497e507c5487 (patch)
tree89a67c297b0559500065f88f8d0063e1de0bc111
parent9eed830f296dab257759f5276d0963467007aa6b (diff)
downloadstupid-irssi-tricks-71a4fad6b779a76bf622cb3c0d77497e507c5487.tar.gz
yttitle.pl: rewrite, using irssi pidwait, input_add, etc, instead of timers and waitpid
-rw-r--r--yttitle.pl164
1 files changed, 82 insertions, 82 deletions
diff --git a/yttitle.pl b/yttitle.pl
index a1aaf3a..145d5ac 100644
--- a/yttitle.pl
+++ b/yttitle.pl
@@ -1,16 +1,28 @@
#!/usr/bin/perl
+# yttitle.pl
+
+# When someone says something with a youtube video link in it, get the
+# title and say it in the channel.
+
+# Uses yt-dlp to do the title lookups. Works much better than the
+# usual HTML-scraping, but unfortunately is *slow* (like, average 3
+# sec per lookup).
+
+# Since lookups take so long, they're done asynchronously, via
+# fork(). This script owes a great debt to the dns.pl script
+# included with irssi, which shows how to use irssi's pidwait and
+# input_(add|remove).
+
no strict;
-use POSIX ":sys_wait_h";
+use POSIX '_exit';
use Irssi qw/
signal_add_first
- signal_continue
- signal_register
- command_bind
- timeout_add_once
- get_irssi_dir
+ pidwait_add
+ input_add
+ input_remove
/;
our $VERSION = "0.1";
@@ -26,24 +38,29 @@ our %IRSSI = (
# video ID => title
our %cache;
-our $tmpdir = get_irssi_dir . "/yttitle.tmp";
+# keep track of video IDs of in-progress lookups, so we don't spawn
+# a 2nd process for the same ID.
+our %in_progress;
-# pid => [server, target, video ID]
-our %jobs;
+# TODO: the rest of these variables should be irssi settings.
-# video ID keys (values meaningless; only care about key presence)
-our %job_videos;
+# how many jobs are currently running?
+our $jobcount = 0;
-# attempts to spawn more jobs than this are just ignored.
+# attempts to spawn more simultaneous jobs than this are just ignored.
our $maxjobs = 10;
-# milliseconds: how often to check %jobs to see if any jobs are done.
-our $queue_time = 1000;
+# yt-dlp processes that don't respond within this many seconds
+# are terminated.
+our $job_timeout = 15;
+
+# passed to yt-dlp itself.
+our $socket_timeout = 10;
-# command to execute, 1st %s replaced with video ID, 2nd with tmp filename.
+# command to execute, %s replaced with video ID.
# --socket-timeout arg is seconds, should be longer that $timeout.
our $command_fmt =
- "yt-dlp -q --print '%%(title)s' --socket-timeout 10 -- %s >%s 2>/dev/null";
+ "yt-dlp -q --print '%%(title)s' --socket-timeout $socket_timeout -- %s 2>/dev/null";
our $DEBUG = 1;
@@ -55,35 +72,8 @@ sub debugf {
Irssi::print(sprintf(@_)) if $DEBUG;
}
-sub start_timer {
- timeout_add_once($queue_time, "check_jobs", undef);
-}
-
-sub get_tmp_filename {
- my $id = shift;
- return $tmpdir . "/" . $id;
-}
-
-sub read_tmp_file {
- my $file = shift;
-
- open my $fh, "<:encoding(UTF-8)", $file;
-
- if(!$fh) {
- debug("read_tmp_file() failed to open $file: $!");
- return;
- }
-
- my $result = <$fh>;
- close $fh;
-
- chomp $result if defined $result;
- return length($result) ? $result : undef;
-}
-
sub spawn_job {
my ($server, $target, $video_id) = @_;
- my $jobcount = keys %jobs;
debug("spawn_job() called, video_id $video_id");
@@ -92,45 +82,76 @@ sub spawn_job {
return;
}
- if($job_videos{$video_id}) {
+ if($in_progress{$video_id}) {
debug("spawn_job(): video_id $video_id already in queue");
return;
}
- my $cmd = sprintf($command_fmt, $video_id, get_tmp_filename($video_id));
- debug("spawn_job() command is: $cmd");
+ $in_progress{$video_id} = 1;
+
+ my $cmd = sprintf($command_fmt, $video_id);
+ debug("spawn_job() command is: $cmd");
+
+ my($read, $write);
+ pipe($read, $write);
my $pid = fork();
+ if(!defined($pid)) {
+ debug("spawn_job(): video_id $video_id: fork() failed!");
+ close $read;
+ close $write;
+ return;
+ }
if($pid) {
# parent
- debug("spawn_job() forked, kid pid is $pid");
- start_timer unless keys %jobs;
- $jobs{$pid} = [ $server, $target, $video_id ];
- $job_videos{$video_id} = 1;
- debug("spawn_job(): job pids: " . join(",", keys %jobs));
+ $jobcount++;
+ debug("spawn_job() forked, kid pid is $pid, jobcount now $jobcount");
+ close $write;
+ my $pipe_args = [ $pid, $server, $target, $video_id, $read ];
+ pidwait_add($pid);
+ $pipe_tags{$video_id} = input_add(fileno($read), INPUT_READ, \&job_done, $pipe_args);
+ return;
} else {
# child, debug() and Irssi::print don't work, here.
- exec $cmd;
+ eval {
+ local $SIG{ALRM} = sub { die "alarum\n" };
+ alarm $job_timeout;
+ print $write `$cmd`;
+ alarm 0;
+ };
+ # don't bother to die() on error here, we're about to exit anyway.
+ if($@ && ($@ eq "alarum\n")) {
+ # print *something* if $cmd timed out
+ print $write "\n";
+ }
+ # not sure this needs to be wrapped in eval, but...
+ eval {
+ close $write;
+ };
+ _exit(1);
}
}
sub job_done {
- my $pid = shift;
+ my $pipe_args = shift;
+ my ($pid, $server, $target, $video_id, $read) = @$pipe_args;
- my ($server, $target, $video_id) = @{$jobs{$pid}};
+ debug("job_done() pid $pid, target $target, video_id $video_id, jobcount was $jobcount");
- debug("job_done() pid $pid, target $target, video_id $video_id");
+ my $title = <$read>;
+ chomp $title;
+ close $read;
+ input_remove($pipe_tags{$video_id});
- my $file = get_tmp_filename($video_id);
- my $title = read_tmp_file($file);
-
- unlink $file;
+ $jobcount--;
+ delete $in_progress{$video_id};
$cache{$video_id} = $title;
- delete $job_videos{$video_id};
- if(!defined($title)) {
+ if(defined($title)) {
+ debug("job_done(): video_id $video_id title is $title");
+ } else {
debug("job_done(): video_id $video_id failed to get title");
return;
}
@@ -138,26 +159,6 @@ sub job_done {
say_title($server, $target, $video_id, $title);
}
-sub check_jobs {
- my @k = keys %jobs;
-
- debug("check_jobs(): " . @k . " jobs");
- return unless @k;
-
- for my $jobpid (@k) {
- debug("check_jobs(): about to waitpid($jobpid, WNOHANG)");
- $pid = waitpid($jobpid, WNOHANG);
-
- debug("check_jobs(): jobpid $jobpid, pid $pid");
-
- next if $pid == 0; # still running, let it run
- job_done($jobpid) if $pid > 0; # -1 is "no such pid"
- delete $jobs{$jobpid};
- }
-
- start_timer if keys %jobs;
-}
-
sub say_title {
my ($server, $target, $video_id, $title) = @_;
my $tag = "YouTube" . ($DEBUG ? "($video_id)" : "");
@@ -190,5 +191,4 @@ sub on_public_msg {
}
### main()
-mkdir $tmpdir;
signal_add_first("message public", "on_public_msg");