diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d78a750c5b..fb248dad33a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 0.4.6 + +* `Common` + * New annotation `Warning` has been added +* `Pagination` + * `Common` + * `Pagination` got new extension: `Pagination#isFirstPage` +* `Coroutines`: + * New extension `FlowCollector#invoke` has been added +* `Repos` + * `Common` + * `JVM` (and `Android` since `Android API 26`): + * `FileStandardKeyValueRepo` has been added + * Add several `typealias`es for each type of repos + ## 0.4.5 * `Android` diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Annotations.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Annotations.kt index a05f4360397..7873443768c 100644 --- a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Annotations.kt +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Annotations.kt @@ -17,3 +17,21 @@ package dev.inmo.micro_utils.common AnnotationTarget.TYPE_PARAMETER ) annotation class PreviewFeature + +@RequiresOptIn( + "This thing is marked as warned. See message of warn to get more info", + RequiresOptIn.Level.WARNING +) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.FIELD, + AnnotationTarget.PROPERTY, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.FUNCTION, + AnnotationTarget.TYPE, + AnnotationTarget.TYPEALIAS, + AnnotationTarget.TYPE_PARAMETER +) +annotation class Warning(val message: String) diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowCollectorInvoking.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowCollectorInvoking.kt new file mode 100644 index 00000000000..8ae54ebfc75 --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowCollectorInvoking.kt @@ -0,0 +1,5 @@ +package dev.inmo.micro_utils.coroutines + +import kotlinx.coroutines.flow.FlowCollector + +suspend inline operator fun FlowCollector.invoke(value: T) = emit(value) diff --git a/gradle.properties b/gradle.properties index 7e25e2c12df..5d73253b5e1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,5 +41,5 @@ dokka_version=1.4.0 # Project data group=dev.inmo -version=0.4.5 -android_code_version=9 +version=0.4.6 +android_code_version=10 diff --git a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt index adfa364f0fc..69bb2138c64 100644 --- a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt +++ b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt @@ -22,6 +22,12 @@ interface Pagination { val size: Int } +/** + * Logical shortcut for comparison that page is 0 + */ +inline val Pagination.isFirstPage + get() = page == 0 + /** * First number in index of objects. It can be used as offset for databases or other data sources */ diff --git a/repos/common/build.gradle b/repos/common/build.gradle index cd8a5615d26..3a41642d0cd 100644 --- a/repos/common/build.gradle +++ b/repos/common/build.gradle @@ -17,6 +17,11 @@ kotlin { } } + jvmMain { + dependencies { + api internalProject("micro_utils.common") + } + } androidMain { dependencies { api "androidx.core:core-ktx:$core_ktx_version" diff --git a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/OneToManyKeyValueRepo.kt b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/OneToManyKeyValueRepo.kt index 12137c06c74..b08e10ff5da 100644 --- a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/OneToManyKeyValueRepo.kt +++ b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/OneToManyKeyValueRepo.kt @@ -36,6 +36,7 @@ interface ReadOneToManyKeyValueRepo : Repo { } } } +typealias ReadKeyValuesRepo = ReadOneToManyKeyValueRepo interface WriteOneToManyKeyValueRepo : Repo { val onNewValue: Flow> @@ -53,6 +54,7 @@ interface WriteOneToManyKeyValueRepo : Repo { add(toSet) } } +typealias WriteKeyValuesRepo = WriteOneToManyKeyValueRepo suspend inline fun > REPO.add( keysAndValues: List>> @@ -87,6 +89,7 @@ suspend inline fun WriteOneToManyKeyValueRepo.set( ) = set(k, v.toList()) interface OneToManyKeyValueRepo : ReadOneToManyKeyValueRepo, WriteOneToManyKeyValueRepo +typealias KeyValuesRepo = OneToManyKeyValueRepo suspend inline fun WriteOneToManyKeyValueRepo.remove( keysAndValues: List>> diff --git a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartCRUDRepo.kt b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartCRUDRepo.kt index 28ad1ac34c7..df64fc47de7 100644 --- a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartCRUDRepo.kt +++ b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartCRUDRepo.kt @@ -10,6 +10,7 @@ interface ReadStandardCRUDRepo : Repo { suspend fun contains(id: IdType): Boolean suspend fun count(): Long } +typealias ReadCRUDRepo = ReadStandardCRUDRepo typealias UpdatedValuePair = Pair val UpdatedValuePair.id @@ -27,6 +28,7 @@ interface WriteStandardCRUDRepo : Repo { suspend fun update(values: List>): List suspend fun deleteById(ids: List) } +typealias WriteCRUDRepo = WriteStandardCRUDRepo suspend fun WriteStandardCRUDRepo.create( vararg values: InputValueType @@ -39,4 +41,5 @@ suspend fun WriteStandardCRUDRepo : ReadStandardCRUDRepo, - WriteStandardCRUDRepo \ No newline at end of file + WriteStandardCRUDRepo +typealias CRUDRepo = StandardCRUDRepo \ No newline at end of file diff --git a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt index e66ee7bd2f3..a9ae6adfd1c 100644 --- a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt +++ b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt @@ -12,6 +12,7 @@ interface ReadStandardKeyValueRepo : Repo { suspend fun contains(key: Key): Boolean suspend fun count(): Long } +typealias ReadKeyValueRepo = ReadStandardKeyValueRepo interface WriteStandardKeyValueRepo : Repo { val onNewValue: Flow> @@ -20,6 +21,7 @@ interface WriteStandardKeyValueRepo : Repo { suspend fun set(toSet: Map) suspend fun unset(toUnset: List) } +typealias WriteKeyValueRepo = WriteStandardKeyValueRepo suspend inline fun WriteStandardKeyValueRepo.set( vararg toSet: Pair @@ -33,4 +35,5 @@ suspend inline fun WriteStandardKeyValueRepo.unset( vararg k: Key ) = unset(k.toList()) -interface StandardKeyValueRepo : ReadStandardKeyValueRepo, WriteStandardKeyValueRepo \ No newline at end of file +interface StandardKeyValueRepo : ReadStandardKeyValueRepo, WriteStandardKeyValueRepo +typealias KeyValueRepo = StandardKeyValueRepo diff --git a/repos/common/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/FileStandardKeyValueRepo.kt b/repos/common/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/FileStandardKeyValueRepo.kt new file mode 100644 index 00000000000..99553103c5a --- /dev/null +++ b/repos/common/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/FileStandardKeyValueRepo.kt @@ -0,0 +1,171 @@ +package dev.inmo.micro_utils.repos + +import dev.inmo.micro_utils.common.Warning +import dev.inmo.micro_utils.pagination.* +import dev.inmo.micro_utils.pagination.utils.reverse +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import java.io.File +import java.nio.file.FileSystems +import java.nio.file.Path +import java.nio.file.StandardWatchEventKinds.* + +private inline val String.isAbsolute + get() = startsWith(File.separator) + +class FileReadStandardKeyValueRepo( + private val folder: File +) : ReadStandardKeyValueRepo { + init { + folder.mkdirs() + } + + override suspend fun get(k: String): File? { + val file = File(folder, k) + if (file.exists()) { + return file + } + return null + } + + override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult { + val count = count() + val resultPagination = if (reversed) pagination.reverse(count) else pagination + val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult() + if (reversed) { + filesPaths.reverse() + } + return filesPaths.map { File(folder, it) }.createPaginationResult( + resultPagination, + count + ) + } + + override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult { + val count = count() + val resultPagination = if (reversed) pagination.reverse(count) else pagination + val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult() + if (reversed) { + filesPaths.reverse() + } + return filesPaths.toList().createPaginationResult( + resultPagination, + count + ) + } + + override suspend fun keys( + v: File, + pagination: Pagination, + reversed: Boolean + ): PaginationResult { + val resultPagination = if (reversed) pagination.reverse(1L) else pagination + return if (resultPagination.isFirstPage) { + val fileSubpath = v.absolutePath.removePrefix(folder.absolutePath) + if (fileSubpath == v.absolutePath) { + emptyList() + } else { + listOf(fileSubpath) + } + } else { + emptyList() + }.createPaginationResult(resultPagination, 1L) + } + + override suspend fun contains(key: String): Boolean { + return File(folder, key).exists() + } + + override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L +} + +/** + * Files watching will not correctly works on Android with version of API lower than API 26 + */ +@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26") +class FileWriteStandardKeyValueRepo( + private val folder: File, + filesChangedProcessingScope: CoroutineScope? = null +) : WriteStandardKeyValueRepo { + private val _onNewValue = MutableSharedFlow>() + override val onNewValue: Flow> = _onNewValue.asSharedFlow() + private val _onValueRemoved = MutableSharedFlow() + override val onValueRemoved: Flow = _onValueRemoved.asSharedFlow() + + init { + folder.mkdirs() + filesChangedProcessingScope ?.let { + it.launch { + try { + val folderPath = folder.toPath() + while (isActive) { + val key = try { + folderPath.register(FileSystems.getDefault().newWatchService(), ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE) + } catch (e: Exception) { + // add verbose way to show that file watching is not working + return@launch + } + + for (event in key.pollEvents()) { + val relativeFilePath = (event.context() as? Path) ?: continue + val file = relativeFilePath.toFile() + val relativePath = file.toRelativeString(folder) + + when (event.kind()) { + ENTRY_CREATE, ENTRY_MODIFY -> { + launch { + _onNewValue.emit(relativePath to file) + } + } + ENTRY_DELETE -> { + launch { + _onValueRemoved.emit(relativePath) + } + } + } + } + + if (key.isValid || folder.exists()) { + continue + } + break + } + } catch (e: Throwable) { + // add verbose way to notify that this functionality is disabled + } + } + } + } + + override suspend fun set(toSet: Map) { + supervisorScope { + toSet.map { (filename, fileSource) -> + launch { + val file = File(folder, filename) + + file.delete() + fileSource.copyTo(file, overwrite = true) + _onNewValue.emit(filename to file) + } + } + }.joinAll() + } + + override suspend fun unset(toUnset: List) { + toUnset.forEach { + val file = File(folder, it) + if (file.exists()) { + file.delete() + _onValueRemoved.emit(it) + } + } + } +} + +@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26") +class FileStandardKeyValueRepo( + folder: File, + filesChangedProcessingScope: CoroutineScope? = null +) : StandardKeyValueRepo, + WriteStandardKeyValueRepo by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope), + ReadStandardKeyValueRepo by FileReadStandardKeyValueRepo(folder) \ No newline at end of file