mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-06-26 11:37:55 +00:00
commit
5a69bd6c63
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,5 +1,20 @@
|
||||||
# Changelog
|
# 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
|
## 0.4.5
|
||||||
|
|
||||||
* `Android`
|
* `Android`
|
||||||
|
|
|
@ -17,3 +17,21 @@ package dev.inmo.micro_utils.common
|
||||||
AnnotationTarget.TYPE_PARAMETER
|
AnnotationTarget.TYPE_PARAMETER
|
||||||
)
|
)
|
||||||
annotation class PreviewFeature
|
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)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
|
|
||||||
|
suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)
|
|
@ -41,5 +41,5 @@ dokka_version=1.4.0
|
||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.4.5
|
version=0.4.6
|
||||||
android_code_version=9
|
android_code_version=10
|
||||||
|
|
|
@ -22,6 +22,12 @@ interface Pagination {
|
||||||
val size: Int
|
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
|
* First number in index of objects. It can be used as offset for databases or other data sources
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,6 +17,11 @@ kotlin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
api internalProject("micro_utils.common")
|
||||||
|
}
|
||||||
|
}
|
||||||
androidMain {
|
androidMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api "androidx.core:core-ktx:$core_ktx_version"
|
api "androidx.core:core-ktx:$core_ktx_version"
|
||||||
|
|
|
@ -36,6 +36,7 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||||
val onNewValue: Flow<Pair<Key, Value>>
|
val onNewValue: Flow<Pair<Key, Value>>
|
||||||
|
@ -53,6 +54,7 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||||
add(toSet)
|
add(toSet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||||
keysAndValues: List<Pair<Key, List<Value>>>
|
keysAndValues: List<Pair<Key, List<Value>>>
|
||||||
|
@ -87,6 +89,7 @@ suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||||
) = set(k, v.toList())
|
) = set(k, v.toList())
|
||||||
|
|
||||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||||
|
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||||
keysAndValues: List<Pair<Key, List<Value>>>
|
keysAndValues: List<Pair<Key, List<Value>>>
|
||||||
|
|
|
@ -10,6 +10,7 @@ interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
|
||||||
suspend fun contains(id: IdType): Boolean
|
suspend fun contains(id: IdType): Boolean
|
||||||
suspend fun count(): Long
|
suspend fun count(): Long
|
||||||
}
|
}
|
||||||
|
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
|
||||||
|
|
||||||
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
|
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
|
||||||
val <IdType> UpdatedValuePair<IdType, *>.id
|
val <IdType> UpdatedValuePair<IdType, *>.id
|
||||||
|
@ -27,6 +28,7 @@ interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||||
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
|
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
|
||||||
suspend fun deleteById(ids: List<IdType>)
|
suspend fun deleteById(ids: List<IdType>)
|
||||||
}
|
}
|
||||||
|
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
|
||||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
|
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
|
||||||
vararg values: InputValueType
|
vararg values: InputValueType
|
||||||
|
@ -39,4 +41,5 @@ suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectTyp
|
||||||
) = deleteById(ids.toList())
|
) = deleteById(ids.toList())
|
||||||
|
|
||||||
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
|
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
|
||||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
|
@ -12,6 +12,7 @@ interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||||
suspend fun contains(key: Key): Boolean
|
suspend fun contains(key: Key): Boolean
|
||||||
suspend fun count(): Long
|
suspend fun count(): Long
|
||||||
}
|
}
|
||||||
|
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
||||||
val onNewValue: Flow<Pair<Key, Value>>
|
val onNewValue: Flow<Pair<Key, Value>>
|
||||||
|
@ -20,6 +21,7 @@ interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
||||||
suspend fun set(toSet: Map<Key, Value>)
|
suspend fun set(toSet: Map<Key, Value>)
|
||||||
suspend fun unset(toUnset: List<Key>)
|
suspend fun unset(toUnset: List<Key>)
|
||||||
}
|
}
|
||||||
|
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
||||||
vararg toSet: Pair<Key, Value>
|
vararg toSet: Pair<Key, Value>
|
||||||
|
@ -33,4 +35,5 @@ suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
|
||||||
vararg k: Key
|
vararg k: Key
|
||||||
) = unset(k.toList())
|
) = unset(k.toList())
|
||||||
|
|
||||||
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
|
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
|
||||||
|
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
|
||||||
|
|
|
@ -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<String, File> {
|
||||||
|
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<File> {
|
||||||
|
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<String> {
|
||||||
|
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<String> {
|
||||||
|
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<String, File> {
|
||||||
|
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
|
||||||
|
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
|
||||||
|
private val _onValueRemoved = MutableSharedFlow<String>()
|
||||||
|
override val onValueRemoved: Flow<String> = _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<String, File>) {
|
||||||
|
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<String>) {
|
||||||
|
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<String, File>,
|
||||||
|
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
|
||||||
|
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder)
|
Loading…
Reference in New Issue
Block a user