Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package com.github.damontecres.wholphin.services

import android.content.Context
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.filter.CommunityRatingFilter
import com.github.damontecres.wholphin.data.filter.DecadeFilter
import com.github.damontecres.wholphin.data.filter.FavoriteFilter
import com.github.damontecres.wholphin.data.filter.FilterValueOption
import com.github.damontecres.wholphin.data.filter.FilterVideoType
import com.github.damontecres.wholphin.data.filter.GenreFilter
import com.github.damontecres.wholphin.data.filter.ItemFilterBy
import com.github.damontecres.wholphin.data.filter.OfficialRatingFilter
import com.github.damontecres.wholphin.data.filter.PlayedFilter
import com.github.damontecres.wholphin.data.filter.StudioFilter
import com.github.damontecres.wholphin.data.filter.VideoTypeFilter
import com.github.damontecres.wholphin.data.filter.YearFilter
import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
import com.github.damontecres.wholphin.ui.showToast
import com.mayakapps.kache.InMemoryKache
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.filterApi
import org.jellyfin.sdk.api.client.extensions.genresApi
import org.jellyfin.sdk.api.client.extensions.localizationApi
import org.jellyfin.sdk.api.client.extensions.studiosApi
import org.jellyfin.sdk.api.client.extensions.yearsApi
import org.jellyfin.sdk.model.UUID
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
import timber.log.Timber
import java.util.TreeSet
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.time.Duration.Companion.hours

