Skip to content

Commit

Permalink
[New version] Update with new Kotlin version and cleanup on the inter…
Browse files Browse the repository at this point in the history
…face (#62)
  • Loading branch information
kittinunf authored Apr 11, 2022
1 parent fbff7e1 commit 80d95e8
Show file tree
Hide file tree
Showing 24 changed files with 383 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,9 @@ class FuseByteCacheTest : BaseTestCase() {

val timestamp = cache.getTimestamp("timestamp")

assertThat(timestamp, notNullValue())
assertThat(timestamp, not(equalTo(-1L)))
assertThat(System.currentTimeMillis() - timestamp, object : BaseMatcher<Long>() {
assertThat(System.currentTimeMillis() - timestamp!!, object : BaseMatcher<Long>() {
override fun describeTo(description: Description?) {}

override fun matches(item: Any?): Boolean {
Expand Down
56 changes: 5 additions & 51 deletions fuse/src/main/java/com/github/kittinunf/fuse/core/Cache.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
package com.github.kittinunf.fuse.core

import com.github.kittinunf.fuse.core.cache.Entry
import com.github.kittinunf.fuse.core.fetch.DiskFetcher
import com.github.kittinunf.fuse.core.fetch.Fetcher
import com.github.kittinunf.fuse.core.fetch.NoFetcher
import com.github.kittinunf.fuse.core.fetch.SimpleFetcher
import com.github.kittinunf.fuse.util.md5
import com.github.kittinunf.result.Result
import com.github.kittinunf.result.flatMap
import java.io.File

object CacheBuilder {

fun <T : Any> config(
dir: String,
convertible: Fuse.DataConvertible<T>,
construct: Config<T>.() -> Unit = {}
): Config<T> {
fun <T : Any> config(dir: String, convertible: Fuse.DataConvertible<T>, construct: Config<T>.() -> Unit = {}): Config<T> {
return Config(dir, convertible = convertible).apply(construct)
}

fun <T : Any> config(
dir: String,
name: String,
convertible: Fuse.DataConvertible<T>,
construct: Config<T>.() -> Unit = {}
): Config<T> {
fun <T : Any> config(dir: String, name: String, convertible: Fuse.DataConvertible<T>, construct: Config<T>.() -> Unit = {}): Config<T> {
return Config(dir, name, convertible).apply(construct)
}
}
Expand All @@ -38,11 +25,7 @@ enum class Source {
DISK,
}

interface Cache<T : Any> :
Fuse.Cacheable,
Fuse.Cacheable.Put<T>,
Fuse.Cacheable.Get<T>,
Fuse.DataConvertible<T>
interface Cache<T : Any> : Fuse.Cacheable, Fuse.Cacheable.Put<T>, Fuse.Cacheable.Get<T>, Fuse.DataConvertible<T>

class CacheImpl<T : Any> internal constructor(
private val config: Config<T>
Expand Down Expand Up @@ -147,42 +130,13 @@ class CacheImpl<T : Any> internal constructor(
return value != null
}

override fun getTimestamp(key: String): Long {
override fun getTimestamp(key: String): Long? {
val safeKey = key.md5()
return memCache.getTimestamp(safeKey) ?: diskCache.getTimestamp(safeKey) ?: -1
return memCache.getTimestamp(safeKey) ?: diskCache.getTimestamp(safeKey) ?: null
}

private fun fetchAndPut(fetcher: Fetcher<T>): Result<T, Exception> {
val fetchResult = fetcher.fetch()
return fetchResult.flatMap { put(fetcher.key, it) }
}
}

// region File
fun <T : Any> Cache<T>.get(file: File): Result<T, Exception> = get(DiskFetcher(file, this))

fun <T : Any> Cache<T>.getWithSource(file: File): Pair<Result<T, Exception>, Source> =
getWithSource(DiskFetcher(file, this))

fun <T : Any> Cache<T>.put(file: File): Result<T, Exception> = put(DiskFetcher(file, this))
// endregion File

// region Value
fun <T : Any> Cache<T>.get(key: String, getValue: (() -> T?)? = null): Result<T, Exception> {
val fetcher = if (getValue == null) NoFetcher<T>(key) else SimpleFetcher(key, getValue)
return get(fetcher)
}

fun <T : Any> Cache<T>.getWithSource(
key: String,
getValue: (() -> T?)? = null
): Pair<Result<T, Exception>, Source> {
val fetcher = if (getValue == null) NoFetcher<T>(key) else SimpleFetcher(key, getValue)
return getWithSource(fetcher)
}

fun <T : Any> Cache<T>.put(key: String, putValue: T? = null): Result<T, Exception> {
val fetcher = if (putValue == null) NoFetcher<T>(key) else SimpleFetcher(key, { putValue })
return put(fetcher)
}
// endregion Value
7 changes: 2 additions & 5 deletions fuse/src/main/java/com/github/kittinunf/fuse/core/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ class Config<T : Any>(
}

internal fun defaultMemoryCache(minimalSize: Int = 128): Persistence<Any> = MemCache(minimalSize)

internal fun defaultDiskCache(cacheDir: String, name: String, diskCapacity: Long): Persistence<ByteArray> =
DiskCache.open(
cacheDir,
name,
diskCapacity
)
DiskCache.open(cacheDir, name, diskCapacity)
91 changes: 88 additions & 3 deletions fuse/src/main/java/com/github/kittinunf/fuse/core/Fuse.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.github.kittinunf.fuse.core

import com.github.kittinunf.fuse.core.fetch.DiskFetcher
import com.github.kittinunf.fuse.core.fetch.Fetcher
import com.github.kittinunf.fuse.core.fetch.NeverFetcher
import com.github.kittinunf.fuse.core.fetch.SimpleFetcher
import com.github.kittinunf.result.Result
import java.io.File

class Fuse {
object Fuse {

interface DataConvertible<T : Any> {
fun convertFromData(bytes: ByteArray): T
Expand Down Expand Up @@ -77,8 +81,89 @@ class Fuse {
/**
* Retrieve the keys from all values persisted
* @param key The key associated with the object to be persisted
* @return Long represents the timestamp in milliseconds since epoch 1970
* @return Long represents the timestamp in milliseconds since epoch 1970, or null which means that the key is not present in cache
*/
fun getTimestamp(key: String): Long
fun getTimestamp(key: String): Long?
}
}

// region File
/**
* Get the entry associated as a Data of file content in T with its particular key as File path. If File is not there or too large, it returns as [Result.Failure]
* Otherwise, it returns [Result.Success] of data of a given file in T
*
* @param file The file object that represent file data on the disk
* @return Result<T, Exception> The Result that represents the success/failure of the operation
*/
fun <T : Any> Cache<T>.get(file: File): Result<T, Exception> = get(DiskFetcher(file, this))

/**
* Get the entry associated as a Data of file content in T with its particular key as File path. If File is not there or too large, it returns as [Result.Failure]
* Otherwise, it returns [Result.Success] data of a given file in T
*
* @param file The file object that represent file data on the disk
* @return Pair<Result<T, Exception>, Source>> The Result that represents the success/failure of the operation
*/
fun <T : Any> Cache<T>.getWithSource(file: File): Pair<Result<T, Exception>, Source> =
getWithSource(DiskFetcher(file, this))

/**
* Put the entry as a content of a file into Cache
*
* @param file The file object that represent file data on the disk
* @return Result<T, Exception> The Result that represents the success/failure of the operation
*/
fun <T : Any> Cache<T>.put(file: File): Result<T, Exception> = put(DiskFetcher(file, this))
// endregion File

// region Value
/**
* Get the entry associated as a value in T by using lambda getValue as a default value generator. If value for associated Key is not there, it saves with value from defaultValue.
*
* @param key The String represent key of the entry
* @return Result<T, Exception> The Result that represents the success/failure of the operation
*/
fun <T : Any> Cache<T>.get(key: String, defaultValue: (() -> T?)): Result<T, Exception> {
val fetcher = SimpleFetcher(key, defaultValue)
return get(fetcher)
}

/**
* Get the entry associated as a value in T. Unlike [Cache<T>.get(key: String, defaultValue: (() -> T))] counterpart, if value for associated Key is not there, it returns as [Result.Failure]
*
* @param key The String represent key of the entry
* @return Result<T, Exception> The Result that represents the success/failure of the operation
*/
fun <T : Any> Cache<T>.get(key: String): Result<T, Exception> = get(NeverFetcher(key))

/**
* Get the entry associated as a value in T by using lambda as a default value generator. if value for associated key is not there, it saves with value from defaultValue.
*
* @param key The string represent key of the entry
* @return Pair<Result<T, Exception>, Source>> The result that represents the success/failure of the operation
*/
fun <T : Any> Cache<T>.getWithSource(key: String, getValue: (() -> T?)): Pair<Result<T, Exception>, Source> {
val fetcher = SimpleFetcher(key, getValue)
return getWithSource(fetcher)
}

/**
* Get the entry associated as a value in T by using lambda as a default value generator. if value for associated key is not there, it saves with value from defaultValue.
*
* @param key The string represent key of the entry
* @return Pair<Result<T, Exception>, Source>> The result that represents the success/failure of the operation
*/
fun <T : Any> Cache<T>.getWithSource(key: String): Pair<Result<T, Exception>, Source> = getWithSource(NeverFetcher(key))

/**
* Put the entry as a content of a file into Cache
*
* @param key file object that represent file data on the disk
* @return Result<T, Exception> The Result that represents the success/failure of the operation
*/
fun <T : Any> Cache<T>.put(key: String, putValue: T): Result<T, Exception> {
val fetcher = SimpleFetcher(key, { putValue })
return put(fetcher)
}
// endregion Value

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import com.github.kittinunf.result.Result
import com.github.kittinunf.result.map
import java.io.File

class DiskFetcher<T : Any>(private val file: File, private val convertible: Fuse.DataConvertible<T>) :
Fetcher<T>,
class DiskFetcher<T : Any>(private val file: File, private val convertible: Fuse.DataConvertible<T>) : Fetcher<T>,
Fuse.DataConvertible<T> by convertible {

override val key: String = file.path
Expand All @@ -15,7 +14,7 @@ class DiskFetcher<T : Any>(private val file: File, private val convertible: Fuse

override fun fetch(): Result<T, Exception> {
val readFileResult = Result.of<ByteArray, Exception> { file.readBytes() }
if (cancelled) return Result.error(RuntimeException("Fetch got cancelled"))
if (cancelled) return Result.failure(RuntimeException("Fetch got cancelled"))
return readFileResult.map { convertFromData(it) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ interface Fetcher<out T : Any> {
}
}

class NotFoundException(key: String) : RuntimeException("value from $key is not found")
class NotFoundException(key: String) : RuntimeException("Value with key: $key is not found in cache")

internal class SimpleFetcher<out T : Any>(override val key: String, private val getValue: () -> T?) : Fetcher<T> {

override fun fetch(): Result<T, Exception> = Result.of(getValue(), { NotFoundException(key) })
override fun fetch(): Result<T, Exception> =
if (getValue() == null) Result.failure(RuntimeException("Fetch with Key: $key is failure")) else Result.of(getValue)
}

internal class NoFetcher<out T : Any>(override val key: String) : Fetcher<T> {
internal class NeverFetcher<out T : Any>(override val key: String) : Fetcher<T> {

override fun fetch(): Result<T, Exception> = Result.error(NotFoundException(key))
override fun fetch(): Result<T, Exception> = Result.failure(NotFoundException(key))
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package com.github.kittinunf.fuse.core.scenario

import com.github.kittinunf.fuse.core.Cache
import com.github.kittinunf.fuse.core.Fuse
import com.github.kittinunf.fuse.core.Source
import com.github.kittinunf.fuse.core.fetch.Fetcher
import com.github.kittinunf.fuse.core.fetch.NoFetcher
import com.github.kittinunf.fuse.core.fetch.NeverFetcher
import com.github.kittinunf.fuse.core.fetch.SimpleFetcher
import com.github.kittinunf.result.Result
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.Duration.Companion.milliseconds

class ExpirableCache<T : Any>(private val cache: Cache<T>) : Fuse.Cacheable by cache, Fuse.Cacheable.Put<T> by cache {
class ExpirableCache<T : Any>(private val cache: Cache<T>) : Cache<T> by cache {

/**
* Get the entry associated with its particular key which provided by the persistence.
Expand All @@ -26,7 +25,6 @@ class ExpirableCache<T : Any>(private val cache: Cache<T>) : Fuse.Cacheable by c
* @param useEntryEvenIfExpired The flag indicates whether we still want to use the entry or not
* @return Result<T, Exception> The Result that represents the success/failure of the operation
*/
@OptIn(ExperimentalTime::class)
fun get(
fetcher: Fetcher<T>,
timeLimit: Duration = Duration.INFINITE,
Expand All @@ -47,7 +45,6 @@ class ExpirableCache<T : Any>(private val cache: Cache<T>) : Fuse.Cacheable by c
* @param useEntryEvenIfExpired The flag indicates whether we still want to use the entry or not
* @return Pair<Result<T, Exception>, Cache.Source> The Pair of the result that represents the success/failure of the operation and The source of the entry
*/
@OptIn(ExperimentalTime::class)
fun getWithSource(
fetcher: Fetcher<T>,
timeLimit: Duration = Duration.INFINITE,
Expand All @@ -57,7 +54,7 @@ class ExpirableCache<T : Any>(private val cache: Cache<T>) : Fuse.Cacheable by c
val persistedTimestamp = getTimestamp(key)

// no timestamp fetch, we need to just fetch the new data
return if (persistedTimestamp == -1L) {
return if (persistedTimestamp == null) {
put(fetcher) to Source.ORIGIN
} else {
val isExpired = hasExpired(persistedTimestamp, timeLimit)
Expand All @@ -82,39 +79,44 @@ class ExpirableCache<T : Any>(private val cache: Cache<T>) : Fuse.Cacheable by c
}
}

@OptIn(ExperimentalTime::class)
private fun hasExpired(persistedTimestamp: Long, timeLimit: Duration): Boolean {
val now = System.currentTimeMillis()
val durationSincePersisted = Duration.milliseconds((now - persistedTimestamp))
val durationSincePersisted = (now - persistedTimestamp).milliseconds
return durationSincePersisted > timeLimit
}
}

// region Value
@OptIn(ExperimentalTime::class)
fun <T : Any> ExpirableCache<T>.get(
key: String,
getValue: (() -> T?)? = null,
getValue: (() -> T),
timeLimit: Duration = Duration.INFINITE,
useEntryEvenIfExpired: Boolean = false
): Result<T, Exception> {
val fetcher = if (getValue == null) NoFetcher(key) else SimpleFetcher(key, getValue)
val fetcher = SimpleFetcher(key, getValue)
return get(fetcher, timeLimit, useEntryEvenIfExpired)
}

@OptIn(ExperimentalTime::class)
fun <T : Any> ExpirableCache<T>.get(
key: String,
timeLimit: Duration = Duration.INFINITE,
useEntryEvenIfExpired: Boolean = false
): Result<T, Exception> = get(NeverFetcher(key), timeLimit, useEntryEvenIfExpired)

fun <T : Any> ExpirableCache<T>.getWithSource(
key: String,
getValue: (() -> T?)? = null,
getValue: (() -> T),
timeLimit: Duration = Duration.INFINITE,
useEntryEvenIfExpired: Boolean = false
): Pair<Result<T, Exception>, Source> {
val fetcher = if (getValue == null) NoFetcher(key) else SimpleFetcher(key, getValue)
val fetcher = SimpleFetcher(key, getValue)
return getWithSource(fetcher, timeLimit, useEntryEvenIfExpired)
}

fun <T : Any> ExpirableCache<T>.put(key: String, putValue: T? = null): Result<T, Exception> {
val fetcher = if (putValue == null) NoFetcher(key) else SimpleFetcher(key, { putValue })
return put(fetcher)
}
fun <T : Any> ExpirableCache<T>.getWithSource(
key: String,
timeLimit: Duration = Duration.INFINITE,
useEntryEvenIfExpired: Boolean = false
): Pair<Result<T, Exception>, Source> = getWithSource(NeverFetcher(key), timeLimit, useEntryEvenIfExpired)

// endregion
2 changes: 1 addition & 1 deletion fuse/src/main/java/com/github/kittinunf/fuse/util/MD5.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.github.kittinunf.fuse.util

import java.security.MessageDigest

fun String.md5(): String {
internal fun String.md5(): String {
val md = MessageDigest.getInstance("MD5")
val digested = md.digest(toByteArray())
return digested.joinToString("") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,12 @@ class FuseByteCacheTest : BaseTestCase() {

val timestamp = cache.getTimestamp("timestamp")

assertThat(timestamp, notNullValue())
assertThat(timestamp, not(equalTo(-1L)))

val timeLimit = 2000L
assertThat(
System.currentTimeMillis() - timestamp,
System.currentTimeMillis() - timestamp!!,
object : BaseMatcher<Long>() {
override fun describeTo(description: Description) {
description.appendText("$timestamp is over than $timeLimit")
Expand Down
Loading

0 comments on commit 80d95e8

Please sign in to comment.