diff --git a/kstore-file/api/android/kstore-file.api b/kstore-file/api/android/kstore-file.api index 30bf03b..1112cbd 100644 --- a/kstore-file/api/android/kstore-file.api +++ b/kstore-file/api/android/kstore-file.api @@ -1,5 +1,5 @@ public final class io/github/xxfast/kstore/file/FileCodec : io/github/xxfast/kstore/Codec { - public fun (Ljava/lang/String;Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;)V + public fun (Lokio/Path;Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;)V public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun encode (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -9,8 +9,8 @@ public final class io/github/xxfast/kstore/file/extensions/KVersionedStoreKt { } public final class io/github/xxfast/kstore/file/extensions/VersionedCodec : io/github/xxfast/kstore/Codec { - public fun (Ljava/lang/String;ILkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function2;)V - public synthetic fun (Ljava/lang/String;ILkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lokio/Path;ILkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lokio/Path;ILkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun encode (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/kstore-file/api/desktop/kstore-file.api b/kstore-file/api/desktop/kstore-file.api index 30bf03b..1112cbd 100644 --- a/kstore-file/api/desktop/kstore-file.api +++ b/kstore-file/api/desktop/kstore-file.api @@ -1,5 +1,5 @@ public final class io/github/xxfast/kstore/file/FileCodec : io/github/xxfast/kstore/Codec { - public fun (Ljava/lang/String;Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;)V + public fun (Lokio/Path;Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;)V public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun encode (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -9,8 +9,8 @@ public final class io/github/xxfast/kstore/file/extensions/KVersionedStoreKt { } public final class io/github/xxfast/kstore/file/extensions/VersionedCodec : io/github/xxfast/kstore/Codec { - public fun (Ljava/lang/String;ILkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function2;)V - public synthetic fun (Ljava/lang/String;ILkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lokio/Path;ILkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lokio/Path;ILkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun encode (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/kstore-file/build.gradle.kts b/kstore-file/build.gradle.kts index afb074c..77254e9 100644 --- a/kstore-file/build.gradle.kts +++ b/kstore-file/build.gradle.kts @@ -88,7 +88,7 @@ kotlin { val commonMain by getting { dependencies { implementation(project(":kstore")) - implementation(libs.okio) + api(libs.okio) implementation(libs.kotlinx.coroutines) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json.okio) diff --git a/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/FileCodec.kt b/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/FileCodec.kt index 6e24e57..cc7bed1 100644 --- a/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/FileCodec.kt +++ b/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/FileCodec.kt @@ -18,31 +18,26 @@ import kotlinx.serialization.json.okio.decodeFromBufferedSource as decode import kotlinx.serialization.json.okio.encodeToBufferedSink as encode public inline fun FileCodec( - filePath: String, + file: Path, json: Json = DefaultJson, ): FileCodec = FileCodec( - filePath = filePath, + file = file, json = json, serializer = json.serializersModule.serializer(), ) @OptIn(ExperimentalSerializationApi::class) public class FileCodec( - filePath: String, + private val file: Path, private val json: Json, private val serializer: KSerializer, ) : Codec { - private val path: Path = filePath.toPath() - override suspend fun decode(): T? = - try { json.decode(serializer, FILE_SYSTEM.source(path).buffer()) } + try { json.decode(serializer, FILE_SYSTEM.source(file).buffer()) } catch (e: FileNotFoundException) { null } override suspend fun encode(value: T?) { - val parentFolder: Path? = path.parent - if (parentFolder != null && !FILE_SYSTEM.exists(parentFolder)) - FILE_SYSTEM.createDirectories(parentFolder, mustCreate = false) - if (value != null) FILE_SYSTEM.sink(path).buffer().use { json.encode(serializer, value, it) } - else FILE_SYSTEM.delete(path) + if (value != null) FILE_SYSTEM.sink(file).buffer().use { json.encode(serializer, value, it) } + else FILE_SYSTEM.delete(file) } } diff --git a/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/KStore.kt b/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/KStore.kt index 55d7e81..80885a8 100644 --- a/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/KStore.kt +++ b/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/KStore.kt @@ -4,11 +4,12 @@ import io.github.xxfast.kstore.DefaultJson import io.github.xxfast.kstore.KStore import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import okio.Path /** * Creates a store with [FileCodec] * - * @param filePath path to the file that is managed by this store + * @param file path to the file that is managed by this store * @param default returns this value if the file is not found. defaults to null * @param enableCache maintain a cache. If set to false, it always reads from disk * @param json Serializer to use. defaults to [DefaultJson] @@ -16,12 +17,12 @@ import kotlinx.serialization.json.Json * @return store that contains a value of type [T] */ public inline fun storeOf( - filePath: String, + file: Path, default: T? = null, enableCache: Boolean = true, json: Json = DefaultJson, ): KStore = KStore( default = default, enableCache = enableCache, - codec = FileCodec(filePath, json) + codec = FileCodec(file, json) ) diff --git a/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/extensions/KListStore.kt b/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/extensions/KListStore.kt index 7a8057d..4fa3d9c 100644 --- a/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/extensions/KListStore.kt +++ b/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/extensions/KListStore.kt @@ -6,11 +6,12 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterNotNull import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import okio.Path /** * Creates a store that contains a list * - * @param filePath path to the file that is managed by this store + * @param file path to the file that is managed by this store * @param default returns this value if the file is not found. defaults to empty list * @param enableCache maintain a cache. If set to false, it always reads from disk * @param json Serializer to use. Defaults serializer ignores unknown keys and encodes the defaults @@ -18,9 +19,9 @@ import kotlinx.serialization.json.Json * @return store that contains a list of type [T] */ public inline fun listStoreOf( - filePath: String, + file: Path, default: List = emptyList(), enableCache: Boolean = true, json: Json = Json { ignoreUnknownKeys = true; encodeDefaults = true }, ): KStore> = - storeOf(filePath, default, enableCache, json) + storeOf(file, default, enableCache, json) diff --git a/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/extensions/KVersionedStore.kt b/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/extensions/KVersionedStore.kt index d1a8cbe..81628e1 100644 --- a/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/extensions/KVersionedStore.kt +++ b/kstore-file/src/commonMain/kotlin/io/github/xxfast/kstore/file/extensions/KVersionedStore.kt @@ -33,14 +33,14 @@ import kotlinx.serialization.json.okio.encodeToBufferedSink as encode * @return store that contains a value of type [T] */ public inline fun storeOf( - filePath: String, + file: Path, version: Int, default: T? = null, enableCache: Boolean = true, json: Json = Json { ignoreUnknownKeys = true; encodeDefaults = true }, noinline migration: Migration = DefaultMigration(default), ): KStore { - val codec: Codec = VersionedCodec(filePath, version, json, json.serializersModule.serializer(), migration) + val codec: Codec = VersionedCodec(file, version, json, json.serializersModule.serializer(), migration) return KStore(default, enableCache, codec) } @@ -51,24 +51,23 @@ public typealias Migration = (version: Int?, JsonElement?) -> T? @OptIn(ExperimentalSerializationApi::class) public class VersionedCodec( - filePath: String, + private val file: Path, private val version: Int = 0, private val json: Json, private val serializer: KSerializer, private val migration: Migration, ): Codec { - private val dataPath: Path = filePath.toPath() - private val versionPath: Path = "$filePath.version".toPath() // TODO: Save to file metadata instead + private val versionPath: Path = "$${file.name}.version".toPath() // TODO: Save to file metadata instead override suspend fun decode(): T? = try { - json.decode(serializer, FILE_SYSTEM.source(dataPath).buffer()) + json.decode(serializer, FILE_SYSTEM.source(file).buffer()) } catch (e: SerializationException) { val previousVersion: Int = if (FILE_SYSTEM.exists(versionPath)) json.decode(Int.serializer(), FILE_SYSTEM.source(versionPath).buffer()) else 0 - val data: JsonElement = json.decode(FILE_SYSTEM.source(dataPath).buffer()) + val data: JsonElement = json.decode(FILE_SYSTEM.source(file).buffer()) migration(previousVersion, data) } catch (e: FileNotFoundException) { null @@ -77,10 +76,10 @@ public class VersionedCodec( override suspend fun encode(value: T?) { if (value != null) { FILE_SYSTEM.sink(versionPath).buffer().use { json.encode(Int.serializer(), version, it) } - FILE_SYSTEM.sink(dataPath).buffer().use { json.encode(serializer, value, it) } + FILE_SYSTEM.sink(file).buffer().use { json.encode(serializer, value, it) } } else { FILE_SYSTEM.delete(versionPath) - FILE_SYSTEM.delete(dataPath) + FILE_SYSTEM.delete(file) } } } diff --git a/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/FileCodecTests.kt b/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/FileCodecTests.kt index 10a9eb6..e659b0f 100644 --- a/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/FileCodecTests.kt +++ b/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/FileCodecTests.kt @@ -8,27 +8,29 @@ import kotlinx.serialization.SerializationException import okio.Path.Companion.toPath import okio.buffer import okio.use -import kotlin.test.AfterTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith +import kotlin.test.* import kotlinx.serialization.json.okio.decodeFromBufferedSource as decode import kotlinx.serialization.json.okio.encodeToBufferedSink as decode class FileCodecTests { - private val codec: FileCodec = FileCodec(filePath = FILE) + private val codec: FileCodec = FileCodec(file = FILE_PATH.toPath()) @OptIn(ExperimentalSerializationApi::class) private var stored: Cat? - get() = FILE_SYSTEM.source(FILE.toPath()).buffer().use { DefaultJson.decode(it) } + get() = FILE_SYSTEM.source(FILE_PATH.toPath()).buffer().use { DefaultJson.decode(it) } set(value) { - FILE_SYSTEM.sink(FILE.toPath()).buffer().use { DefaultJson.decode(value, it) } + FILE_SYSTEM.sink(FILE_PATH.toPath()).buffer().use { DefaultJson.decode(value, it) } } + @BeforeTest + fun setup(){ + FILE_SYSTEM.createDirectory(FOLDER.toPath()) + } + @AfterTest fun cleanUp() { - FILE_SYSTEM.delete(FILE.toPath()) + FILE_SYSTEM.delete(FILE_PATH.toPath()) } @Test @@ -49,7 +51,7 @@ class FileCodecTests { @Test fun testEncodeDecodeFromDirectory() = runTest { - val dirCodec: FileCodec = FileCodec(filePath = "$FOLDER/$FILE") + val dirCodec: FileCodec = FileCodec(file = "$FOLDER/$FILE_PATH".toPath()) dirCodec.encode(MYLO) val expect: Pet = MYLO val actual: Pet? = dirCodec.decode() @@ -58,7 +60,7 @@ class FileCodecTests { @Test fun testDecodeMalformedFile() = runTest { - FILE_SYSTEM.sink(FILE.toPath()).buffer().use { it.writeUtf8("💩") } + FILE_SYSTEM.sink(FILE_PATH.toPath()).buffer().use { it.writeUtf8("💩") } assertFailsWith { codec.decode() } } } diff --git a/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/TestModels.kt b/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/TestModels.kt index 96c9504..d3e0c00 100644 --- a/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/TestModels.kt +++ b/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/TestModels.kt @@ -18,5 +18,5 @@ data class Cat( internal val MYLO = Cat(name = "Mylo", age = 1) internal val OREO = Cat(name = "Oreo", age = 1) -const val FILE = "test.json" +const val FILE_PATH = "test.json" const val FOLDER = "build/bin/tests" diff --git a/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/extensions/KListStoreTests.kt b/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/extensions/KListStoreTests.kt index 5606a51..53413de 100644 --- a/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/extensions/KListStoreTests.kt +++ b/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/extensions/KListStoreTests.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.okio.encodeToBufferedSink +import okio.Path import okio.Path.Companion.toPath import okio.buffer import okio.use @@ -28,12 +29,12 @@ import kotlin.test.Test import kotlin.test.assertEquals class KListStoreTests { - private val filePath: String = "test_lists.json" - private val store: KStore> = listStoreOf(filePath = filePath) + private val file: Path = "test_lists.json".toPath() + private val store: KStore> = listStoreOf(file = file) @AfterTest fun setup() { - FILE_SYSTEM.delete(filePath.toPath()) + FILE_SYSTEM.delete(file) } @Test @@ -53,7 +54,7 @@ class KListStoreTests { @Test fun testReadDefault() = runTest { - val defaultStore: KStore> = listStoreOf(filePath = filePath, default = listOf(MYLO)) + val defaultStore: KStore> = listStoreOf(file = file, default = listOf(MYLO)) val expect: List = listOf(MYLO) val actual: List = defaultStore.getOrEmpty() assertEquals(expect, actual) @@ -61,9 +62,9 @@ class KListStoreTests { @Test fun testReadPreviouslyStoredList() = runTest { - FILE_SYSTEM.sink(filePath.toPath()).buffer().use { Json.encodeToBufferedSink(listOf(OREO) , it) } + FILE_SYSTEM.sink(file).buffer().use { Json.encodeToBufferedSink(listOf(OREO) , it) } // Mylo will never be sent 😿 because there is already a stored value - val newStore: KStore> = listStoreOf(filePath = filePath, default = listOf(MYLO)) + val newStore: KStore> = listStoreOf(file = file, default = listOf(MYLO)) val expect: List = listOf(OREO) val actual: List = newStore.getOrEmpty() assertEquals(expect, actual) diff --git a/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/extensions/KVersionedStoreTests.kt b/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/extensions/KVersionedStoreTests.kt index 8e86c57..7d63cc0 100644 --- a/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/extensions/KVersionedStoreTests.kt +++ b/kstore-file/src/commonTest/kotlin/io/github/xxfast/kstore/file/extensions/KVersionedStoreTests.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.long +import okio.Path import okio.Path.Companion.toPath import kotlin.test.AfterTest import kotlin.test.Test @@ -25,14 +26,14 @@ val MYLO_V2 = CatV2(name = "mylo", lives = 7, age = 2, kawaiiness = 12L) val MYLO_V3 = CatV3(name = "mylo", lives = 7, age = 2, isCute = true) class KVersionedStoreTests { - private val filePath: String = "test_migration.json" + private val file: Path = "test_migration.json".toPath() - private val storeV0: KStore = storeOf(filePath = filePath) + private val storeV0: KStore = storeOf(file = file) - private val storeV1: KStore = storeOf(filePath = filePath, version = 1) + private val storeV1: KStore = storeOf(file = file, version = 1) private val storeV2: KStore = storeOf( - filePath = filePath, + file = file, version = 2 ) { version, jsonElement -> when (version) { @@ -49,7 +50,7 @@ class KVersionedStoreTests { } private val storeV3: KStore = storeOf( - filePath = filePath, + file = file, version = 3 ) { version, jsonElement -> when (version) { @@ -75,8 +76,8 @@ class KVersionedStoreTests { @AfterTest fun cleanup() { - FILE_SYSTEM.delete(filePath.toPath()) - FILE_SYSTEM.delete("$filePath.version".toPath()) + FILE_SYSTEM.delete(file) + FILE_SYSTEM.delete("${file.name}.version".toPath()) } @Test