mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Fix crash when go backing too quickly (#309)
Fixes #306 Also fixes network-on-main and potential concurrency issues
This commit is contained in:
parent
78e7eb421f
commit
5fbe12e6cf
3 changed files with 54 additions and 42 deletions
|
|
@ -5,12 +5,17 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.github.damontecres.wholphin.data.ServerRepository
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||||
|
import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
|
||||||
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
|
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.google.common.cache.CacheBuilder
|
||||||
import com.google.common.cache.CacheLoader
|
import com.google.common.cache.CacheLoader
|
||||||
import dagger.hilt.android.qualifiers.ActivityContext
|
import dagger.hilt.android.qualifiers.ActivityContext
|
||||||
import dagger.hilt.android.scopes.ActivityScoped
|
import dagger.hilt.android.scopes.ActivityScoped
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.api.client.exception.InvalidStatusException
|
import org.jellyfin.sdk.api.client.exception.InvalidStatusException
|
||||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||||
|
|
@ -28,46 +33,47 @@ class DatePlayedService
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val api: ApiClient,
|
private val api: ApiClient,
|
||||||
|
@param:IoCoroutineScope private val scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val datePlayedCache =
|
private val datePlayedCache =
|
||||||
CacheBuilder
|
CacheBuilder
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.maximumSize(AppPreference.HomePageItems.max)
|
.maximumSize(AppPreference.HomePageItems.max)
|
||||||
.expireAfterWrite(2, TimeUnit.HOURS)
|
.expireAfterWrite(2, TimeUnit.HOURS)
|
||||||
.build<SeriesItemId, LocalDateTime?>(
|
.build<SeriesItemId, Deferred<LocalDateTime>>(
|
||||||
object :
|
object : CacheLoader<SeriesItemId, Deferred<LocalDateTime>>() {
|
||||||
CacheLoader<SeriesItemId, LocalDateTime>() {
|
override fun load(key: SeriesItemId): Deferred<LocalDateTime> = getLastPlayed(key)
|
||||||
override fun load(key: SeriesItemId): LocalDateTime = getLastPlayed(key)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getLastPlayed(key: SeriesItemId): LocalDateTime {
|
private fun getLastPlayed(key: SeriesItemId): Deferred<LocalDateTime> {
|
||||||
val request =
|
val request =
|
||||||
GetEpisodesRequest(
|
GetEpisodesRequest(
|
||||||
seriesId = key.seriesId,
|
seriesId = key.seriesId,
|
||||||
adjacentTo = key.itemId,
|
adjacentTo = key.itemId,
|
||||||
limit = 1,
|
limit = 1,
|
||||||
)
|
)
|
||||||
return try {
|
return scope.async(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
val result =
|
val result =
|
||||||
runBlocking {
|
|
||||||
GetEpisodesRequestHandler
|
GetEpisodesRequestHandler
|
||||||
.execute(
|
.execute(
|
||||||
api,
|
api,
|
||||||
request,
|
request,
|
||||||
).content.items
|
).content.items
|
||||||
}
|
|
||||||
result.firstOrNull()?.userData?.lastPlayedDate ?: LocalDateTime.MIN
|
result.firstOrNull()?.userData?.lastPlayedDate ?: LocalDateTime.MIN
|
||||||
} catch (ex: InvalidStatusException) {
|
} catch (ex: InvalidStatusException) {
|
||||||
Timber.w("Error fetching %s: %s", key, ex.localizedMessage)
|
Timber.w("Error fetching %s: %s", key, ex.localizedMessage)
|
||||||
LocalDateTime.MIN
|
LocalDateTime.MIN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getLastPlayed(item: BaseItem): LocalDateTime? {
|
suspend fun getLastPlayed(item: BaseItem): LocalDateTime? =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val seriesId = item.data.seriesId
|
val seriesId = item.data.seriesId
|
||||||
return if (seriesId != null) {
|
return@withContext if (seriesId != null) {
|
||||||
datePlayedCache.get(SeriesItemId(seriesId, item.id))
|
datePlayedCache.get(SeriesItemId(seriesId, item.id)).await()
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,11 @@ class NavigationManager
|
||||||
* Go to the previous page
|
* Go to the previous page
|
||||||
*/
|
*/
|
||||||
fun goBack() {
|
fun goBack() {
|
||||||
|
synchronized(this) {
|
||||||
|
if (backStack.size > 1) {
|
||||||
backStack.removeLastOrNull()
|
backStack.removeLastOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
log()
|
log()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -264,24 +264,26 @@ class HomeViewModel
|
||||||
private suspend fun buildCombined(
|
private suspend fun buildCombined(
|
||||||
resume: List<BaseItem>,
|
resume: List<BaseItem>,
|
||||||
nextUp: List<BaseItem>,
|
nextUp: List<BaseItem>,
|
||||||
): List<BaseItem> {
|
): List<BaseItem> =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
val semaphore = Semaphore(3)
|
val semaphore = Semaphore(3)
|
||||||
val deferred =
|
val deferred =
|
||||||
nextUp
|
nextUp
|
||||||
.filter { it.data.seriesId != null }
|
.filter { it.data.seriesId != null }
|
||||||
.map { item ->
|
.map { item ->
|
||||||
semaphore.withPermit {
|
viewModelScope.async(Dispatchers.IO) {
|
||||||
viewModelScope.async {
|
|
||||||
try {
|
try {
|
||||||
|
semaphore.withPermit {
|
||||||
datePlayedService.getLastPlayed(item)
|
datePlayedService.getLastPlayed(item)
|
||||||
|
}
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Timber.e(ex, "Error fetching %s", item.id)
|
Timber.e(ex, "Error fetching %s", item.id)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
val nextUpLastPlayed = deferred.awaitAll()
|
val nextUpLastPlayed = deferred.awaitAll()
|
||||||
val timestamps = mutableMapOf<UUID, LocalDateTime?>()
|
val timestamps = mutableMapOf<UUID, LocalDateTime?>()
|
||||||
nextUp.map { it.id }.zip(nextUpLastPlayed).toMap(timestamps)
|
nextUp.map { it.id }.zip(nextUpLastPlayed).toMap(timestamps)
|
||||||
|
|
@ -289,7 +291,7 @@ class HomeViewModel
|
||||||
val result = (resume + nextUp).sortedByDescending { timestamps[it.id] }
|
val result = (resume + nextUp).sortedByDescending { timestamps[it.id] }
|
||||||
val duration = (System.currentTimeMillis() - start).milliseconds
|
val duration = (System.currentTimeMillis() - start).milliseconds
|
||||||
Timber.v("buildCombined took %s", duration)
|
Timber.v("buildCombined took %s", duration)
|
||||||
return result
|
return@withContext result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setWatched(
|
fun setWatched(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue