@@ -6,18 +6,22 @@ import dev.paulee.api.data.*
66import dev.paulee.api.data.provider.IStorageProvider
77import dev.paulee.api.data.provider.ProviderStatus
88import dev.paulee.api.data.provider.QueryOrder
9+ import dev.paulee.api.data.provider.StorageType
910import dev.paulee.api.internal.Embedding
1011import dev.paulee.core.data.analysis.Indexer
1112import dev.paulee.core.data.model.DataPool
1213import dev.paulee.core.data.provider.EmbeddingProvider
1314import dev.paulee.core.data.provider.StorageProvider
1415import dev.paulee.core.splitStr
1516import kotlinx.coroutines.Dispatchers
17+ import kotlinx.coroutines.sync.Mutex
18+ import kotlinx.coroutines.sync.withLock
1619import kotlinx.coroutines.withContext
1720import org.slf4j.LoggerFactory.getLogger
1821import java.io.IOException
1922import java.nio.file.Path
2023import java.time.Duration
24+ import java.util.concurrent.ConcurrentHashMap
2125import kotlin.coroutines.cancellation.CancellationException
2226import kotlin.io.path.*
2327import kotlin.math.ceil
@@ -49,7 +53,9 @@ object DataServiceImpl : IDataService {
4953
5054 private val storageProvider = mutableMapOf<String , IStorageProvider >()
5155
52- private val dataPools = mutableMapOf<String , DataPool >()
56+ private val dataPools = ConcurrentHashMap <String , DataPool >()
57+
58+ private val mutex = Mutex ()
5359
5460 init {
5561 loadDataPools(FileService .dataDir)
@@ -100,7 +106,7 @@ object DataServiceImpl : IDataService {
100106 currentProvider.streamData(name)
101107 .chunked(BATCH_SIZE )
102108 .forEach { entries ->
103- dataPool.indexer.indexEntries(name, entries)
109+ dataPool.indexer? .indexEntries(name, entries)
104110
105111 processedBatches++
106112
@@ -114,7 +120,7 @@ object DataServiceImpl : IDataService {
114120 }
115121 }
116122
117- dataPool.indexer.finish()
123+ dataPool.indexer? .finish()
118124 EmbeddingProvider .finish()
119125
120126 dataPools[dataInfo.name] = dataPool
@@ -133,6 +139,84 @@ object DataServiceImpl : IDataService {
133139 }
134140 }
135141
142+ @OptIn(ExperimentalPathApi ::class )
143+ override suspend fun deleteDataPool (name : String ): Boolean = withContext(Dispatchers .IO ) {
144+ val (pool, wasCurrent, externalProvider) = mutex.withLock {
145+ val pool = dataPools.remove(name) ? : return @withLock null
146+
147+ val isCurrent = currentPool == name
148+
149+ if (isCurrent) {
150+ currentPool = null
151+ currentField = null
152+ }
153+
154+ val externalProvider = storageProvider.remove(name)
155+
156+ Triple (pool, isCurrent, externalProvider)
157+ } ? : return @withContext false
158+
159+ runCatching { pool.storageProvider?.close() }
160+ .onFailure { e -> logger.error(" Failed to close storage provider for $name ." , e) }
161+
162+ runCatching { pool.indexer?.close() }
163+ .onFailure { e -> logger.error(" Failed to close indexer for $name ." , e) }
164+
165+ externalProvider?.let {
166+ runCatching { it.close() }
167+ .onFailure { e -> logger.error(" Failed to close external storage provider for $name ." , e) }
168+ }
169+
170+ val poolPath = FileService .dataDir.resolve(name)
171+
172+ if (poolPath.notExists()) return @withContext true
173+
174+ runCatching { poolPath.deleteRecursively() }
175+ .onFailure { e ->
176+ logger.error(" Failed to delete directory for $name ." , e)
177+ return @withContext false
178+ }
179+
180+ logger.info(" Deleted data pool $name ." )
181+
182+ if (wasCurrent && dataPools.isNotEmpty()) {
183+ mutex.withLock {
184+ val newPool = dataPools.entries.firstOrNull() ? : return @withLock
185+
186+ currentPool = newPool.key
187+ currentField = newPool.value.defaultClass
188+ }
189+ }
190+
191+ true
192+ }
193+
194+ override suspend fun rebuildDataPool (dataInfo : DataInfo , onProgress : (progress: Int ) -> Unit ): Boolean {
195+ logger.info(" Rebuilding data pool ${dataInfo.name} ." )
196+
197+ val (oldPool, oldField) = mutex.withLock { currentPool to currentField }
198+
199+ var info = dataInfo
200+
201+ if (info.storageType == StorageType .SQLITE ) {
202+ logger.warn(" ${info.name} uses deprecated SQLite storage type. Replacing with default storage type." )
203+ info = info.copy(storageType = StorageType .Default )
204+ }
205+
206+ if (! deleteDataPool(info.name)) return false
207+
208+ val created = createDataPool(info, onProgress)
209+
210+ if (created && oldPool == info.name) {
211+ mutex.withLock {
212+ currentPool = oldPool
213+ currentField = oldField
214+ }
215+ }
216+
217+ return created
218+ }
219+
136220 @OptIn(ExperimentalPathApi ::class )
137221 fun loadDataPools (path : Path ) {
138222 if (path.notExists()) {
@@ -153,15 +237,25 @@ object DataServiceImpl : IDataService {
153237 val dataInfo = FileService .fromJson(runCatching { jsonFile.readText() }.getOrDefault(" " ))
154238 ? : return @forEachDirectoryEntry
155239
240+ val infoName = dataInfo.name
241+
242+ if (dataInfo.storageType == StorageType .SQLITE ) {
243+ logger.warn(" Data pool '$infoName ' uses deprecated SQLite storage type. Skipping." )
244+
245+ dataPools[infoName] = DataPool (null , dataInfo, null )
246+
247+ return @forEachDirectoryEntry
248+ }
249+
156250 val storageProvider = StorageProvider .of(dataInfo.storageType)
157251
158252 if (storageProvider.init (dataInfo, child) == ProviderStatus .Exists ) {
159- dataPools[dataInfo.name ] =
253+ dataPools[infoName ] =
160254 DataPool (Indexer (child.resolve(" index" ), dataInfo), dataInfo, storageProvider)
161255
162- logger.info(" Loaded ${dataInfo.name} data pool." )
256+ logger.info(" Loaded $infoName data pool." )
163257 } else {
164- logger.info(" Deleting invalid or empty data pool directory '${dataInfo.name} '." )
258+ logger.info(" Deleting invalid or empty data pool directory '$infoName '." )
165259
166260 runCatching { child.deleteRecursively() }
167261 .onFailure { e -> logger.error(" Failed to delete directory ${child.fileName} ." , e) }
@@ -210,14 +304,15 @@ object DataServiceImpl : IDataService {
210304
211305 val dataPool = this .dataPools[this .currentPool] ? : return emptyList()
212306
307+ if (dataPool.storageProvider == null ) return emptyList()
308+
213309 val fieldExists = dataPool.dataInfo.sources
214310 .firstOrNull { it.name == current }
215311 ?.fields
216312 ?.any { it.name == field } == true
217313
218314 if (! fieldExists) return emptyList()
219315
220-
221316 return dataPool.storageProvider.suggestions(current, field, value, 6 )
222317 }
223318
@@ -236,6 +331,8 @@ object DataServiceImpl : IDataService {
236331
237332 val dataPool = this .dataPools[this .currentPool] ? : return Pair (emptyList(), emptyMap())
238333
334+ if (dataPool.storageProvider == null ) return Pair (emptyList(), emptyMap())
335+
239336 val (filterQuery, filter) = this .getPreFilter(query)
240337
241338 val indexResult = dataPool.search(this .handleReplacements(dataPool.metadata, filterQuery), isSemantic)
@@ -279,6 +376,8 @@ object DataServiceImpl : IDataService {
279376
280377 val dataPool = this .dataPools[this .currentPool] ? : return Triple (- 1 , - 1 , emptySet())
281378
379+ if (dataPool.storageProvider == null ) return Triple (- 1 , - 1 , emptySet())
380+
282381 val (filterQuery, filter) = this .getPreFilter(query)
283382
284383 val indexResult = dataPool.search(handleReplacements(dataPool.metadata, filterQuery), isSemantic)
@@ -298,8 +397,8 @@ object DataServiceImpl : IDataService {
298397 override fun createStorageProvider (infoName : String , path : Path ): IStorageProvider ? {
299398 val dataInfo = this .dataPools[infoName]?.dataInfo ? : return null
300399
301- if (this .storageProvider[infoName] == null ) this .storageProvider[infoName] =
302- StorageProvider .of(dataInfo.storageType)
400+ if (this .storageProvider[infoName] == null )
401+ this .storageProvider[infoName] = StorageProvider .of(dataInfo.storageType)
303402
304403 val provider = this .storageProvider[infoName]
305404
@@ -327,8 +426,8 @@ object DataServiceImpl : IDataService {
327426 this .storageProvider.forEach { it.value.close() }
328427
329428 this .dataPools.values.forEach {
330- it.storageProvider.close()
331- it.indexer.close()
429+ it.storageProvider? .close()
430+ it.indexer? .close()
332431 }
333432
334433 EmbeddingProvider .close()
@@ -337,6 +436,8 @@ object DataServiceImpl : IDataService {
337436 private fun handleReplacements (replacements : Map <String , Any >, query : String ): String {
338437 val dataPool = this .dataPools[this .currentPool] ? : return query
339438
439+ if (dataPool.storageProvider == null ) return query
440+
340441 return variantPattern.replace(query) {
341442 val key = it.groupValues[1 ]
342443 val value = it.groupValues[2 ]
@@ -356,6 +457,8 @@ object DataServiceImpl : IDataService {
356457 private fun getPreFilter (query : String ): Pair <String , List <String >> {
357458 val dataPool = this .dataPools[this .currentPool] ? : return Pair (query, emptyList())
358459
460+ if (dataPool.storageProvider == null ) return Pair (query, emptyList())
461+
359462 val filters = preFilterPattern.findAll(query).map { it.value }.toSet()
360463
361464 val queryWithoutFilter = filters.fold(query) { acc, filter -> acc.replace(filter, " " ) }.trim()
0 commit comments