/**
* Get the possible values for filters in a library
*/
@Singleton
class FilterOptionCache
@Inject
constructor(
@param:ApplicationContext private val context: Context,
@param:IoCoroutineScope private val ioCoroutineScope: CoroutineScope,
private val api: ApiClient,
private val serverRepository: ServerRepository,
) {
private val cache =
InMemoryKache<FilterOptionCacheKey, List<FilterValueOption>>(16) {
creationScope = ioCoroutineScope
expireAfterWriteDuration = 1.hours
}

/**
* Gets the possible values for a filter
*
* For example, the possible genres in the parent ID
*/
suspend fun getFilterOptionValues(
parentId: UUID?,
filterOption: ItemFilterBy<*>,
): List<FilterValueOption> {
val cacheKey = FilterOptionCacheKey(serverRepository.currentUser?.id, parentId, filterOption)
return try {
cache
.getOrPut(cacheKey) { (userId, parentId, filterOption) ->
getFilterOptionValues(userId, parentId, filterOption)
}.orEmpty()
} catch (ex: CancellationException) {
throw ex
} catch (ex: Exception) {
Timber.e(ex, "Error fetching options for %s", filterOption)
showToast(context, "Error occurred: ${ex.localizedMessage}")
emptyList()
}
}

private suspend fun getFilterOptionValues(
userId: UUID?,
parentId: UUID?,
filterOption: ItemFilterBy<*>,
) = when (filterOption) {
GenreFilter -> {
api.genresApi
.getGenres(
parentId = parentId,
userId = userId,
).content.items
.map { FilterValueOption(it.name ?: "", it.id) }
}

StudioFilter -> {
api.studiosApi
.getStudios(
parentId = parentId,
userId = userId,
includeItemTypes = listOf(BaseItemKind.SERIES),
).content.items
.map { FilterValueOption(it.name ?: "", it.id) }
}

FavoriteFilter,
PlayedFilter,
-> {
listOf(
FilterValueOption("True", null),
FilterValueOption("False", null),
)
}

OfficialRatingFilter -> {
val ratings =
api.localizationApi
.getParentalRatings()
.content
.associate {
it.name to it.value
}
api.filterApi
.getQueryFiltersLegacy(
parentId = parentId,
userId = userId,
).content.officialRatings
?.mapNotNull { r ->
val value = ratings[r]
value?.let { FilterValueOption(r, value) }
}?.sortedBy { it.value as Int }
.orEmpty()
}

VideoTypeFilter -> {
FilterVideoType.entries.map {
FilterValueOption(it.readable, it)
}
}

YearFilter -> {
api.yearsApi
.getYears(
parentId = parentId,
userId = userId,
sortBy = listOf(ItemSortBy.SORT_NAME),
sortOrder = listOf(SortOrder.ASCENDING),
).content.items
.mapNotNull {
it.name?.toIntOrNull()?.let { FilterValueOption(it.toString(), it) }
}
}

DecadeFilter -> {
val items = TreeSet<Int>()
api.yearsApi
.getYears(
parentId = parentId,
userId = userId,
sortBy = listOf(ItemSortBy.SORT_NAME),
sortOrder = listOf(SortOrder.ASCENDING),
).content.items
.mapNotNullTo(items) {
it.name
?.toIntOrNull()
?.div(10)
?.times(10)
}
items.toList().sorted().map { FilterValueOption("$it's", it) }
}

CommunityRatingFilter -> {
(1..10).map {
FilterValueOption("$it", it)
}
}
}

private data class FilterOptionCacheKey(
val userId: UUID?,
val parentId: UUID?,
val filterOption: ItemFilterBy<*>,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.FilterOptionCache
import com.github.damontecres.wholphin.services.MediaManagementService
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.MusicService
Expand All @@ -63,7 +64,6 @@ import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.ui.util.FilterUtils
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler
Expand Down Expand Up @@ -117,6 +117,7 @@ class CollectionFolderViewModel
private val musicService: MusicService,
val streamChoiceService: StreamChoiceService,
val mediaReportService: MediaReportService,
private val filterOptionCache: FilterOptionCache,
@Assisted val itemId: String,
@Assisted initialSortAndDirection: SortAndDirection?,
@Assisted("recursive") private val recursive: Boolean,
Expand Down Expand Up @@ -430,9 +431,7 @@ class CollectionFolderViewModel
}

suspend fun getFilterOptionValues(filterOption: ItemFilterBy<*>): List<FilterValueOption> =
FilterUtils.getFilterOptionValues(
api,
serverRepository.currentUser?.id,
filterOptionCache.getFilterOptionValues(
itemId.toUUID(),
filterOption,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.FilterOptionCache
import com.github.damontecres.wholphin.services.MediaManagementService
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.MusicService
Expand Down Expand Up @@ -101,7 +102,6 @@ import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.ui.util.FilterUtils
import com.github.damontecres.wholphin.ui.util.LocalClock
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler
Expand Down Expand Up @@ -142,6 +142,7 @@ class PlaylistViewModel
private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
private val favoriteWatchManager: FavoriteWatchManager,
private val mediaReportService: MediaReportService,
private val filterOptionCache: FilterOptionCache,
@Assisted itemId: UUID,
) : MusicViewModel(itemId, context, api, musicService, navigationManager, mediaManagementService) {
@AssistedFactory
Expand Down Expand Up @@ -301,9 +302,7 @@ class PlaylistViewModel
}

suspend fun getFilterOptionValues(filterOption: ItemFilterBy<*>): List<FilterValueOption> =
FilterUtils.getFilterOptionValues(
api,
serverRepository.currentUser?.id,
filterOptionCache.getFilterOptionValues(
itemId,
filterOption,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.FilterOptionCache
import com.github.damontecres.wholphin.services.ImageUrlService
import com.github.damontecres.wholphin.services.KeyValueService
import com.github.damontecres.wholphin.services.MediaManagementService
Expand All @@ -33,7 +34,6 @@ import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.ui.util.FilterUtils
import com.github.damontecres.wholphin.ui.util.ResStringProvider
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler
Expand Down Expand Up @@ -90,6 +90,7 @@ class CollectionViewModel
private val imageUrlService: ImageUrlService,
private val musicService: MusicService,
val mediaReportService: MediaReportService,
private val filterOptionCache: FilterOptionCache,
@Assisted private val itemId: UUID,
) : ViewModel() {
@AssistedFactory
Expand Down Expand Up @@ -352,9 +353,7 @@ class CollectionViewModel
}

suspend fun getPossibleFilterValues(filterOption: ItemFilterBy<*>): List<FilterValueOption> =
FilterUtils.getFilterOptionValues(
api,
serverRepository.currentUser?.id,
filterOptionCache.getFilterOptionValues(
itemId,
filterOption,
)
Expand Down
Loading
Loading