import os\r
import re\r
import shutil\r
+import time\r
from datetime import datetime\r
from typing import List, Literal, Set, Tuple\r
\r
from whoosh.analysis import CharsetFilter, StemmingAnalyzer\r
from whoosh.fields import DATETIME, ID, KEYWORD, TEXT, SchemaClass\r
from whoosh.highlight import ContextFragmenter, WholeFragmenter\r
-from whoosh.index import Index\r
+from whoosh.index import Index, LockError\r
from whoosh.qparser import MultifieldParser\r
from whoosh.qparser.dateparse import DateParserPlugin\r
from whoosh.query import Every\r
f"'{self.storage_path}' is not a valid directory."\r
)\r
self.index = self._load_index()\r
- self._sync_index(optimize=True)\r
+ self._sync_index_with_retry(optimize=True)\r
\r
def create(self, data: NoteCreate) -> Note:\r
"""Create a new note."""\r
limit: int = None,\r
) -> Tuple[SearchResult, ...]:\r
"""Search the index for the given term."""\r
- self._sync_index()\r
+ self._sync_index_with_retry()\r
term = self._pre_process_search_term(term)\r
with self.index.searcher() as searcher:\r
# Parse Query\r
def get_tags(self) -> list[str]:\r
"""Return a list of all indexed tags. Note: Tags no longer in use will\r
only be cleared when the index is next optimized."""\r
- self._sync_index()\r
+ self._sync_index_with_retry()\r
with self.index.reader() as reader:\r
tags = reader.field_terms("tags")\r
return [tag for tag in tags]\r
)\r
logger.info(f"'{filename}' added to index")\r
writer.commit(optimize=optimize)\r
+ logger.info("Index synchronized")\r
+\r
+ def _sync_index_with_retry(\r
+ self,\r
+ optimize: bool = False,\r
+ clean: bool = False,\r
+ max_retries: int = 8,\r
+ retry_delay: float = 0.25,\r
+ ) -> None:\r
+ for _ in range(max_retries):\r
+ try:\r
+ self._sync_index(optimize=optimize, clean=clean)\r
+ return\r
+ except LockError:\r
+ logger.warning(f"Index locked, retrying in {retry_delay}s")\r
+ time.sleep(retry_delay)\r
+ logger.error(f"Failed to sync index after {max_retries} retries")\r
\r
@classmethod\r
def _pre_process_search_term(cls, term):\r