From 512c44f08409d090d2ced0ac8da03143256aa2c9 Mon Sep 17 00:00:00 2001 From: Gustavo Sousa Date: Thu, 26 Dec 2024 09:37:05 -0300 Subject: [PATCH] commands/search/tag: Narrow search down to needed messages For some reason, tagging long threads is taking a lot of time in my system. It might be related to the python library for notmuch, since doing the same directly from notmuch CLI is usually fast. In some use cases (e.g. archiving), most of the messages in the thread might already contain the tags being added or removed. We can optimize for this case by narrowing down the search to only the messages that need the update. So, while the slow tagging operation should be properly investigated at the library level, we can proactively optimize the testquery, which is beneficial anyway. --- alot/commands/search.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/alot/commands/search.py b/alot/commands/search.py index ae757c16f..1eff4aa8a 100644 --- a/alot/commands/search.py +++ b/alot/commands/search.py @@ -188,10 +188,36 @@ async def apply(self, ui): if threadline_widget is None: return + tags = [x for x in self.tagsstring.split(',') if x] + testquery = searchbuffer.querystring thread = threadline_widget.get_thread() if not self.allm: testquery = "thread:%s" % thread.get_thread_id() + + # Reduce time for tagging long threads by selecting only messages that + # need the update. + if self.action == 'add': + testquery += ' AND NOT (' + testquery += ' AND '.join(f'tag:{x}' for x in tags) + testquery += ')' + elif self.action == 'remove': + testquery += ' AND (' + testquery += ' OR '.join(f'tag:{x}' for x in tags) + testquery += ')' + elif self.action == 'set': + # The "set" action means replacing the current set of tags with the + # one passed on the command. We could skip messages where the + # current set is the same as the one passed, but there is no + # efficient and simple way of doing that. + pass + elif self.action == 'toggle': + # The "toggle" affects all matched messages, so there is no further + # filtering to do here. + pass + else: + logging.warning('unandled action %s', self.action) + logging.debug('all? %s', self.allm) logging.debug('q: %s', testquery) @@ -217,8 +243,6 @@ def refresh(): ui.update() - tags = [x for x in self.tagsstring.split(',') if x] - try: if self.action == 'add': ui.dbman.tag(testquery, tags, remove_rest=False)