From 619b9d46b91cdf0f523b3804ec9a07b0f1ba4efe Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 16 Jun 2016 22:45:28 +0000 Subject: watch: introduce watch directive This will allow users to run importers off existing mail accounts where they may not have access to run -mda. Currently, we only support Maildirs, but IMAP ought to be doable. --- lib/PublicInbox/WatchMaildir.pm | 141 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 lib/PublicInbox/WatchMaildir.pm (limited to 'lib/PublicInbox/WatchMaildir.pm') diff --git a/lib/PublicInbox/WatchMaildir.pm b/lib/PublicInbox/WatchMaildir.pm new file mode 100644 index 00000000..b23556ae --- /dev/null +++ b/lib/PublicInbox/WatchMaildir.pm @@ -0,0 +1,141 @@ +# Copyright (C) 2016 all contributors +# License: AGPL-3.0+ +package PublicInbox::WatchMaildir; +use strict; +use warnings; +use Email::MIME; +use Email::MIME::ContentType; +$Email::MIME::ContentType::STRICT_PARAMS = 0; # user input is imperfect +use PublicInbox::Git; +use PublicInbox::Import; +use PublicInbox::MDA; + +sub new { + my ($class, $config) = @_; + my (%mdmap, @mdir); + foreach my $k (keys %$config) { + $k =~ /\Apublicinbox\.([^\.]+)\.watch\z/ or next; + my $name = $1; + my $watch = $config->{$k}; + if ($watch =~ s/\Amaildir://) { + $watch =~ s!/+\z!!; + my $inbox = $config->lookup_name($name); + if (my $wm = $inbox->{watchheader}) { + my ($k, $v) = split(/:/, $wm, 2); + $inbox->{-watchheader} = [ $k, qr/\Q$v\E/ ]; + } + my $new = "$watch/new"; + my $cur = "$watch/cur"; + push @mdir, $new, $cur; + $mdmap{$new} = $inbox; + $mdmap{$cur} = $inbox; + } else { + warn "watch unsupported: $k=$watch\n"; + } + } + return unless @mdir; + + my $mdre = join('|', map { quotemeta($_) } @mdir); + $mdre = qr!\A($mdre)/!; + bless { + mdmap => \%mdmap, + mdir => \@mdir, + mdre => $mdre, + importers => {}, + }, $class; +} + +sub _try_fsn_paths { + my ($self, $paths) = @_; + _try_path($self, $_->{path}) foreach @$paths; + $_->done foreach values %{$self->{importers}}; +} + +sub _try_path { + my ($self, $path) = @_; + if ($path !~ $self->{mdre}) { + warn "unrecognized path: $path\n"; + return; + } + my $inbox = $self->{mdmap}->{$1}; + unless ($inbox) { + warn "unmappable dir: $1\n"; + return; + } + my $im = $inbox->{-import} ||= eval { + my $git = $inbox->git; + my $name = $inbox->{name}; + my $addr = $inbox->{-primary_address}; + PublicInbox::Import->new($git, $name, $addr); + }; + $self->{importers}->{"$im"} = $im; + my $mime; + if (open my $fh, '<', $path) { + local $/; + my $str = <$fh>; + $str or return; + $mime = Email::MIME->new(\$str); + } elsif ($!{ENOENT}) { + return; + } else { + warn "failed to open $path: $!\n"; + return; + } + + $mime->header_set($_) foreach @PublicInbox::MDA::BAD_HEADERS; + my $wm = $inbox->{-watchheader}; + if ($wm) { + my $v = $mime->header_obj->header_raw($wm->[0]); + unless ($v && $v =~ $wm->[1]) { + warn "$wm->[0] failed to match $wm->[1]\n"; + return; + } + } + my $f = $inbox->{filter}; + if ($f && $f =~ /::/) { + eval "require $f"; + if ($@) { + warn $@; + } else { + $f = $f->new; + $mime = $f->scrub($mime); + } + } + $mime or return; + my $mid = $mime->header_obj->header_raw('Message-Id'); + $im->add($mime); +} + +sub watch { + my ($self) = @_; + my $cb = sub { _try_fsn_paths($self, \@_) }; + my $mdir = $self->{mdir}; + + require Filesys::Notify::Simple; + my $watcher = Filesys::Notify::Simple->new($mdir); + $watcher->wait($cb) while (1); +} + +sub scan { + my ($self) = @_; + my $mdir = $self->{mdir}; + foreach my $dir (@$mdir) { + my $ok = opendir(my $dh, $dir); + unless ($ok) { + warn "failed to open $dir: $!\n"; + next; + } + while (my $fn = readdir($dh)) { + next unless $fn =~ /\A[a-zA-Z0-9][\w:,=\.]+\z/; + $fn = "$dir/$fn"; + if (-f $fn) { + _try_path($self, $fn); + } else { + warn "not a file: $fn\n"; + } + } + closedir $dh; + } +} + +1; -- cgit v1.2.3-24-ge0c7