فهرست منبع

caching cursor fixes

Milan Jurkulak 3 سال پیش
والد
کامیت
708ee987a1

+ 2 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/CursorExt.kt

@@ -26,6 +26,7 @@ object CursorExt {
         projection.forEach { cname ->
             val idx = getColumnIndex(cname)
             val value = try {
+                // todo index out of bounds
                 getColumn(idx)
             } catch (e: Exception) {
                 Timber.e(e)
@@ -51,7 +52,7 @@ object CursorExt {
     }
 
     fun Cursor.getData(idx: Int): Any? {
-        return if (this is CachingCursor)
+        return if (this is CachingCursor<*>)
             getData(idx)
         else {
             try {

+ 5 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/ListExt.kt

@@ -29,7 +29,11 @@ object ListExt {
         }
     }
 
-    fun <T> List<T>.indexOf(predicate: (T) -> Boolean) = indexOf(first(predicate))
+    fun <T> List<T>.indexOf(
+        predicate: (T) -> Boolean
+    ) = firstOrNull(predicate)?.let { item ->
+        indexOf(item)
+    } ?: -1
 
     fun <T> List<T>.contains(block: (T) -> Boolean) = count(block) > 0
 

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/AudioCursor.kt

@@ -12,7 +12,7 @@ import android.content.Context
 
 class AudioCursor(
     context: Context
-) : CachingCursor(
+) : CachingCursor<AudioItem>(
     context = context,
     uri = AudioItem.URI,
     projection = AudioItem.MEDIA_PROJECTION,

+ 68 - 44
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/CachingCursor.kt

@@ -20,6 +20,8 @@ import android.database.DataSetObserver
 import android.net.Uri
 import android.os.Build
 import android.os.Bundle
+import android.os.Looper
+import android.os.Handler
 import androidx.annotation.RequiresApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -28,59 +30,68 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.mjdev.tvlib.extensions.CursorExt.isNotEmpty
 import timber.log.Timber
+import kotlin.coroutines.CoroutineContext
 
 @Suppress("FunctionName")
-open class CachingCursor(
+open class CachingCursor<T>(
     context: Context,
     uri: Uri?,
     private val projection: Array<String>,
     selection: String? = null,
     selectionArgs: Array<String>? = null,
     sortOrder: String? = null,
-    private val transform: (Cursor) -> Any? = { it },
-) : Cursor, List<Any?> {
+    private val transform: (Cursor) -> T? = { null },
+) : Cursor, List<T?> {
 
-    private val resolver: ContentResolver by lazy {
+    private val contentResolver by lazy {
         context.contentResolver
     }
 
-    private val observer = object : ContentObserver(null) {
+    private val handler by lazy {
+        Looper.myLooper()?.let { looper ->
+            Handler(looper)
+        }
+    }
+
+    private val observer = object : ContentObserver(handler) {
+        // todo, refresh
         @Suppress("DEPRECATION")
         override fun onChange(selfChange: Boolean) {
             requery()
         }
     }
 
-    private val cache: MutableList<CachedCursorItem> = mutableListOf()
+    private val cache: MutableList<CachedCursorItem<T>> = mutableListOf()
 
     private val cursor: Cursor? by lazy {
-        runBlocking(Dispatchers.IO) {
-            if (uri != null) {
-                try {
-                    resolver.query(
-                        uri, projection, selection, selectionArgs, sortOrder
-                    )?.apply {
-                        registerContentObserver(observer)
-                        if (this.isNotEmpty) {
-                            moveToFirst()
-                        }
+        if (uri != null) {
+            try {
+                contentResolver.query(
+                    uri, projection, selection, selectionArgs, sortOrder
+                )?.apply {
+                    registerContentObserver(observer)
+                    if (this.isNotEmpty) {
+                        moveToFirst()
                     }
-                } catch (e: Exception) {
-                    Timber.e(e)
-                    null
                 }
-            } else null
-        }
+            } catch (e: Exception) {
+                Timber.e(e)
+                null
+            }
+        } else null
     }
 
     val isNotEmpty: Boolean get() = count > 0
 
-    private fun _get(idx: Int): Any? {
-        var result: Any? = null
+    private fun _get(idx: Int): T? {
+        var result: T? = null
         if (isCached(idx)) {
             result = getFromCache(idx)
         } else {
@@ -99,15 +110,15 @@ open class CachingCursor(
         return result
     }
 
-    fun getData(idx: Int): Any? {
+    fun getData(idx: Int): T? {
         return _get(idx)
     }
 
-    private fun insertToCache(idx: Int, value: Any) {
+    private fun insertToCache(idx: Int, value: T) {
         cache.add(CachedCursorItem(idx, System.currentTimeMillis(), value))
     }
 
-    private fun getFromCache(idx: Int): Any? {
+    private fun getFromCache(idx: Int): T? {
         return cache.firstOrNull { item ->
             item.idx == idx
         }?.data
@@ -225,12 +236,24 @@ open class CachingCursor(
     }
 
     @Deprecated("Deprecated in Java")
-    override fun requery(): Boolean = runBlocking(Dispatchers.IO) { _requery() }
+    override fun requery(): Boolean {
+        launch {
+            _requery()
+        }
+        return true
+    }
+
+    private fun launch(
+        context: CoroutineContext = Dispatchers.IO,
+        start: CoroutineStart = CoroutineStart.DEFAULT,
+        block: suspend CoroutineScope.() -> Unit
+    ) = CoroutineScope(context).launch(context, start, block)
 
     @Suppress("SpellCheckingInspection", "DEPRECATION")
     private fun _requery(): Boolean {
         cache.clear()
-        return cursor?.requery() ?: false
+        val ret = cursor?.requery() ?: false
+        return ret
     }
 
     override fun isClosed(): Boolean =
@@ -275,22 +298,23 @@ open class CachingCursor(
 
     override val size: Int get() = count
 
-    override fun get(index: Int): Any? = _get(index)
+    override fun get(index: Int): T? = _get(index)
+
     override fun isEmpty(): Boolean = (size == 0)
 
-    override fun iterator(): Iterator<Any?> =
+    override fun iterator(): Iterator<T?> =
         CachingCursorListIterator(this)
 
-    override fun listIterator(): ListIterator<Any?> =
+    override fun listIterator(): ListIterator<T?> =
         CachingCursorListIterator(this)
 
-    override fun listIterator(index: Int): ListIterator<Any?> =
+    override fun listIterator(index: Int): ListIterator<T?> =
         CachingCursorListIterator(this, index)
 
-    override fun subList(fromIndex: Int, toIndex: Int): List<Any?> =
+    override fun subList(fromIndex: Int, toIndex: Int): List<T?> =
         (fromIndex..toIndex).map { idx -> get(idx) }
 
-    override fun lastIndexOf(element: Any?): Int {
+    override fun lastIndexOf(element: T?): Int {
         var curr: Int = size - 1
         while ((this[curr] != element) && (curr > 0)) {
             curr--
@@ -298,7 +322,7 @@ open class CachingCursor(
         return curr
     }
 
-    override fun indexOf(element: Any?): Int {
+    override fun indexOf(element: T?): Int {
         var curr = 0
         while ((this[curr] != element) && (curr < size)) {
             curr++
@@ -306,18 +330,18 @@ open class CachingCursor(
         return if (curr == size) -1 else curr
     }
 
-    override fun containsAll(elements: Collection<Any?>): Boolean {
+    override fun containsAll(elements: Collection<T?>): Boolean {
         TODO("Not yet implemented")
     }
 
-    override fun contains(element: Any?): Boolean {
+    override fun contains(element: T?): Boolean {
         TODO("Not yet implemented")
     }
 
-    data class CachedCursorItem(
+    data class CachedCursorItem<CT>(
         val idx: Int,
         val cached: Long,
-        val data: Any?
+        val data: CT?
     )
 
     companion object {
@@ -325,22 +349,22 @@ open class CachingCursor(
         @Deprecated("Please for better performance use view model with cursors.")
         @SuppressLint("Recycle")
         @Composable
-        fun rememberCursor(
+        fun <RT> rememberCursor(
             uri: Uri?,
             projection: Array<String>,
             selection: String? = null,
             selectionArgs: Array<String>? = null,
             sortOrder: String? = null,
-            transform: (Cursor) -> Any?,
-        ): CachingCursor? {
+            transform: (Cursor) -> RT?,
+        ): CachingCursor<RT>? {
             if (uri == null) return null
             else {
                 val context = LocalContext.current
                 var cursor by remember {
-                    mutableStateOf<CachingCursor?>(null)
+                    mutableStateOf<CachingCursor<RT>?>(null)
                 }
                 DisposableEffect(uri) {
-                    cursor = CachingCursor(
+                    cursor = CachingCursor<RT>(
                         context,
                         uri,
                         projection,

+ 5 - 5
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/CachingCursorListIterator.kt

@@ -9,10 +9,10 @@
 package org.mjdev.tvlib.helpers.cursor
 
 @Suppress("CanBeParameter")
-class CachingCursorListIterator(
-    private val cachingCursor: CachingCursor,
+class CachingCursorListIterator<T>(
+    private val cachingCursor: CachingCursor<T>,
     private val index: Int = 0
-) : ListIterator<Any?> {
+) : ListIterator<T?> {
 
     private var idx = index
 
@@ -20,7 +20,7 @@ class CachingCursorListIterator(
 
     override fun hasPrevious(): Boolean = idx > -1
 
-    override fun next(): Any? {
+    override fun next(): T? {
         val ret = cachingCursor[idx]
         idx = nextIndex()
         return ret
@@ -28,7 +28,7 @@ class CachingCursorListIterator(
 
     override fun nextIndex(): Int = idx + 1
 
-    override fun previous(): Any? {
+    override fun previous(): T? {
         idx = previousIndex()
         return cachingCursor[idx]
     }

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/PhotoCursor.kt

@@ -12,7 +12,7 @@ import android.content.Context
 
 class PhotoCursor(
     context: Context,
-) : CachingCursor(
+) : CachingCursor<PhotoItem>(
     context = context,
     uri = PhotoItem.URI,
     projection = PhotoItem.MEDIA_PROJECTION,

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/VideoCursor.kt

@@ -12,7 +12,7 @@ import android.content.Context
 
 class VideoCursor(
     context: Context
-) : CachingCursor(
+) : CachingCursor<VideoItem>(
     context = context,
     uri = VideoItem.URI,
     projection = VideoItem.MEDIA_PROJECTION,

+ 2 - 2
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/other/Observed.kt

@@ -30,8 +30,8 @@ open class Observed<T>(
 
     override fun component1(): T = value
 
-    override fun component2(): (T) -> Unit = {
-        value = it
+    override fun component2(): (T) -> Unit = { v ->
+        value = v
     }
 
 }