From aeb29c54cd9e931774bbfe36c9522d6966358f04 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Thu, 30 Nov 2023 00:12:46 +0100 Subject: [PATCH] WIP Channel content cache --- .../futo/platformplayer/states/StateCache.kt | 106 ++++++++++++++++++ .../stores/db/ManagedDBStore.kt | 28 ++++- .../stores/db/types/DBChannelCache.kt | 39 ++++++- 3 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateCache.kt diff --git a/app/src/main/java/com/futo/platformplayer/states/StateCache.kt b/app/src/main/java/com/futo/platformplayer/states/StateCache.kt new file mode 100644 index 00000000..699f8b10 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateCache.kt @@ -0,0 +1,106 @@ +package com.futo.platformplayer.states + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent +import com.futo.platformplayer.api.media.structures.DedupContentPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.api.media.structures.MultiChronoContentPager +import com.futo.platformplayer.api.media.structures.PlatformContentPager +import com.futo.platformplayer.cache.ChannelContentCache +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.resolveChannelUrl +import com.futo.platformplayer.serializers.PlatformContentSerializer +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.db.ManagedDBStore +import com.futo.platformplayer.stores.db.types.DBChannelCache +import com.futo.platformplayer.stores.db.types.DBHistory +import com.futo.platformplayer.toSafeFileName +import java.time.OffsetDateTime + +class StateCache { + private val _channelCache = ManagedDBStore.create("channelCache", DBChannelCache.Descriptor(), PlatformContentSerializer()) + .load(); + + fun clear() { + _channelCache.deleteAll(); + } + fun clearToday() { + val today = _channelCache.queryGreater(DBChannelCache.Index::datetime, OffsetDateTime.now().toEpochSecond()); + for(content in today) + _channelCache.delete(content); + } + + fun getChannelCachePager(channelUrl: String): IPager { + return _channelCache.queryPager(DBChannelCache.Index::channelUrl, channelUrl, 20) { + it.obj; + } + } + fun getSubscriptionCachePager(): DedupContentPager { + Logger.i(TAG, "Subscriptions CachePager get subscriptions"); + val subs = StateSubscriptions.instance.getSubscriptions(); + Logger.i(TAG, "Subscriptions CachePager polycentric urls"); + val allUrls = subs.map { + val otherUrls = PolycentricCache.instance.getCachedProfile(it.channel.url)?.profile?.ownedClaims?.mapNotNull { c -> c.claim.resolveChannelUrl() } ?: listOf(); + if(!otherUrls.contains(it.channel.url)) + return@map listOf(listOf(it.channel.url), otherUrls).flatten(); + else + return@map otherUrls; + }.flatten().distinct(); + Logger.i(TAG, "Subscriptions CachePager compiling"); + + val pagers = MultiChronoContentPager(allUrls.map { getChannelCachePager(it) }, false, 20); + return DedupContentPager(pagers, StatePlatform.instance.getEnabledClients().map { it.id }); + } + + + fun getCachedContent(url: String): DBChannelCache.Index? { + return _channelCache.query(DBChannelCache.Index::url, url).firstOrNull(); + } + + fun uncacheContent(content: SerializedPlatformContent) { + val item = getCachedContent(content.url); + if(item != null) + _channelCache.delete(item); + } + fun cacheContents(contents: List): List { + return contents.filter { cacheContent(it) }; + } + fun cacheContent(content: IPlatformContent, doUpdate: Boolean = false): Boolean { + if(content.author.url.isEmpty()) + return false; + + val serialized = SerializedPlatformContent.fromContent(content); + val existing = getCachedContent(content.url); + + if(existing != null && doUpdate) { + _channelCache.update(existing.id!!, serialized); + return true; + } + else if(existing == null) { + _channelCache.insert(serialized); + return true; + } + + return false; + } + + + companion object { + private val TAG = "StateCache"; + + private var _instance : StateCache? = null; + val instance : StateCache + get(){ + if(_instance == null) + _instance = StateCache(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBStore.kt b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBStore.kt index 30076fd3..50eac84b 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBStore.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBStore.kt @@ -225,12 +225,32 @@ class ManagedDBStore, T, D: ManagedDBDatabase, DA return deserializeIndexes(dbDaoBase.getMultiple(_sqlGetAll(id))); } + fun query(field: KProperty<*>, obj: Any): List = query(validateFieldName(field), obj); fun query(field: String, obj: Any): List { val queryStr = "SELECT * FROM ${descriptor.table_name} WHERE ${field} = ?"; val query = SimpleSQLiteQuery(queryStr, arrayOf(obj)); return deserializeIndexes(dbDaoBase.getMultiple(query)); } - fun query(field: KProperty<*>, obj: Any): List = query(validateFieldName(field), obj); + fun queryGreater(field: KProperty<*>, obj: Any): List = queryGreater(validateFieldName(field), obj); + fun queryGreater(field: String, obj: Any): List { + val queryStr = "SELECT * FROM ${descriptor.table_name} WHERE ${field} > ?"; + val query = SimpleSQLiteQuery(queryStr, arrayOf(obj)); + return deserializeIndexes(dbDaoBase.getMultiple(query)); + } + fun querySmaller(field: KProperty<*>, obj: Any): List = querySmaller(validateFieldName(field), obj); + fun querySmaller(field: String, obj: Any): List { + val queryStr = "SELECT * FROM ${descriptor.table_name} WHERE ${field} < ?"; + val query = SimpleSQLiteQuery(queryStr, arrayOf(obj)); + return deserializeIndexes(dbDaoBase.getMultiple(query)); + } + fun queryBetween(field: KProperty<*>, greaterThan: Any, smallerThan: Any): List = queryBetween(validateFieldName(field), greaterThan, smallerThan); + fun queryBetween(field: String, greaterThan: Any, smallerThan: Any): List { + val queryStr = "SELECT * FROM ${descriptor.table_name} WHERE ${field} > ? AND ${field} < ?"; + val query = SimpleSQLiteQuery(queryStr, arrayOf(greaterThan, smallerThan)); + return deserializeIndexes(dbDaoBase.getMultiple(query)); + } + + fun queryPage(field: String, obj: Any, page: Int, pageSize: Int): List { val queryStr = "SELECT * FROM ${descriptor.table_name} WHERE ${field} = ? ${_orderSQL} LIMIT ? OFFSET ?"; val query = SimpleSQLiteQuery(queryStr, arrayOf(obj, pageSize, page * pageSize)); @@ -247,6 +267,12 @@ class ManagedDBStore, T, D: ManagedDBDatabase, DA queryPage(field, obj, it - 1, pageSize); }); } + fun queryPager(field: KProperty<*>, obj: Any, pageSize: Int, convert: (I)->X): IPager = queryPager(validateFieldName(field), obj, pageSize, convert); + fun queryPager(field: String, obj: Any, pageSize: Int, convert: (I)->X): IPager { + return AdhocPager({ + queryPage(field, obj, it - 1, pageSize).map(convert); + }); + } fun queryObjectPager(field: KProperty<*>, obj: Any, pageSize: Int): IPager = queryObjectPager(validateFieldName(field), obj, pageSize); fun queryObjectPager(field: String, obj: Any, pageSize: Int): IPager { diff --git a/app/src/main/java/com/futo/platformplayer/stores/db/types/DBChannelCache.kt b/app/src/main/java/com/futo/platformplayer/stores/db/types/DBChannelCache.kt index 8007f393..9f2af268 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/db/types/DBChannelCache.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/db/types/DBChannelCache.kt @@ -1,30 +1,65 @@ package com.futo.platformplayer.stores.db.types import androidx.room.ColumnInfo +import androidx.room.Dao +import androidx.room.Database import androidx.room.Ignore import androidx.room.PrimaryKey import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent import com.futo.platformplayer.models.HistoryVideo +import com.futo.platformplayer.stores.db.ColumnIndex +import com.futo.platformplayer.stores.db.ColumnOrdered +import com.futo.platformplayer.stores.db.ManagedDBDAOBase +import com.futo.platformplayer.stores.db.ManagedDBDatabase +import com.futo.platformplayer.stores.db.ManagedDBDescriptor import com.futo.platformplayer.stores.db.ManagedDBIndex +import java.time.OffsetDateTime +import kotlin.reflect.KClass class DBChannelCache { companion object { - const val TABLE_NAME = "channelCache"; + const val TABLE_NAME = "feed_cache"; + } + + + //These classes solely exist for bounding generics for type erasure + @Dao + interface DBDAO: ManagedDBDAOBase {} + @Database(entities = [Index::class], version = 2) + abstract class DB: ManagedDBDatabase() { + abstract override fun base(): DBDAO; + } + + class Descriptor: ManagedDBDescriptor() { + override val table_name: String = TABLE_NAME; + override fun create(obj: SerializedPlatformContent): Index = Index(obj); + override fun dbClass(): KClass = DB::class; + override fun indexClass(): KClass = Index::class; } class Index: ManagedDBIndex { + @ColumnIndex @PrimaryKey(true) + @ColumnOrdered(1) override var id: Long? = null; - var feedType: String? = null; + @ColumnIndex + var url: String? = null; + @ColumnIndex var channelUrl: String? = null; + @ColumnIndex + @ColumnOrdered(0) + var datetime: Long? = null; + constructor() {} constructor(sCache: SerializedPlatformContent) { id = null; serialized = null; + url = sCache.url; channelUrl = sCache.author.url; + datetime = sCache.datetime?.toEpochSecond(); } } } \ No newline at end of file