package be.simplenotes.search import be.simplenotes.domain.model.PersistedNote import be.simplenotes.domain.usecases.search.NoteSearcher import be.simplenotes.domain.usecases.search.SearchTerms import be.simplenotes.search.utils.rmdir import org.apache.lucene.analysis.standard.StandardAnalyzer import org.apache.lucene.document.Document import org.apache.lucene.index.* import org.apache.lucene.search.IndexSearcher import org.apache.lucene.search.TermQuery import org.apache.lucene.store.Directory import org.apache.lucene.store.FSDirectory import org.slf4j.LoggerFactory import java.io.File import java.nio.file.Path import java.util.* class NoteSearcherImpl(basePath: Path = Path.of("/tmp", "lucene")) : NoteSearcher { private val baseFile = basePath.toFile() private val logger = LoggerFactory.getLogger(javaClass) // region utils private fun getDirectory(userId: Int): Directory { val index = File(baseFile, userId.toString()).toPath() return FSDirectory.open(index) } private fun indexSearcher(userId: Int): IndexSearcher { val directory = getDirectory(userId) val reader: IndexReader = DirectoryReader.open(directory) return IndexSearcher(reader) } private fun writer(userId: Int): IndexWriter { val dir = getDirectory(userId) val config = IndexWriterConfig(StandardAnalyzer()) return IndexWriter(dir, config) } // endregion override fun indexNote(userId: Int, note: PersistedNote) { logger.debug("Indexing note ${note.uuid} for user $userId") val doc = note.toDocument() with(writer(userId)) { addDocument(doc) commit() close() } } override fun indexNotes(userId: Int, notes: List) { logger.debug("Indexing notes for user $userId") val docs = notes.map { it.toDocument() } with(writer(userId)) { addDocuments(docs) commit() close() } } override fun deleteIndex(userId: Int, uuid: UUID) { logger.debug("Deleting index $uuid for user $userId") with(writer(userId)) { deleteDocuments(TermQuery(Term(uuidField, UuidFieldConverter.toDoc(uuid)))) commit() close() } } override fun updateIndex(userId: Int, note: PersistedNote) { logger.debug("Updating note ${note.uuid} for user $userId") deleteIndex(userId, note.uuid) indexNote(userId, note) } override fun search(userId: Int, terms: SearchTerms) = indexSearcher(userId).query { or { titleField eq terms.title } or { tagsField eq terms.tag } or { contentField eq terms.content } }.map(Document::toNoteMeta) override fun dropIndex(userId: Int) = rmdir(File(baseFile, userId.toString()).toPath()) override fun dropAll() = rmdir(baseFile.toPath()) }