diff options
Diffstat (limited to 'bin/mbsyncloop')
-rwxr-xr-x | bin/mbsyncloop | 307 |
1 files changed, 0 insertions, 307 deletions
diff --git a/bin/mbsyncloop b/bin/mbsyncloop deleted file mode 100755 index a1cf181..0000000 --- a/bin/mbsyncloop +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env perl -use 5.016; -use strict; -use warnings; - -use File::Path; -use File::Spec; -use File::Temp; -use JSON::PP; -use POSIX 'mkfifo'; - -my $RUN_DIR = make_run_dir(); -my ($PW_PID, $GOIMAPNOTIFY_PID); - -$SIG{INT} = $SIG{TERM} = sub { cleanup(); exit }; -END { cleanup() } - -my $config = "$ENV{HOME}/.config/mbsyncloop/config.json"; - -main($config); - -sub main { - my ($config) = @_; - - my $pw_pipe = make_pw_pipe(); - my $password_command_pipe = "head -n1 '$pw_pipe'"; - - my $config_data; - if (-f $config) { - $config_data = JSON::PP::decode_json(slurp($config)); - } - else { - $config_data = {}; - } - - my $mbsync_config_data = slurp( - ($config_data->{mbsync_config} // '~/.mbsyncrc') - =~ s{^~/}{$ENV{HOME}/}r - ); - my $password_command = extract_password_command($mbsync_config_data); - - spawn_pw_proc($pw_pipe, $password_command); - - my $goimapnotify_config_data = extract_goimapnotify_config( - $config_data, - $mbsync_config_data, - $password_command_pipe, - ); - - my $goimapnotify_r = spawn_goimapnotify_proc( - $goimapnotify_config_data, - ); - - my $generated_mbsync_config_file = write_mbsync_config( - $mbsync_config_data, - $goimapnotify_config_data->{boxes}, - $password_command_pipe, - ); - - loop( - $config_data, - $generated_mbsync_config_file, - $goimapnotify_r, - ); -} - -sub make_pw_pipe { - my $file = File::Spec->catfile($RUN_DIR, "mbsyncloop"); - unlink($file); - mkfifo($file, 0700) or die "couldn't create $file: $!"; - $file -} - -sub extract_goimapnotify_config { - my ($config_data, $mbsync_config_data, $password_command) = @_; - - (my $host) = $mbsync_config_data =~ /^Host (.*)$/m; - (my $port) = $mbsync_config_data =~ /^Port (.*)$/m; - (my $user) = $mbsync_config_data =~ /^User (.*)$/m; - - my $tls; - if ($mbsync_config_data =~ /SSLType\s+IMAPS/) { - $tls = JSON::PP::true; - $port //= 993; - } - else { - $tls = JSON::PP::false; - $port //= 143; - } - - my $goimapnotify_config = { - host => $host, - port => $port, - tls => $tls, - username => $user, - passwordCmd => $password_command, - }; - - my @mailboxes; - if ($config_data->{boxes}) { - @mailboxes = @{ $config_data->{boxes} }; - } - else { - @mailboxes = read_mailboxes($goimapnotify_config); - if ($config_data->{box_patterns}) { - @mailboxes = grep { - my $mailbox = $_; - grep { - $mailbox =~ /$_/ - } @{ $config_data->{box_patterns} } - } @mailboxes - } - } - - $goimapnotify_config->{onNewMail} = "echo new"; - $goimapnotify_config->{boxes} = \@mailboxes; - - $goimapnotify_config -} - -sub read_mailboxes { - my ($config) = @_; - - my $tmp = File::Temp->new(DIR => $RUN_DIR); - $tmp->print(JSON::PP::encode_json($config)); - $tmp->flush; - open my $fh, '-|', 'goimapnotify', '--conf', $tmp->filename, '--list' - or die "couldn't run goimapnotify: $!"; - <$fh>; - - map { chomp; s/^[^ ]* //r } <$fh> -} - -sub extract_password_command { - my ($mbsync_config) = @_; - (my $password_command) = $mbsync_config =~ /^PassCmd "(.*)"$/m; - $password_command -} - -sub spawn_pw_proc { - my ($pw_pipe, $password_command) = @_; - - my $pw = fetch_password($password_command); - - $PW_PID = fork; - die "fork failed: $!" unless defined $PW_PID; - if (!$PW_PID) { - $SIG{PIPE} = 'IGNORE'; - setpgrp(0, 0); - while (1) { - open my $fh, '>', $pw_pipe or die "couldn't open $pw_pipe"; - $fh->print("$pw\n"); - close $fh; - } - } -} - -sub fetch_password { - my ($password_command) = @_; - my $pw = `$password_command`; - die "failed to fetch password: command returned $?" if $?; - $pw -} - -sub spawn_goimapnotify_proc { - my ($config) = @_; - - pipe(my $goimapnotify_r, my $goimapnotify_w) - or die "failed to create unnamed pipe: $!"; - - $GOIMAPNOTIFY_PID = fork; - die "fork failed: $!" unless defined $GOIMAPNOTIFY_PID; - if (!$GOIMAPNOTIFY_PID) { - setpgrp(0, 0); - close $goimapnotify_r; - - my $tmp = File::Temp->new(DIR => $RUN_DIR); - $tmp->print(JSON::PP::encode_json($config)); - $tmp->flush; - - while (1) { - open my $fh, '-|', 'goimapnotify', '--conf', $tmp->filename - or die "couldn't run goimapnotify: $!"; - while (<$fh>) { - $goimapnotify_w->print("N\n"); - $goimapnotify_w->flush; - } - } - } - close $goimapnotify_w; - - $goimapnotify_r -} - -sub write_mbsync_config { - my ($mbsync_config, $mailboxes, $password_command) = @_; - - $mbsync_config =~ s/^PassCmd .*$/PassCmd "$password_command"/m; - my ($far) = $mbsync_config =~ /^IMAPStore (.*)$/m; - my ($near) = $mbsync_config =~ /^MaildirStore (.*)$/m; - - my $patterns = join "\n", map { "Pattern $_" } @$mailboxes; - my $mbsync_channels = <<EOF; -Channel mbsyncloop_priority -Far :$far: -Near :$near: -Sync Pull Push -$patterns - -Channel mbsyncloop_all -Far :$far: -Near :$near: -Sync Pull Push -Patterns * -EOF - - my $tmp = File::Temp->new(DIR => $RUN_DIR); - $tmp->print($mbsync_config); - $tmp->print("\n"); - $tmp->print($mbsync_channels); - $tmp->flush; - - $tmp -} - -sub loop { - my ($config_data, $mbsync_config, $goimapnotify_r) = @_; - - my $poll_interval = $config_data->{poll_interval} // 15 * 60; - my $last_all = 0; - $SIG{HUP} = sub { $last_all = 0 }; - while (1) { - my $now = time; - if (($now - $last_all) >= $poll_interval) { - sync( - $mbsync_config, - "mbsyncloop_all", - $config_data->{on_new_mail}, - ); - $last_all = $now; - } - if (idle($goimapnotify_r, $poll_interval)) { - sync( - $mbsync_config, - "mbsyncloop_priority", - $config_data->{on_new_mail}, - ); - } - } -} - -sub sync { - my ($config, $channel, $on_new_mail) = @_; - my $config_file = $config->filename; - while (1) { - my $status = system("mbsync -c '$config_file' $channel"); - if (!$status) { - system($on_new_mail) if defined $on_new_mail; - last; - } - sleep 5; - } -} - -sub idle { - my ($goimapnotify_r, $max_delay) = @_; - my $rin = ''; - vec($rin, fileno($goimapnotify_r), 1) = 1; - my $ready = select(my $rout = $rin, undef, undef, $max_delay); - return 0 if $ready == -1 && $! == POSIX::EINTR; - die "failed to read goimapnotify output: $!" if $ready == -1; - if ($ready) { - while (1) { - my $ready = select(my $rout = $rin, undef, undef, 0.01); - return 0 if $ready == -1 && $! == POSIX::EINTR; - die "failed to read goimapnotify output: $!" if $ready == -1; - last unless $ready; - sysread $goimapnotify_r, my $data, 4096; - } - return 1; - } - return 0; -} - -sub slurp { - my ($file) = @_; - local $/; - open my $fh, '<', $file or die "couldn't open $file: $!"; - <$fh> -} - -sub make_run_dir { - my $dir = "/run/user/$>"; - if (!-d $dir) { - $dir = File::Spec->tmpdir(); - } - unlink File::Spec->catfile($dir, "mbsyncloop"); - mkdir File::Spec->catfile($dir, "mbsyncloop"); - mkdir File::Spec->catfile($dir, "mbsyncloop", $$); - File::Spec->catfile($dir, "mbsyncloop", $$) -} - -sub cleanup { - kill KILL => -$PW_PID if $PW_PID; - kill KILL => -$GOIMAPNOTIFY_PID if $GOIMAPNOTIFY_PID; - File::Path::remove_tree($RUN_DIR) if $RUN_DIR; -} |