From 248e0b13f6216c224d60244a2b2cbd7b9909dab7 Mon Sep 17 00:00:00 2001 From: Hugo Lefrancois Date: Tue, 18 Jun 2024 09:44:46 -0400 Subject: [PATCH] Open FileSystemDataSource and add shouldSkipRequest and impl delete (#221) --- .../android/datasources-flow-filesystem.api | 8 ++-- .../api/jvm/datasources-flow-filesystem.api | 8 ++-- .../flow/generic/FileSystemDataSource.kt | 37 ++++++++++++--- .../flow/generic/FileSystemDataSourceTests.kt | 45 +++++++++++++++++++ 4 files changed, 86 insertions(+), 12 deletions(-) diff --git a/trikot-datasources/datasources-flow-filesystem/api/android/datasources-flow-filesystem.api b/trikot-datasources/datasources-flow-filesystem/api/android/datasources-flow-filesystem.api index 93578132..9ff043ef 100644 --- a/trikot-datasources/datasources-flow-filesystem/api/android/datasources-flow-filesystem.api +++ b/trikot-datasources/datasources-flow-filesystem/api/android/datasources-flow-filesystem.api @@ -1,9 +1,11 @@ -public final class com/mirego/trikot/datasources/flow/generic/FileSystemDataSource : com/mirego/trikot/datasources/flow/BaseExpiringExecutableFlowDataSource { - public fun (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Ljava/lang/String;Lokio/FileSystem;)V - public synthetic fun (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Ljava/lang/String;Lokio/FileSystem;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public class com/mirego/trikot/datasources/flow/generic/FileSystemDataSource : com/mirego/trikot/datasources/flow/BaseExpiringExecutableFlowDataSource { + public fun (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Ljava/lang/String;Lokio/FileSystem;Lcom/mirego/trikot/datasources/flow/FlowDataSource;)V + public synthetic fun (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Ljava/lang/String;Lokio/FileSystem;Lcom/mirego/trikot/datasources/flow/FlowDataSource;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun delete (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun internalRead (Lcom/mirego/trikot/datasources/flow/ExpiringFlowDataSourceRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public synthetic fun internalRead (Lcom/mirego/trikot/datasources/flow/FlowDataSourceRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun save (Lcom/mirego/trikot/datasources/flow/ExpiringFlowDataSourceRequest;Lcom/mirego/trikot/datasources/flow/FlowDataSourceExpiringValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public synthetic fun save (Lcom/mirego/trikot/datasources/flow/FlowDataSourceRequest;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun shouldSkipRequest (Lcom/mirego/trikot/datasources/flow/ExpiringFlowDataSourceRequest;)Z } diff --git a/trikot-datasources/datasources-flow-filesystem/api/jvm/datasources-flow-filesystem.api b/trikot-datasources/datasources-flow-filesystem/api/jvm/datasources-flow-filesystem.api index 93578132..9ff043ef 100644 --- a/trikot-datasources/datasources-flow-filesystem/api/jvm/datasources-flow-filesystem.api +++ b/trikot-datasources/datasources-flow-filesystem/api/jvm/datasources-flow-filesystem.api @@ -1,9 +1,11 @@ -public final class com/mirego/trikot/datasources/flow/generic/FileSystemDataSource : com/mirego/trikot/datasources/flow/BaseExpiringExecutableFlowDataSource { - public fun (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Ljava/lang/String;Lokio/FileSystem;)V - public synthetic fun (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Ljava/lang/String;Lokio/FileSystem;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public class com/mirego/trikot/datasources/flow/generic/FileSystemDataSource : com/mirego/trikot/datasources/flow/BaseExpiringExecutableFlowDataSource { + public fun (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Ljava/lang/String;Lokio/FileSystem;Lcom/mirego/trikot/datasources/flow/FlowDataSource;)V + public synthetic fun (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/KSerializer;Ljava/lang/String;Lokio/FileSystem;Lcom/mirego/trikot/datasources/flow/FlowDataSource;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun delete (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun internalRead (Lcom/mirego/trikot/datasources/flow/ExpiringFlowDataSourceRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public synthetic fun internalRead (Lcom/mirego/trikot/datasources/flow/FlowDataSourceRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun save (Lcom/mirego/trikot/datasources/flow/ExpiringFlowDataSourceRequest;Lcom/mirego/trikot/datasources/flow/FlowDataSourceExpiringValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public synthetic fun save (Lcom/mirego/trikot/datasources/flow/FlowDataSourceRequest;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun shouldSkipRequest (Lcom/mirego/trikot/datasources/flow/ExpiringFlowDataSourceRequest;)Z } diff --git a/trikot-datasources/datasources-flow-filesystem/src/commonMain/kotlin/com/mirego/trikot/datasources/flow/generic/FileSystemDataSource.kt b/trikot-datasources/datasources-flow-filesystem/src/commonMain/kotlin/com/mirego/trikot/datasources/flow/generic/FileSystemDataSource.kt index ae9826bf..f84fe71a 100644 --- a/trikot-datasources/datasources-flow-filesystem/src/commonMain/kotlin/com/mirego/trikot/datasources/flow/generic/FileSystemDataSource.kt +++ b/trikot-datasources/datasources-flow-filesystem/src/commonMain/kotlin/com/mirego/trikot/datasources/flow/generic/FileSystemDataSource.kt @@ -3,6 +3,7 @@ package com.mirego.trikot.datasources.flow.generic import com.mirego.trikot.datasources.flow.BaseExpiringExecutableFlowDataSource import com.mirego.trikot.datasources.flow.DataSourceDispatchers import com.mirego.trikot.datasources.flow.ExpiringFlowDataSourceRequest +import com.mirego.trikot.datasources.flow.FlowDataSource import com.mirego.trikot.datasources.flow.FlowDataSourceExpiringValue import com.mirego.trikot.datasources.flow.filesystem.NativeFileSystem import kotlinx.coroutines.withContext @@ -14,16 +15,21 @@ import okio.FileSystem import okio.Path import okio.Path.Companion.toPath -class FileSystemDataSource( +open class FileSystemDataSource( private val json: Json, private val dataSerializer: KSerializer, private val diskCachePath: String, - private val fileManager: FileSystem = NativeFileSystem.fileSystem -) : BaseExpiringExecutableFlowDataSource() { + private val fileManager: FileSystem = NativeFileSystem.fileSystem, + cacheDataSource: FlowDataSource>? = null, +) : BaseExpiringExecutableFlowDataSource(cacheDataSource) { public override suspend fun internalRead(request: R): FlowDataSourceExpiringValue { + if (shouldSkipRequest(request)) { + throw Throwable("Skipping internal read") + } + return withContext(DataSourceDispatchers.IO) { - val filePath = buildFilePath(request) + val filePath = buildFilePath(request.cacheableId) try { val data = json.decodeFromString(dataSerializer, getFileAsString(filePath)) FlowDataSourceExpiringValue( @@ -38,10 +44,14 @@ class FileSystemDataSource( } override suspend fun save(request: R, data: FlowDataSourceExpiringValue?) { + if (shouldSkipRequest(request)) { + return + } + data?.value?.let { dataToSave -> withContext(DataSourceDispatchers.IO) { try { - val filePath = buildFilePath(request) + val filePath = buildFilePath(request.cacheableId) saveStringValueToFile(filePath, json.encodeToString(dataSerializer, dataToSave)) } catch (exception: Throwable) { println("Failed to save json to disk cache. Error: $exception") @@ -50,6 +60,21 @@ class FileSystemDataSource( } } + override suspend fun delete(cacheableId: String) { + withContext(DataSourceDispatchers.IO) { + try { + val filePath = buildFilePath(cacheableId) + fileManager.delete(filePath) + } catch (exception: Throwable) { + println("Failed to delete file to disk. Error: $exception") + } + } + } + + open fun shouldSkipRequest(request: R): Boolean { + return false + } + private fun getFileAsString(filePath: Path) = if (fileManager.exists(filePath)) { fileManager.read(filePath) { readUtf8() @@ -71,5 +96,5 @@ class FileSystemDataSource( 0 } - private fun buildFilePath(request: R) = "$diskCachePath/${request.cacheableId}.json".toPath() + private fun buildFilePath(cacheableId: String) = "$diskCachePath/$cacheableId.json".toPath() } diff --git a/trikot-datasources/datasources-flow-filesystem/src/commonTest/kotlin/com/mirego/trikot/datasources/flow/generic/FileSystemDataSourceTests.kt b/trikot-datasources/datasources-flow-filesystem/src/commonTest/kotlin/com/mirego/trikot/datasources/flow/generic/FileSystemDataSourceTests.kt index f4b3c4da..dc634088 100644 --- a/trikot-datasources/datasources-flow-filesystem/src/commonTest/kotlin/com/mirego/trikot/datasources/flow/generic/FileSystemDataSourceTests.kt +++ b/trikot-datasources/datasources-flow-filesystem/src/commonTest/kotlin/com/mirego/trikot/datasources/flow/generic/FileSystemDataSourceTests.kt @@ -3,16 +3,21 @@ package com.mirego.trikot.datasources.flow.generic import com.mirego.trikot.datasources.flow.ExpiringFlowDataSourceRequest import com.mirego.trikot.datasources.flow.FlowDataSourceExpiringValue import com.mirego.trikot.datasources.flow.FlowDataSourceRequest +import com.mirego.trikot.datasources.flow.filesystem.NativeFileSystem import kotlinx.coroutines.test.runTest import kotlinx.datetime.Clock +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import okio.FileNotFoundException +import okio.FileSystem import okio.Path.Companion.toPath import okio.fakefilesystem.FakeFileSystem import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFails import kotlin.test.assertFailsWith +import kotlin.test.assertFalse import kotlin.test.assertTrue class FileSystemDataSourceTests { @@ -22,6 +27,7 @@ class FileSystemDataSourceTests { private val fileSystem = FakeFileSystem() private val fileSystemDataSource = FileSystemDataSource(json, dataSerializer, diskCachePath, fileSystem) + private val fileSystemDataSourceSkipAll = SkipAllFileSystemDataSource(json, dataSerializer, diskCachePath, fileSystem) @Test fun test_read_valid_data() = runTest { @@ -65,6 +71,34 @@ class FileSystemDataSourceTests { assertEquals("{\"value\":\"savedValue\"}", savedContent) } + @Test + fun test_read_skip() = runTest { + val testRequest = BasicRequest("test", 1000L, FlowDataSourceRequest.Type.REFRESH_CACHE) + val testDataJson = "{\"value\": \"value\"}" + val filePath = "$diskCachePath/${testRequest.cacheableId}.json".toPath() + + val parentDirectory = filePath.parent ?: throw AssertionError("Parent directory should not be null") + fileSystem.createDirectories(parentDirectory) + fileSystem.write(filePath) { + writeUtf8(testDataJson) + } + + assertFails { + fileSystemDataSourceSkipAll.internalRead(testRequest) + } + } + + @Test + fun test_save_data_skip() = runTest { + val testRequest = BasicRequest("saveTest", 1000L, FlowDataSourceRequest.Type.REFRESH_CACHE) + val testData = TestData("savedValue") + val filePath = "$diskCachePath/${testRequest.cacheableId}.json".toPath() + + fileSystemDataSourceSkipAll.save(testRequest, FlowDataSourceExpiringValue(testData, Clock.System.now().toEpochMilliseconds())) + + assertFalse(fileSystem.exists(filePath)) + } + @Serializable private data class TestData(val value: String) @@ -73,4 +107,15 @@ class FileSystemDataSourceTests { override val expiredInMilliseconds: Long = 0, override val requestType: FlowDataSourceRequest.Type ) : ExpiringFlowDataSourceRequest + + private class SkipAllFileSystemDataSource( + json: Json, + dataSerializer: KSerializer, + diskCachePath: String, + fileManager: FileSystem = NativeFileSystem.fileSystem, + ) : FileSystemDataSource(json, dataSerializer, diskCachePath, fileManager) { + override fun shouldSkipRequest(request: R): Boolean { + return true + } + } }