about summary refs log tree commit homepage
path: root/lib/PublicInbox
diff options
context:
space:
mode:
Diffstat (limited to 'lib/PublicInbox')
-rw-r--r--lib/PublicInbox/LEI.pm30
-rw-r--r--lib/PublicInbox/LeiExportKw.pm31
-rw-r--r--lib/PublicInbox/LeiForgetMailSync.pm6
-rw-r--r--lib/PublicInbox/LeiImport.pm7
-rw-r--r--lib/PublicInbox/LeiInput.pm2
-rw-r--r--lib/PublicInbox/LeiInspect.pm5
-rw-r--r--lib/PublicInbox/LeiLsMailSync.pm3
-rw-r--r--lib/PublicInbox/LeiMailSync.pm46
-rw-r--r--lib/PublicInbox/LeiNoteEvent.pm26
-rw-r--r--lib/PublicInbox/LeiRefreshMailSync.pm31
-rw-r--r--lib/PublicInbox/LeiStore.pm39
-rw-r--r--lib/PublicInbox/LeiToMail.pm6
12 files changed, 107 insertions, 125 deletions
diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm
index 41e761f8..053b6174 100644
--- a/lib/PublicInbox/LEI.pm
+++ b/lib/PublicInbox/LEI.pm
@@ -1442,20 +1442,16 @@ sub refresh_watches {
         }
 
         # add all known Maildir folders as implicit watches
-        my $sto = $lei->_lei_store;
-        my $renames = 0;
-        if (my $lms = $sto ? $sto->search->lms : undef) {
+        my $lms = $lei->lms;
+        if ($lms) {
+                $lms->lms_write_prepare;
                 for my $d ($lms->folders('maildir:')) {
                         substr($d, 0, length('maildir:')) = '';
-                        my $cd = canonpath_harder($d);
-                        my $f = "maildir:$cd";
 
                         # fixup old bugs while we're iterating:
-                        if ($d ne $cd) {
-                                $sto->ipc_do('lms_rename_folder',
-                                                "maildir:$d", $f);
-                                ++$renames;
-                        }
+                        my $cd = canonpath_harder($d);
+                        my $f = "maildir:$cd";
+                        $lms->rename_folder("maildir:$d", $f) if $d ne $cd;
                         next if $watches->{$f}; # may be set to pause
                         require PublicInbox::LeiWatch;
                         $watches->{$f} = PublicInbox::LeiWatch->new($f);
@@ -1463,7 +1459,6 @@ sub refresh_watches {
                         add_maildir_watch($cd, $cfg_f);
                 }
         }
-        $lei->sto_done_request if $renames;
         if ($old) { # cull old non-existent entries
                 for my $url (keys %$old) {
                         next if exists $seen{$url};
@@ -1490,13 +1485,12 @@ sub git_oid {
         git_sha(1, $eml);
 }
 
-sub lms { # read-only LeiMailSync
-        my ($lei) = @_;
-        my $lse = $lei->{lse} // do {
-                my $sto = $lei->{sto} // _lei_store($lei);
-                $sto ? $sto->search : undef
-        };
-        $lse ? $lse->lms : undef;
+sub lms {
+        my ($lei, $rw) = @_;
+        my $sto = $lei->{sto} // _lei_store($lei) // return;
+        require PublicInbox::LeiMailSync;
+        my $f = "$sto->{priv_eidx}->{topdir}/mail_sync.sqlite3";
+        (-f $f || $rw) ? PublicInbox::LeiMailSync->new($f) : undef;
 }
 
 sub sto_done_request { # only call this from lei-daemon process (not workers)
diff --git a/lib/PublicInbox/LeiExportKw.pm b/lib/PublicInbox/LeiExportKw.pm
index 8b8aa373..8c5fbc13 100644
--- a/lib/PublicInbox/LeiExportKw.pm
+++ b/lib/PublicInbox/LeiExportKw.pm
@@ -40,7 +40,7 @@ sub export_kw_md { # LeiMailSync->each_src callback
                         if (!unlink($src) and $! != ENOENT) {
                                 $lei->child_error(1, "E: unlink($src): $!");
                         }
-                        $lei->{sto}->ipc_do('lms_mv_src', "maildir:$mdir",
+                        $self->{lms}->mv_src("maildir:$mdir",
                                                 $oidbin, $id, $bn);
                         return; # success anyways if link(2) worked
                 } elsif ($! == EEXIST) { # lost race with lei/store?
@@ -55,7 +55,7 @@ sub export_kw_md { # LeiMailSync->each_src callback
         my $src = "$mdir/{".join(',', @try)."}/$$id";
         $lei->child_error(1, "link($src -> $dst) ($oidhex): $e");
         for (@try) { return if -e "$mdir/$_/$$id" }
-        $lei->{sto}->ipc_do('lms_clear_src', "maildir:$mdir", $id);
+        $self->{lms}->clear_src("maildir:$mdir", $id);
 }
 
 sub export_kw_imap { # LeiMailSync->each_src callback
@@ -67,18 +67,17 @@ sub export_kw_imap { # LeiMailSync->each_src callback
 # overrides PublicInbox::LeiInput::input_path_url
 sub input_path_url {
         my ($self, $input, @args) = @_;
-        my $lms = $self->{-lms_ro} //= $self->{lse}->lms;
+        $self->{lms}->lms_write_prepare;
         if ($input =~ /\Amaildir:(.+)/i) {
                 my $mdir = $1;
                 require PublicInbox::LeiToMail; # kw2suffix
-                $lms->each_src($input, \&export_kw_md, $self, $mdir);
+                $self->{lms}->each_src($input, \&export_kw_md, $self, $mdir);
         } elsif ($input =~ m!\Aimaps?://!i) {
                 my $uri = PublicInbox::URIimap->new($input);
                 my $mic = $self->{nwr}->mic_for_folder($uri);
-                $lms->each_src($$uri, \&export_kw_imap, $self, $mic);
+                $self->{lms}->each_src($$uri, \&export_kw_imap, $self, $mic);
                 $mic->expunge;
         } else { die "BUG: $input not supported" }
-        my $wait = $self->{lei}->{sto}->ipc_do('done');
 }
 
 sub lei_export_kw {
@@ -86,26 +85,25 @@ sub lei_export_kw {
         my $sto = $lei->_lei_store or return $lei->fail(<<EOM);
 lei/store uninitialized, see lei-import(1)
 EOM
-        my $lse = $sto->search;
-        my $lms = $lse->lms or return $lei->fail(<<EOM);
+        my $lms = $lei->lms or return $lei->fail(<<EOM);
 lei mail_sync uninitialized, see lei-import(1)
 EOM
-        my $opt = $lei->{opt};
-        if (defined(my $all = $opt->{all})) { # --all=<local|remote>
+        if (defined(my $all = $lei->{opt}->{all})) { # --all=<local|remote>
                 $lms->group2folders($lei, $all, \@folders) or return;
+                @folders = grep(/\A(?:maildir|imaps?):/i, @folders);
         } else {
                 my $err = $lms->arg2folder($lei, \@folders);
                 $lei->qerr(@{$err->{qerr}}) if $err->{qerr};
                 return $lei->fail($err->{fail}) if $err->{fail};
         }
-        my $self = bless { lse => $lse }, __PACKAGE__;
+        $lms->lms_pause;
+        my $self = bless { lse => $sto->search, lms => $lms }, __PACKAGE__;
         $lei->{opt}->{'mail-sync'} = 1; # for prepare_inputs
         $self->prepare_inputs($lei, \@folders) or return;
-        my $j = $opt->{jobs} // scalar(@{$self->{inputs}}) || 1;
         if (my @ro = grep(!/\A(?:maildir|imaps?):/i, @folders)) {
                 return $lei->fail("cannot export to read-only folders: @ro");
         }
-        my $m = $opt->{mode} // 'merge';
+        my $m = $lei->{opt}->{mode} // 'merge';
         if ($m eq 'merge') { # default
                 $self->{-merge_kw} = 1;
         } elsif ($m eq 'set') {
@@ -120,11 +118,9 @@ EOM
                 $self->{imap_mod_kw} = $net->can($self->{-merge_kw} ?
                                         'imap_add_kw' : 'imap_set_kw');
         }
-        undef $lms; # for fork
         my $ops = {};
-        $sto->write_prepare($lei);
         $lei->{auth}->op_merge($ops, $self) if $lei->{auth};
-        (my $op_c, $ops) = $lei->workers_start($self, $j, $ops);
+        (my $op_c, $ops) = $lei->workers_start($self, 1, $ops);
         $lei->{wq1} = $self;
         $lei->{-err_type} = 'non-fatal';
         net_merge_all_done($self) unless $lei->{auth};
@@ -133,8 +129,7 @@ EOM
 
 sub _complete_export_kw {
         my ($lei, @argv) = @_;
-        my $sto = $lei->_lei_store or return;
-        my $lms = $sto->search->lms or return;
+        my $lms = $lei->lms or return;
         my $match_cb = $lei->complete_url_prepare(\@argv);
         map { $match_cb->($_) } $lms->folders;
 }
diff --git a/lib/PublicInbox/LeiForgetMailSync.pm b/lib/PublicInbox/LeiForgetMailSync.pm
index 2b4e58a9..701f48d2 100644
--- a/lib/PublicInbox/LeiForgetMailSync.pm
+++ b/lib/PublicInbox/LeiForgetMailSync.pm
@@ -15,13 +15,11 @@ use PublicInbox::LeiExportKw;
 sub lei_forget_mail_sync {
         my ($lei, @folders) = @_;
         my $lms = $lei->lms or return;
-        my $sto = $lei->_lei_store or return; # may disappear due to race
-        $sto->write_prepare($lei);
+        $lms->lms_write_prepare;
         my $err = $lms->arg2folder($lei, \@folders);
         $lei->qerr(@{$err->{qerr}}) if $err->{qerr};
         return $lei->fail($err->{fail}) if $err->{fail};
-        $sto->ipc_do('lms_forget_folders', @folders);
-        $lei->sto_done_request;
+        $lms->forget_folders(@folders);
 }
 
 *_complete_forget_mail_sync = \&PublicInbox::LeiExportKw::_complete_export_kw;
diff --git a/lib/PublicInbox/LeiImport.pm b/lib/PublicInbox/LeiImport.pm
index b1cb3940..9084d771 100644
--- a/lib/PublicInbox/LeiImport.pm
+++ b/lib/PublicInbox/LeiImport.pm
@@ -36,7 +36,7 @@ sub pmdir_cb { # called via wq_io_do from LeiPmdir->each_mdir_fn
         my $kw = PublicInbox::MdirReader::flags2kw($fl);
         substr($folder, 0, 0) = 'maildir:'; # add prefix
         my $lse = $self->{lse} //= $self->{lei}->{sto}->search;
-        my $lms = $self->{-lms_ro} //= $lse->lms; # may be 0 or undef
+        my $lms = $self->{-lms_ro} //= $self->{lei}->lms; # may be 0 or undef
         my $oidbin = $lms ? $lms->name_oidbin($folder, $bn) : undef;
         my @docids = defined($oidbin) ? $lse->over->oidbin_exists($oidbin) : ();
         my $vmd = $self->{-import_kw} ? { kw => $kw } : undef;
@@ -83,7 +83,7 @@ sub do_import_index ($$@) {
                 # $j = $net->net_concurrency($j); TODO
                 if ($lei->{opt}->{incremental} // 1) {
                         $net->{incremental} = 1;
-                        $net->{-lms_ro} = $sto->search->lms // 0;
+                        $net->{-lms_ro} = $lei->lms // 0;
                         if ($self->{-import_kw} && $net->{-lms_ro} &&
                                         !$lei->{opt}->{'new-only'} &&
                                         $net->{imap_order}) {
@@ -120,8 +120,7 @@ sub _complete_import {
         my $match_cb = $lei->complete_url_prepare(\@argv);
         my @m = map { $match_cb->($_) } $lei->url_folder_cache->keys;
         my %f = map { $_ => 1 } @m;
-        my $sto = $lei->_lei_store;
-        if (my $lms = $sto ? $sto->search->lms : undef) {
+        if (my $lms = $lei->lms) {
                 @m = map { $match_cb->($_) } $lms->folders;
                 @f{@m} = @m;
         }
diff --git a/lib/PublicInbox/LeiInput.pm b/lib/PublicInbox/LeiInput.pm
index 372e0fe1..fe736981 100644
--- a/lib/PublicInbox/LeiInput.pm
+++ b/lib/PublicInbox/LeiInput.pm
@@ -360,7 +360,7 @@ $input is `eml', not --in-format=$in_fmt
                 # start watching Maildirs ASAP
                 if ($may_sync && $lei->{sto}) {
                         grep(!m!\Amaildir:/!i, @md) and die "BUG: @md (no pfx)";
-                        my $wait = $lei->{sto}->ipc_do('add_sync_folders', @md);
+                        $lei->lms(1)->lms_write_prepare->add_folders(@md);
                         $lei->refresh_watches;
                 }
         }
diff --git a/lib/PublicInbox/LeiInspect.pm b/lib/PublicInbox/LeiInspect.pm
index 2385f7f8..f06cea61 100644
--- a/lib/PublicInbox/LeiInspect.pm
+++ b/lib/PublicInbox/LeiInspect.pm
@@ -18,7 +18,7 @@ sub inspect_blob ($$) {
                 my $oidbin = pack('H*', $oidhex);
                 my @docids = $lse ? $lse->over->oidbin_exists($oidbin) : ();
                 $ent->{'lei/store'} = \@docids if @docids;
-                my $lms = $lse->lms;
+                my $lms = $lei->lms;
                 if (my $loc = $lms ? $lms->locations_for($oidbin) : undef) {
                         $ent->{'mail-sync'} = $loc;
                 }
@@ -29,8 +29,7 @@ sub inspect_blob ($$) {
 sub inspect_imap_uid ($$) {
         my ($lei, $uid_uri) = @_;
         my $ent = {};
-        my $lse = $lei->{lse} or return $ent;
-        my $lms = $lse->lms or return $ent;
+        my $lms = $lei->lms or return $ent;
         my $oidhex = $lms->imap_oid($lei, $uid_uri);
         if (ref(my $err = $oidhex)) { # art2folder error
                 $lei->qerr(@{$err->{qerr}}) if $err->{qerr};
diff --git a/lib/PublicInbox/LeiLsMailSync.pm b/lib/PublicInbox/LeiLsMailSync.pm
index 505c0b3f..2b167b1d 100644
--- a/lib/PublicInbox/LeiLsMailSync.pm
+++ b/lib/PublicInbox/LeiLsMailSync.pm
@@ -9,8 +9,7 @@ use PublicInbox::LeiMailSync;
 
 sub lei_ls_mail_sync {
         my ($lei, $filter) = @_;
-        my $sto = $lei->_lei_store or return;
-        my $lms = $sto->search->lms or return;
+        my $lms = $lei->lms or return;
         my $opt = $lei->{opt};
         my $re = $opt->{globoff} ? undef : $lei->glob2re($filter // '*');
         $re //= qr/\Q$filter\E/;
diff --git a/lib/PublicInbox/LeiMailSync.pm b/lib/PublicInbox/LeiMailSync.pm
index 8f584ccb..690c6477 100644
--- a/lib/PublicInbox/LeiMailSync.pm
+++ b/lib/PublicInbox/LeiMailSync.pm
@@ -5,6 +5,7 @@
 package PublicInbox::LeiMailSync;
 use strict;
 use v5.10.1;
+use parent qw(PublicInbox::Lock);
 use DBI;
 use PublicInbox::ContentHash qw(git_sha);
 use Carp ();
@@ -21,7 +22,7 @@ sub dbh_new {
                 sqlite_use_immediate_transaction => 1,
         });
         # no sqlite_unicode, here, all strings are binary
-        create_tables($dbh) if $rw;
+        create_tables($self, $dbh) if $rw;
         $dbh->do('PRAGMA journal_mode = WAL') if $creat;
         $dbh->do('PRAGMA case_sensitive_like = ON');
         $dbh;
@@ -29,13 +30,24 @@ sub dbh_new {
 
 sub new {
         my ($cls, $f) = @_;
-        bless { filename => $f, fmap => {} }, $cls;
+        bless {
+                filename => $f,
+                fmap => {},
+                lock_path => "$f.flock",
+        }, $cls;
 }
 
-sub lms_write_prepare { ($_[0]->{dbh} //= dbh_new($_[0], 1)) };
+sub lms_write_prepare { ($_[0]->{dbh} //= dbh_new($_[0], 1)); $_[0] }
+
+sub lms_pause {
+        my ($self) = @_;
+        $self->{fmap} = {};
+        delete $self->{dbh};
+}
 
 sub create_tables {
-        my ($dbh) = @_;
+        my ($self, $dbh) = @_;
+        my $lk = $self->lock_for_scope;
 
         $dbh->do(<<'');
 CREATE TABLE IF NOT EXISTS folders (
@@ -115,8 +127,15 @@ EOM
         $fid;
 }
 
+sub add_folders {
+        my ($self, @folders) = @_;
+        my $lk = $self->lock_for_scope;
+        for my $f (@folders) { $self->{fmap}->{$f} //= fid_for($self, $f, 1) }
+}
+
 sub set_src {
         my ($self, $oidbin, $folder, $id) = @_;
+        my $lk = $self->lock_for_scope;
         my $fid = $self->{fmap}->{$folder} //= fid_for($self, $folder, 1);
         my $sth;
         if (ref($id)) { # scalar name
@@ -134,6 +153,7 @@ INSERT OR IGNORE INTO blob2num (oidbin, fid, uid) VALUES (?, ?, ?)
 
 sub clear_src {
         my ($self, $folder, $id) = @_;
+        my $lk = $self->lock_for_scope;
         my $fid = $self->{fmap}->{$folder} //= fid_for($self, $folder, 1);
         my $sth;
         if (ref($id)) { # scalar name
@@ -152,6 +172,7 @@ DELETE FROM blob2num WHERE fid = ? AND uid = ?
 # Maildir-only
 sub mv_src {
         my ($self, $folder, $oidbin, $id, $newbn) = @_;
+        my $lk = $self->lock_for_scope;
         my $fid = $self->{fmap}->{$folder} //= fid_for($self, $folder, 1);
         my $sth = $self->{dbh}->prepare_cached(<<'');
 UPDATE blob2name SET name = ? WHERE fid = ? AND oidbin = ? AND name = ?
@@ -421,18 +442,23 @@ EOF
         $err;
 }
 
-sub forget_folder {
-        my ($self, $folder) = @_;
-        my $fid = delete($self->{fmap}->{$folder}) //
-                fid_for($self, $folder) // return;
-        for my $t (qw(blob2name blob2num folders)) {
-                $self->{dbh}->do("DELETE FROM $t WHERE fid = ?", undef, $fid);
+sub forget_folders {
+        my ($self, @folders) = @_;
+        my $lk = $self->lock_for_scope;
+        for my $folder (@folders) {
+                my $fid = delete($self->{fmap}->{$folder}) //
+                        fid_for($self, $folder) // next;
+                for my $t (qw(blob2name blob2num folders)) {
+                        $self->{dbh}->do("DELETE FROM $t WHERE fid = ?",
+                                        undef, $fid);
+                }
         }
 }
 
 # only used for changing canonicalization errors
 sub rename_folder {
         my ($self, $old, $new) = @_;
+        my $lk = $self->lock_for_scope;
         my $ofid = delete($self->{fmap}->{$old}) //
                 fid_for($self, $old) // return;
         eval {
diff --git a/lib/PublicInbox/LeiNoteEvent.pm b/lib/PublicInbox/LeiNoteEvent.pm
index 41415346..c03c5319 100644
--- a/lib/PublicInbox/LeiNoteEvent.pm
+++ b/lib/PublicInbox/LeiNoteEvent.pm
@@ -2,6 +2,7 @@
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 
 # internal command for dealing with inotify, kqueue vnodes, etc
+# it is a semi-persistent worker
 package PublicInbox::LeiNoteEvent;
 use strict;
 use v5.10.1;
@@ -12,11 +13,8 @@ our $to_flush; # { cfgpath => $lei }
 
 sub flush_lei ($) {
         my ($lei) = @_;
-        if (my $lne = delete $lei->{cfg}->{-lei_note_event}) {
-                $lne->wq_close(1, undef, $lei); # runs _lei_wq_eof;
-        } elsif ($lei->{sto}) { # lms_clear_src calls only:
-                $lei->sto_done_request;
-        }
+        my $lne = delete $lei->{cfg}->{-lei_note_event};
+        $lne->wq_close(1, undef, $lei) if $lne; # runs _lei_wq_eof;
 }
 
 # we batch up writes and flush every 5s (matching Linux default
@@ -38,14 +36,14 @@ sub note_event_arm_done ($) {
 sub eml_event ($$$$) {
         my ($self, $eml, $vmd, $state) = @_;
         my $sto = $self->{lei}->{sto};
-        my $lse = $self->{lse} //= $sto->search;
         if ($state =~ /\Aimport-(?:rw|ro)\z/) {
                 $sto->ipc_do('set_eml', $eml, $vmd);
         } elsif ($state =~ /\Aindex-(?:rw|ro)\z/) {
                 my $xoids = $self->{lei}->ale->xoids_for($eml);
                 $sto->ipc_do('index_eml_only', $eml, $vmd, $xoids);
         } elsif ($state =~ /\Atag-(?:rw|ro)\z/) {
-                my $c = $lse->kw_changed($eml, $vmd->{kw}, my $docids = []);
+                my $docids = [];
+                my $c = $self->{lse}->kw_changed($eml, $vmd->{kw}, $docids);
                 if (scalar @$docids) { # already in lei/store
                         $sto->ipc_do('set_eml_vmd', undef, $vmd, $docids) if $c;
                 } elsif (my $xoids = $self->{lei}->ale->xoids_for($eml)) {
@@ -69,21 +67,19 @@ sub lei_note_event {
         my $cfg = $lei->_lei_cfg or return; # gone (race)
         my $sto = $lei->_lei_store or return; # gone
         return flush_lei($lei) if $folder eq 'done'; # special case
-        my $lms = $sto->search->lms or return;
+        my $lms = $lei->lms or return;
+        $lms->lms_write_prepare if $new_cur eq ''; # for ->clear_src below
         my $err = $lms->arg2folder($lei, [ $folder ]);
         return if $err->{fail};
-        undef $lms;
         my $state = $cfg->get_1("watch.$folder", 'state') // 'tag-rw';
         return if $state eq 'pause';
+        return $lms->clear_src($folder, \$bn) if $new_cur eq '';
+        $lms->lms_pause;
         $lei->ale; # prepare
         $sto->write_prepare($lei);
-        if ($new_cur eq '') {
-                $sto->ipc_do('lms_clear_src', $folder, \$bn);
-                return note_event_arm_done($lei);
-        }
         require PublicInbox::MdirReader;
         my $self = $cfg->{-lei_note_event} //= do {
-                my $wq = bless {}, __PACKAGE__;
+                my $wq = bless { lms => $lms }, __PACKAGE__;
                 # MUAs such as mutt can trigger massive rename() storms so
                 # use all CPU power available:
                 my $jobs = $wq->detect_nproc // 1;
@@ -105,6 +101,8 @@ sub lei_note_event {
 sub ipc_atfork_child {
         my ($self) = @_;
         $self->{lei}->_lei_atfork_child(1); # persistent, for a while
+        $self->{lms}->lms_write_prepare;
+        $self->{lse} = $self->{lei}->{sto}->search;
         $self->SUPER::ipc_atfork_child;
 }
 
diff --git a/lib/PublicInbox/LeiRefreshMailSync.pm b/lib/PublicInbox/LeiRefreshMailSync.pm
index cdd99725..72b8fe63 100644
--- a/lib/PublicInbox/LeiRefreshMailSync.pm
+++ b/lib/PublicInbox/LeiRefreshMailSync.pm
@@ -11,9 +11,9 @@ use PublicInbox::LeiExportKw;
 use PublicInbox::InboxWritable qw(eml_from_path);
 use PublicInbox::Import;
 
-sub folder_missing {
+sub folder_missing { # may be called by LeiInput
         my ($self, $folder) = @_;
-        $self->{lei}->{sto}->ipc_do('lms_forget_folders', $folder);
+        $self->{lms}->forget_folders($folder);
 }
 
 sub prune_mdir { # lms->each_src callback
@@ -21,13 +21,13 @@ sub prune_mdir { # lms->each_src callback
         my @try = $$id =~ /:2,[a-zA-Z]*\z/ ? qw(cur new) : qw(new cur);
         for (@try) { return if -f "$mdir/$_/$$id" }
         # both tries failed
-        $self->{lei}->{sto}->ipc_do('lms_clear_src', "maildir:$mdir", $id);
+        $self->{lms}->clear_src("maildir:$mdir", $id);
 }
 
 sub prune_imap { # lms->each_src callback
         my ($oidbin, $uid, $self, $uids, $url) = @_;
         return if exists $uids->{$uid};
-        $self->{lei}->{sto}->ipc_do('lms_clear_src', $url, $uid);
+        $self->{lms}->clear_src($url, $uid);
 }
 
 # detects missed file moves
@@ -36,18 +36,16 @@ sub pmdir_cb { # called via LeiPmdir->each_mdir_fn
         my ($folder, $bn) = ($f =~ m!\A(.+?)/(?:new|cur)/([^/]+)\z!) or
                 die "BUG: $f was not from a Maildir?";
         substr($folder, 0, 0) = 'maildir:'; # add prefix
-        my $lms = $self->{-lms_ro} //= $self->{lei}->lms;
-        return if defined($lms->name_oidbin($folder, $bn));
+        return if defined($self->{lms}->name_oidbin($folder, $bn));
         my $eml = eml_from_path($f) // return;
         my $oidbin = $self->{lei}->git_oid($eml)->digest;
-        $self->{lei}->{sto}->ipc_do('lms_set_src', $oidbin, $folder, \$bn);
+        $self->{lms}->set_src($oidbin, $folder, \$bn);
 }
 
 sub input_path_url { # overrides PublicInbox::LeiInput::input_path_url
         my ($self, $input, @args) = @_;
-        my $lms = $self->{-lms_ro} //= $self->{lei}->lms;
         if ($input =~ /\Amaildir:(.+)/i) {
-                $lms->each_src($input, \&prune_mdir, $self, my $mdir = $1);
+                $self->{lms}->each_src($input, \&prune_mdir, $self, $1);
                 $self->{lse} //= $self->{lei}->{sto}->search;
                 # call pmdir_cb (via maildir_each_file -> each_mdir_fn)
                 PublicInbox::LeiInput::input_path_url($self, $input);
@@ -56,7 +54,8 @@ sub input_path_url { # overrides PublicInbox::LeiInput::input_path_url
                 if (my $mic = $self->{lei}->{net}->mic_for_folder($uri)) {
                         my $uids = $mic->search('UID 1:*');
                         $uids = +{ map { $_ => undef } @$uids };
-                        $lms->each_src($$uri, \&prune_imap, $self, $uids, $$uri)
+                        $self->{lms}->each_src($$uri, \&prune_imap, $self,
+                                                $uids, $$uri)
                 } else {
                         $self->folder_missing($$uri);
                 }
@@ -79,9 +78,9 @@ EOM
                 $lei->qerr(@{$err->{qerr}}) if $err->{qerr};
                 return $lei->fail($err->{fail}) if $err->{fail};
         }
-        undef $lms; # must be done before fork
+        $lms->lms_pause; # must be done before fork
         $sto->write_prepare($lei);
-        my $self = bless { missing_ok => 1 }, __PACKAGE__;
+        my $self = bless { missing_ok => 1, lms => $lms }, __PACKAGE__;
         $lei->{opt}->{'mail-sync'} = 1; # for prepare_inputs
         $self->prepare_inputs($lei, \@folders) or return;
         my $ops = {};
@@ -93,9 +92,15 @@ EOM
         $lei->wait_wq_events($op_c, $ops); # net_merge_all_done if !{auth}
 }
 
+sub ipc_atfork_child { # needed for PublicInbox::LeiPmdir
+        my ($self) = @_;
+        PublicInbox::LeiInput::input_only_atfork_child($self);
+        $self->{lms}->lms_write_prepare;
+        undef;
+}
+
 no warnings 'once';
 *_complete_refresh_mail_sync = \&PublicInbox::LeiExportKw::_complete_export_kw;
-*ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
 *net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;
 
 1;
diff --git a/lib/PublicInbox/LeiStore.pm b/lib/PublicInbox/LeiStore.pm
index 32f55abd..08add8f5 100644
--- a/lib/PublicInbox/LeiStore.pm
+++ b/lib/PublicInbox/LeiStore.pm
@@ -190,7 +190,7 @@ sub export1_kw_md ($$$$$) {
                                 syslog('warning', "unlink($src): $!");
                         }
                         # TODO: verify oidbin?
-                        lms_mv_src($self, "maildir:$mdir",
+                        $self->{lms}->mv_src("maildir:$mdir",
                                         $oidbin, \$orig, $bn);
                         return;
                 } elsif ($! == EEXIST) { # lost race with "lei export-kw"?
@@ -200,7 +200,7 @@ sub export1_kw_md ($$$$$) {
                 }
         }
         for (@try) { return if -e "$mdir/$_/$orig" };
-        lms_clear_src($self, "maildir:$mdir", \$orig);
+        $self->{lms}->clear_src("maildir:$mdir", \$orig);
 }
 
 sub sto_export_kw ($$$) {
@@ -255,7 +255,7 @@ sub remove_eml_vmd { # remove just the VMD
         \@docids;
 }
 
-sub _lms_rw ($) {
+sub _lms_rw ($) { # it is important to have eidx processes open before lms
         my ($self) = @_;
         my ($eidx, $tl) = eidx_init($self);
         $self->{lms} //= do {
@@ -267,37 +267,11 @@ sub _lms_rw ($) {
         };
 }
 
-sub lms_clear_src {
-        my ($self, $folder, $id) = @_;
-        _lms_rw($self)->clear_src($folder, $id);
-}
-
-sub lms_mv_src {
-        my ($self, $folder, $oidbin, $id, $newbn) = @_;
-        _lms_rw($self)->mv_src($folder, $oidbin, $id, $newbn);
-}
-
-sub lms_forget_folders {
-        my ($self, @folders) = @_;
-        my $lms = _lms_rw($self);
-        for my $f (@folders) { $lms->forget_folder($f) }
-}
-
-sub lms_rename_folder {
-        my ($self, $old, $new) = @_;
-        _lms_rw($self)->rename_folder($old, $new);
-}
-
 sub set_sync_info {
         my ($self, $oidhex, $folder, $id) = @_;
         _lms_rw($self)->set_src(pack('H*', $oidhex), $folder, $id);
 }
 
-sub lms_set_src {
-        my ($self, $oidbin, $folder, $id) = @_;
-        _lms_rw($self)->set_src($oidbin, $folder, $id);
-}
-
 sub _remove_if_local { # git->cat_async arg
         my ($bref, $oidhex, $type, $size, $self) = @_;
         $self->{im}->remove($bref) if $bref;
@@ -608,11 +582,4 @@ sub write_prepare {
         $lei->{sto} = $self;
 }
 
-# called by lei-daemon before lei->refresh_watches
-sub add_sync_folders {
-        my ($self, @folders) = @_;
-        my $lms = _lms_rw($self);
-        for my $f (@folders) { $lms->fid_for($f, 1) }
-}
-
 1;
diff --git a/lib/PublicInbox/LeiToMail.pm b/lib/PublicInbox/LeiToMail.pm
index 15729bda..d3253d9b 100644
--- a/lib/PublicInbox/LeiToMail.pm
+++ b/lib/PublicInbox/LeiToMail.pm
@@ -458,8 +458,10 @@ sub _pre_augment_maildir {
 
 sub clobber_dst_prepare ($;$) {
         my ($lei, $f) = @_;
-        my $wait = (defined($f) && $lei->{sto}) ?
-                        $lei->{sto}->ipc_do('lms_forget_folders', $f) : undef;
+        if (my $lms = defined($f) ? $lei->lms : undef) {
+                $lms->lms_write_prepare;
+                $lms->forget_folders($f);
+        }
         my $dedupe = $lei->{dedupe} or return;
         $dedupe->reset_dedupe if $dedupe->can('reset_dedupe');
 }