diff --git a/build.gradle.kts b/build.gradle.kts index 5bc975b..f889441 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -94,6 +94,7 @@ subprojects { "implementation"("io.ktor:ktor-serialization-kotlinx-json:$ktor_version") "implementation"("dev.usbharu:kmp-logger:1.1.0") "implementation"("com.michael-bull.kotlin-result:kotlin-result:1.1.16") + "implementation"("io.github.reactivecircus.cache4k:cache4k:0.9.0") "testImplementation"("org.junit.jupiter:junit-jupiter-api:5.9.0") "testImplementation"("org.junit.jupiter:junit-jupiter-params:5.9.0") diff --git a/core/src/main/kotlin/dev/usbharu/multim/api/AccountApi.kt b/core/src/main/kotlin/dev/usbharu/multim/api/AccountApi.kt index 74a7689..966d1e9 100644 --- a/core/src/main/kotlin/dev/usbharu/multim/api/AccountApi.kt +++ b/core/src/main/kotlin/dev/usbharu/multim/api/AccountApi.kt @@ -16,13 +16,36 @@ interface AccountApi { until: StatusId? = null ): Result,MultiMError> + val FOLLOW: String + get() = "account/follow" + suspend fun follow(account: Account): Result + + val UNFOLLOW: String + get() = "account/unfollow" suspend fun unfollow(account: Account): Result + + val PROFILE: String + get() = "account/profile" suspend fun profile(account: Account): Result + + val STATUSES:String + get() = "account/statuses" suspend fun statuses(account: Account, includeRepost: Boolean = false): Result,MultiMError> + + val RELEATIONSHIPS:String + get() = "account/relationships" suspend fun relationships(myself: Account, other: Account): Result + + val REQUEST_CANCEL:String + get() = "account/requestCancel" suspend fun requestCancel(account: Account): Result + + val REQUEST_ACCEPT:String + get() = "account/requestAccept" suspend fun requestAccept(account: Account): Result + val REQUEST_REJECT:String + get() = "account/requestReject" suspend fun requestReject(account: Account): Result } diff --git a/core/src/main/kotlin/dev/usbharu/multim/api/EmojiApi.kt b/core/src/main/kotlin/dev/usbharu/multim/api/EmojiApi.kt index 1fdc3ac..836192e 100644 --- a/core/src/main/kotlin/dev/usbharu/multim/api/EmojiApi.kt +++ b/core/src/main/kotlin/dev/usbharu/multim/api/EmojiApi.kt @@ -8,8 +8,13 @@ import dev.usbharu.multim.error.MultiMResult import dev.usbharu.multim.model.Emoji interface EmojiApi { + val GET:String + get() = "emoji/get" suspend fun get(name:String):MultiMResult + val FIND_BY_NAME:String + get() = "emoji/findByName" + suspend fun findByName(name:String):MultiMResult> } diff --git a/core/src/main/kotlin/dev/usbharu/multim/api/StatusApi.kt b/core/src/main/kotlin/dev/usbharu/multim/api/StatusApi.kt index 7e4acce..fd3c921 100644 --- a/core/src/main/kotlin/dev/usbharu/multim/api/StatusApi.kt +++ b/core/src/main/kotlin/dev/usbharu/multim/api/StatusApi.kt @@ -12,12 +12,22 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime interface StatusApi { + val POST:String + get() = "status/post" suspend fun post(status: StatusForPost): Result + + val DELETE:String + get() = "status/delete" suspend fun delete(id: StatusId): Result + val FIND_BY_ID:String + get() = "status/findById" suspend fun findById(id: StatusId): Result + val ADD_REACTION:String + get() = "status/addReaction" suspend fun addReaction(id: StatusId, reaction: Reaction): Result - + val REMOVE_REACTION:String + get() = "status/remvoeReaction" /** * Remove reaction * @@ -26,13 +36,29 @@ interface StatusApi { * @return */ suspend fun removeReaction(id: StatusId, reaction: Reaction?): Result + val REACTIONS:String + get() = "status/reactions" suspend fun reactions(id: StatusId): Result,MultiMError> + val REPLIES:String + get() = "status/replies" suspend fun replies(id: StatusId): Result,MultiMError> + val REPOST:String + get() = "status/repost" suspend fun repost(id: StatusId): Result + val UN_REPOST:String + get() = "status/unRepost" suspend fun unRepost(id: StatusId): Result + val REPLY_TO:String + get() = "status/replyTo" suspend fun replyTo(id: StatusId, status: StatusForPost): Result + val ADD_TO_BOOKMARKS:String + get() = "status/addBookMarks" suspend fun addToBookmarks(id: StatusId): Result + val REMOVE_FROM_BOOKMARKS:String + get() = "status/removeFromBookmarks" suspend fun removeFromBookmarks(id: StatusId): Result + val GET_PREVIOUS_AND_NEXT:String + get() = "status/getPreviousAndNext" suspend fun getPreviousAndNext(id: StatusId): Result fun getUniqueId(status: Status):Int{ diff --git a/core/src/main/kotlin/dev/usbharu/multim/api/TimelineApi.kt b/core/src/main/kotlin/dev/usbharu/multim/api/TimelineApi.kt index c79d192..58d9770 100644 --- a/core/src/main/kotlin/dev/usbharu/multim/api/TimelineApi.kt +++ b/core/src/main/kotlin/dev/usbharu/multim/api/TimelineApi.kt @@ -9,12 +9,20 @@ import dev.usbharu.multim.model.Status import dev.usbharu.multim.model.Timeline interface TimelineApi { + val AVAILABLE_TIMELINES:String + get() = "timeline/availableTimelines" suspend fun availableTimelines(): Result,MultiMError> + val LISTEN:String + get() = "timeline/listen" suspend fun listen(timeline: Timeline,callback: (List) -> Unit):Result + val CONNECT:String + get() = "timeline/connect" // todo 返り値を詳細にする suspend fun connect(timeline: Timeline): Result + val DISCONNECT:String + get() = "timeline/disconnect" /** * Disconnect diff --git a/core/src/main/kotlin/dev/usbharu/multim/cache/Cacheable.kt b/core/src/main/kotlin/dev/usbharu/multim/cache/Cacheable.kt new file mode 100644 index 0000000..29c7884 --- /dev/null +++ b/core/src/main/kotlin/dev/usbharu/multim/cache/Cacheable.kt @@ -0,0 +1,5 @@ +package dev.usbharu.multim.cache + +interface Cacheable { + val cacheKey:String +} diff --git a/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableAccountApi.kt b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableAccountApi.kt new file mode 100644 index 0000000..9cfd588 --- /dev/null +++ b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableAccountApi.kt @@ -0,0 +1,57 @@ +package dev.usbharu.multim.cache + +import com.github.michaelbull.result.Result +import dev.usbharu.multim.api.AccountApi +import dev.usbharu.multim.error.MultiMError +import dev.usbharu.multim.model.* + +class CacheableAccountApi( + private val accountApi: AccountApi, + private val cacheableApi: CacheableApi +) : + CacheableApi by cacheableApi, AccountApi by accountApi { + override suspend fun userTimeline( + account: Account, + since: StatusId?, + until: StatusId? + ): Result, MultiMError> { + return cacheableApi.cacheOrGet( + CacheableApi.generateKey( + account, + since, + until + ) + ) { accountApi.userTimeline(account, since, until) } + } + + override suspend fun profile(account: Account): Result { + return cacheableApi.cacheOrGet(PROFILE, account) { accountApi.profile(account) } + } + + override suspend fun statuses( + account: Account, + includeRepost: Boolean + ): Result, MultiMError> { + return cacheableApi.cacheOrGet( + STATUSES, + CacheableApi.generateKey(account) + includeRepost + ) { + accountApi.statuses( + account, + includeRepost + ) + } + } + + override suspend fun relationships( + myself: Account, + other: Account + ): Result { + return cacheableApi.cacheOrGet(RELEATIONSHIPS, myself, other) { + accountApi.relationships( + myself, + other + ) + } + } +} diff --git a/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableApi.kt b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableApi.kt new file mode 100644 index 0000000..ece8879 --- /dev/null +++ b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableApi.kt @@ -0,0 +1,52 @@ +package dev.usbharu.multim.cache + +import kotlinx.datetime.Clock + +interface CacheableApi { + + companion object { + fun generateKey(vararg objects: Cacheable?): String { + var key: String = Clock.System.now().toString() + for (cacheable in objects) { + key += cacheable?.cacheKey + } + return key + } + } + + fun purge() + fun disable() + fun enable() + fun withNoCache(block: () -> Unit) { + disable() + try { + block() + } finally { + enable() + } + } + + fun cache(method: String, key: String, value: T): T + + fun get(method: String, key: String): T? + suspend fun cacheOrGet(method: String, key: String, block: suspend () -> T): T { + val get = get(method, key) + if (get != null) { + return get + } + return cache(method, key, block()) + } + + suspend fun cacheOrGet( + method: String, + vararg objects: Cacheable?, + block: suspend () -> T + ): T { + val key = generateKey(*objects) + val get = get(method, key) + if (get != null) { + return get + } + return cache(method, key, block()) + } +} diff --git a/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableEmojiApi.kt b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableEmojiApi.kt new file mode 100644 index 0000000..96f87e7 --- /dev/null +++ b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableEmojiApi.kt @@ -0,0 +1,16 @@ +package dev.usbharu.multim.cache + +import dev.usbharu.multim.api.EmojiApi +import dev.usbharu.multim.error.MultiMResult +import dev.usbharu.multim.model.Emoji + +class CacheableEmojiApi(private val cacheableApi: CacheableApi,private val emojiApi: EmojiApi) : + CacheableApi by cacheableApi, EmojiApi by emojiApi { + override suspend fun get(name: String): MultiMResult { + return cacheableApi.cacheOrGet(GET, name) { emojiApi.get(name) } + } + + override suspend fun findByName(name: String): MultiMResult> { + return cacheableApi.cacheOrGet(FIND_BY_NAME, name) { emojiApi.findByName(name) } + } +} diff --git a/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableStatusApi.kt b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableStatusApi.kt new file mode 100644 index 0000000..8219d35 --- /dev/null +++ b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableStatusApi.kt @@ -0,0 +1,31 @@ +package dev.usbharu.multim.cache + +import com.github.michaelbull.result.Result +import dev.usbharu.multim.api.StatusApi +import dev.usbharu.multim.error.MultiMError +import dev.usbharu.multim.model.PreviousAndNextPosts +import dev.usbharu.multim.model.Reaction +import dev.usbharu.multim.model.Status +import dev.usbharu.multim.model.StatusId + +class CacheableStatusApi(private val cacheableApi: CacheableApi,private val statusApi: StatusApi) : + CacheableApi by cacheableApi, StatusApi by statusApi { + override suspend fun findById(id: StatusId): Result { + return cacheableApi.cacheOrGet(FIND_BY_ID, id) { statusApi.findById(id) } + } + + override suspend fun reactions(id: StatusId): Result, MultiMError> { + return cacheableApi.cacheOrGet(REACTIONS, id) { statusApi.reactions(id) } + } + + override suspend fun replies(id: StatusId): Result, MultiMError> { + return cacheableApi.cacheOrGet(REPLIES, id) { statusApi.replies(id) } + } + + override suspend fun getPreviousAndNext(id: StatusId): Result { + return cacheableApi.cacheOrGet( + GET_PREVIOUS_AND_NEXT, + id + ) { statusApi.getPreviousAndNext(id) } + } +} diff --git a/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableTimelineApi.kt b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableTimelineApi.kt new file mode 100644 index 0000000..f40c197 --- /dev/null +++ b/core/src/main/kotlin/dev/usbharu/multim/cache/CacheableTimelineApi.kt @@ -0,0 +1,13 @@ +package dev.usbharu.multim.cache + +import com.github.michaelbull.result.Result +import dev.usbharu.multim.api.TimelineApi +import dev.usbharu.multim.error.MultiMError +import dev.usbharu.multim.model.Timeline + +class CacheableTimelineApi(private val cacheableApi: CacheableApi,private val timelineApi: TimelineApi) : + CacheableApi by cacheableApi, TimelineApi by timelineApi { + override suspend fun availableTimelines(): Result, MultiMError> { + return cacheableApi.cacheOrGet(AVAILABLE_TIMELINES) { timelineApi.availableTimelines() } + } +} diff --git a/core/src/main/kotlin/dev/usbharu/multim/cache/InMemoryLRUCache.kt b/core/src/main/kotlin/dev/usbharu/multim/cache/InMemoryLRUCache.kt new file mode 100644 index 0000000..b88b14f --- /dev/null +++ b/core/src/main/kotlin/dev/usbharu/multim/cache/InMemoryLRUCache.kt @@ -0,0 +1,38 @@ +package dev.usbharu.multim.cache + +import io.github.reactivecircus.cache4k.Cache + +class InMemoryLRUCache(maxSize: Long) : CacheableApi { + private val cache = Cache.Builder().maximumCacheSize(maxSize).build() + private var enable: Boolean = true + override fun purge() { + cache.invalidateAll() + } + + override fun disable() { + enable = false + } + + override fun enable() { + enable = true + } + + override fun cache(method: String, key: String, value: T): T { + if (enable) { + cache.put(method + key, value) + } + return value + } + + override fun get(method: String, key: String): T? { + if (enable) { + val any = cache.get(method + key) + return try { + any as T? + } catch (e: ClassCastException) { + null + } + } + return null + } +} diff --git a/core/src/main/kotlin/dev/usbharu/multim/model/Account.kt b/core/src/main/kotlin/dev/usbharu/multim/model/Account.kt index 4c6a7c7..c7cc553 100644 --- a/core/src/main/kotlin/dev/usbharu/multim/model/Account.kt +++ b/core/src/main/kotlin/dev/usbharu/multim/model/Account.kt @@ -1,8 +1,10 @@ package dev.usbharu.multim.model +import dev.usbharu.multim.cache.Cacheable + abstract class Account( val screenName: String, val accountName: String, val isBot: Boolean? = false, val avatar: Avatar -) +) : Cacheable diff --git a/core/src/main/kotlin/dev/usbharu/multim/model/StatusId.kt b/core/src/main/kotlin/dev/usbharu/multim/model/StatusId.kt index 748d141..1a31b27 100644 --- a/core/src/main/kotlin/dev/usbharu/multim/model/StatusId.kt +++ b/core/src/main/kotlin/dev/usbharu/multim/model/StatusId.kt @@ -1,6 +1,8 @@ package dev.usbharu.multim.model -abstract class StatusId { +import dev.usbharu.multim.cache.Cacheable + +abstract class StatusId : Cacheable { abstract override fun equals(other: Any?): Boolean abstract override fun hashCode(): Int abstract fun getUrl(): String diff --git a/core/src/main/kotlin/dev/usbharu/multim/multi/model/MultiAccountAccount.kt b/core/src/main/kotlin/dev/usbharu/multim/multi/model/MultiAccountAccount.kt index c030ded..12b828c 100644 --- a/core/src/main/kotlin/dev/usbharu/multim/multi/model/MultiAccountAccount.kt +++ b/core/src/main/kotlin/dev/usbharu/multim/multi/model/MultiAccountAccount.kt @@ -12,4 +12,7 @@ class MultiAccountAccount( innerData.accountName, innerData.isBot, innerData.avatar -) +) { + override val cacheKey: String + get() = innerData.cacheKey +} diff --git a/core/src/main/kotlin/dev/usbharu/multim/multi/model/MultiAccountStatusId.kt b/core/src/main/kotlin/dev/usbharu/multim/multi/model/MultiAccountStatusId.kt index 1b7d86a..b6d3c3e 100644 --- a/core/src/main/kotlin/dev/usbharu/multim/multi/model/MultiAccountStatusId.kt +++ b/core/src/main/kotlin/dev/usbharu/multim/multi/model/MultiAccountStatusId.kt @@ -16,4 +16,7 @@ class MultiAccountStatusId(override val innerData: StatusId, override val hashCo override fun getUrl(): String { return innerData.getUrl() } + + override val cacheKey: String + get() = innerData.cacheKey } diff --git a/impl/misskey/src/main/kotlin/dev/usbharu/multim/misskey/v12/common/MisskeyAccount.kt b/impl/misskey/src/main/kotlin/dev/usbharu/multim/misskey/v12/common/MisskeyAccount.kt index 53159c4..07eea5d 100644 --- a/impl/misskey/src/main/kotlin/dev/usbharu/multim/misskey/v12/common/MisskeyAccount.kt +++ b/impl/misskey/src/main/kotlin/dev/usbharu/multim/misskey/v12/common/MisskeyAccount.kt @@ -9,4 +9,7 @@ class MisskeyAccount( accountName: String, isBot: Boolean? = false, avatar: Avatar -) : Account(screenName, accountName, isBot, avatar) +) : Account(screenName, accountName, isBot, avatar){ + override val cacheKey: String + get() = accountName +} diff --git a/impl/misskey/src/main/kotlin/dev/usbharu/multim/misskey/v12/common/MisskeyStatusId.kt b/impl/misskey/src/main/kotlin/dev/usbharu/multim/misskey/v12/common/MisskeyStatusId.kt index 66b96fa..b86daa6 100644 --- a/impl/misskey/src/main/kotlin/dev/usbharu/multim/misskey/v12/common/MisskeyStatusId.kt +++ b/impl/misskey/src/main/kotlin/dev/usbharu/multim/misskey/v12/common/MisskeyStatusId.kt @@ -14,4 +14,7 @@ class MisskeyStatusId(val id: String, private val url: String) : StatusId() { override fun getUrl(): String { return url } + + override val cacheKey: String + get() = id } diff --git a/impl/misskey/src/test/kotlin/dev/usbharu/multim/api/MisskeyAccountApiTest.kt b/impl/misskey/src/test/kotlin/dev/usbharu/multim/api/MisskeyAccountApiTest.kt index 691164c..a938218 100644 --- a/impl/misskey/src/test/kotlin/dev/usbharu/multim/api/MisskeyAccountApiTest.kt +++ b/impl/misskey/src/test/kotlin/dev/usbharu/multim/api/MisskeyAccountApiTest.kt @@ -14,8 +14,6 @@ import dev.usbharu.multim.misskey.v12.common.MisskeyAvatar import dev.usbharu.multim.misskey.v12.common.api.MisskeyAccountApi import dev.usbharu.multim.misskey.v12.common.api.MisskeyApiClient import dev.usbharu.multim.model.SingleTokenAuth -import io.github.aakira.napier.DebugAntilog -import io.github.aakira.napier.Napier import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.* @@ -98,7 +96,6 @@ class MisskeyAccountApiTest : AccountApiTest() { if (it is MultiMHttpError) { }else { - Napier.base(DebugAntilog()) Logger.error("Account Api Test","想定されたエラーではない",it) fail("想定されたエラーではない",) } @@ -146,7 +143,6 @@ class MisskeyAccountApiTest : AccountApiTest() { if (it is MultiMHttpError) { }else { - Napier.base(DebugAntilog()) Logger.error("Account Api Test","想定されたエラーではない",it) fail("想定されたエラーではない",) } @@ -221,7 +217,6 @@ class MisskeyAccountApiTest : AccountApiTest() { @Test override fun relationships_illegalAccount_returnErr() = runTest { - Napier.base(DebugAntilog()) val myself = MisskeyAccount( "9bg1zu54y7", "test",