Skip to content
Draft
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
89 changes: 72 additions & 17 deletions app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.newSearchResponseList
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf
import com.lagradost.cloudstream3.utils.ExtractorLink
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -55,18 +56,33 @@ class APIRepository(val api: MainAPI) {
val hash: Pair<String, String>
)

data class SavedHomePageResponse(
val unixTime: Long,
val response: List<HomePageResponse?>,
val hash: Pair<String, Pair<Int, Int?>>
)

private val cache = atomicListOf<SavedLoadResponse>()
private var cacheIndex: Int = 0
const val CACHE_SIZE = 20

private val homeCache = atomicListOf<SavedHomePageResponse>()
private var homeCacheIndex: Int = 0
const val HOME_CACHE_SIZE = 20

fun getTimeout(desired: Long?): Long {
return (desired ?: DEFAULT_TIMEOUT).coerceIn(MIN_TIMEOUT, MAX_TIMEOUT)
}

fun clearCache() {
cache.clear()
homeCache.clear()
}
}

private fun afterPluginsLoaded(forceReload: Boolean) {
if (forceReload) {
cache.clear()
clearCache()
}
}

Expand All @@ -88,31 +104,37 @@ class APIRepository(val api: MainAPI) {
if (isInvalidData(url)) throw ErrorLoadingException()
val fixedUrl = api.fixUrl(url)
val lookingForHash = Pair(api.name, fixedUrl)
val cacheTtl = DataStoreHelper.cacheTimeSeconds
val isCacheEnabled = DataStoreHelper.isCacheEnabled

val cached = cache.withLock {
var found: LoadResponse? = null
for (item in cache) {
// 10 min save
if (item.hash == lookingForHash && (unixTime - item.unixTime) < 60 * 10) {
found = item.response
break
if (isCacheEnabled) {
val cached = cache.withLock {
var found: LoadResponse? = null
for (item in cache) {
if (item.hash == lookingForHash && unixTime - item.unixTime < cacheTtl) {
found = item.response
break
}
}
found
}
found

if (cached != null) return@withTimeout cached
}

if (cached != null) return@withTimeout cached
api.load(fixedUrl)?.also { response ->
// Remove all blank tags as early as possible
response.tags = response.tags?.filter { it.isNotBlank() }
val add = SavedLoadResponse(unixTime, response, lookingForHash)

cache.withLock {
if (cache.size > CACHE_SIZE) {
cache[cacheIndex] = add // rolling cache
cacheIndex = (cacheIndex + 1) % CACHE_SIZE
} else {
cache.add(add)
if (isCacheEnabled) {
cache.withLock {
if (cache.size > CACHE_SIZE) {
cache[cacheIndex] = add // rolling cache
cacheIndex = (cacheIndex + 1) % CACHE_SIZE
} else {
cache.add(add)
}
}
}
} ?: throw ErrorLoadingException()
Expand Down Expand Up @@ -154,11 +176,30 @@ class APIRepository(val api: MainAPI) {
}

suspend fun getMainPage(page: Int, nameIndex: Int? = null): Resource<List<HomePageResponse?>> {
val lookingForHash = Pair(api.name, Pair(page, nameIndex))
val cacheTtl = DataStoreHelper.cacheTimeSeconds
val isCacheEnabled = DataStoreHelper.isCacheEnabled

if (isCacheEnabled) {
val cached = homeCache.withLock {
var found: List<HomePageResponse?>? = null
for (item in homeCache) {
if (item.hash == lookingForHash && unixTime - item.unixTime < cacheTtl) {
found = item.response
break
}
}
found
}

if (cached != null) return Resource.Success(cached)
}

return safeApiCall {
withTimeout(getTimeout(api.getMainPageTimeoutMs)) {
api.lastHomepageRequest = unixTimeMS

nameIndex?.let { api.mainPage.getOrNull(it) }?.let { data ->
val res = nameIndex?.let { api.mainPage.getOrNull(it) }?.let { data ->
listOf(
api.getMainPage(
page,
Expand Down Expand Up @@ -191,6 +232,20 @@ class APIRepository(val api: MainAPI) {
}
}
}

if (isCacheEnabled && res.isNotEmpty()) {
val add = SavedHomePageResponse(unixTime, res, lookingForHash)
homeCache.withLock {
if (homeCache.size > HOME_CACHE_SIZE) {
homeCache[homeCacheIndex] = add // rolling cache
homeCacheIndex = (homeCacheIndex + 1) % HOME_CACHE_SIZE
} else {
homeCache.add(add)
}
}
}

res
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ open class ParentItemAdapter(
) {
val binding = holder.view
if (binding !is HomepageParentBinding) return
(binding.homeChildRecyclerview.adapter as? HomeChildItemAdapter)?.submitList(item.list.list)
(binding.homeChildRecyclerview.adapter as? HomeChildItemAdapter)?.apply {
isHorizontal = item.list.isHorizontalImages
hasNext = item.hasNext
submitList(item.list.list)
}
}

override fun onBindContent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.findViewTreeLifecycleOwner
Expand Down Expand Up @@ -652,7 +653,7 @@ class HomeParentItemAdapterPreview(
}

private fun updatePreview(preview: Resource<Pair<Boolean, List<LoadResponse>>>) {
if (preview is Resource.Success) {
if (preview is Resource.Success || preview is Resource.Loading) {
homeNonePadding.apply {
val params = layoutParams
params.height = 0
Expand Down Expand Up @@ -693,6 +694,14 @@ class HomeParentItemAdapterPreview(
}
}

is Resource.Loading -> {
previewAdapter.submitList(listOf())
previewViewpager.setCurrentItem(0, false)
previewViewpager.isInvisible = true
previewViewpagerText.isVisible = true
alternativeAccountPadding?.isVisible = false
}

else -> {
previewAdapter.submitList(listOf())
previewViewpager.setCurrentItem(0, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ class HomeViewModel : ViewModel() {
}
}

_page.postValue(Resource.Success(expandable))

val items = data.value.mapNotNull { it?.items }.flatten()


Expand All @@ -375,28 +377,31 @@ class HomeViewModel : ViewModel() {
context?.filterSearchResultByFilmQuality(currentList.shuffled())
?: currentList.shuffled()

updatePreviewResponses(
previewResponses,
previewResponsesAdded,
randomItems,
3
)

_randomItems.postValue(randomItems)
currentShuffledList = randomItems

addJob?.cancel()
addJob = ioSafe {
updatePreviewResponses(
previewResponses,
previewResponsesAdded,
randomItems,
3
)

if (previewResponses.isEmpty()) {
_preview.postValue(
Resource.Failure(
false,
"No homepage responses"
)
)
} else {
_preview.postValue(Resource.Success((previewResponsesAdded.size < currentShuffledList.size) to previewResponses))
}
}
}
}
if (previewResponses.isEmpty()) {
_preview.postValue(
Resource.Failure(
false,
"No homepage responses"
)
)
} else {
_preview.postValue(Resource.Success((previewResponsesAdded.size < currentShuffledList.size) to previewResponses))
}
_page.postValue(Resource.Success(expandable))
} catch (e: Exception) {
_randomItems.postValue(emptyList())
logError(e)
Expand Down Expand Up @@ -512,10 +517,14 @@ class HomeViewModel : ViewModel() {

// if we don't need to reload and we have a valid homepage or currently loading the same thing then return
val currentLoading = isCurrentlyLoadingName
if (!forceReload && (currentPage is Resource.Success && currentPage.value.isNotEmpty() || (currentLoading != null && currentLoading == preferredApiName))) {
if (!forceReload && ((_apiName.value == preferredApiName && currentPage is Resource.Success && currentPage.value.isNotEmpty()) || (currentLoading != null && currentLoading == preferredApiName))) {
return@ioSafe
}

if (forceReload) {
APIRepository.clearCache()
}

val api = getApiFromNameNull(preferredApiName)
if (preferredApiName == noneApi.name) {
// just set to random
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ package com.lagradost.cloudstream3.ui.settings

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.content.edit
import androidx.navigation.fragment.findNavController
import androidx.navigation.NavOptions
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.BasePreferenceFragmentCompat
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.AppContextUtils.getApiDubstatusSettings
import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettings
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
Expand All @@ -33,6 +38,42 @@ class SettingsProviders : BasePreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.settings_providers, rootKey)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())

val cachePref = getPref(R.string.cache_time_key)
val cacheNames = resources.getStringArray(R.array.cache_time_names)
val cacheValues = resources.getIntArray(R.array.cache_time_values)

fun updateCacheSummary() {
val idx = cacheValues.indexOf(DataStoreHelper.cacheTimeMinutes)
cachePref?.summary = cacheNames.getOrNull(idx) ?: "${DataStoreHelper.cacheTimeMinutes}m"
}
updateCacheSummary()

cachePref?.setOnPreferenceClickListener {
val currentIndex = cacheValues.indexOf(DataStoreHelper.cacheTimeMinutes).coerceAtLeast(0)
activity?.showBottomDialog(
cacheNames.toList(),
currentIndex,
getString(R.string.cache_time_settings),
false,
{}
) { selectedIndex ->
DataStoreHelper.cacheTimeMinutes = cacheValues.getOrElse(selectedIndex) { 0 }
updateCacheSummary()
}
true
}

getPref(R.string.clear_provider_cache_key)?.setOnPreferenceClickListener {
try {
APIRepository.clearCache()
RepoLinkGenerator.cache.clear()
showToast(R.string.clear_provider_cache_cleared, Toast.LENGTH_SHORT)
} catch (e: Exception) {
logError(e)
}
true
}

getPref(R.string.display_sub_key)?.setOnPreferenceClickListener {
activity?.getApiDubstatusSettings()?.let { current ->
val dublist = DubStatus.entries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,17 @@ class UserPreferenceDelegate<T : Any>(
private val default: T,
) {
private val klass: KClass<out T> = default::class
private var cache: T? = null
private val realKey get() = "${DataStoreHelper.currentAccount}/$key"
operator fun getValue(self: Any?, property: KProperty<*>) =
getKeyClass(realKey, klass.java) ?: default
cache ?: getKeyClass(realKey, klass.java).also { newCache -> cache = newCache } ?: default

operator fun setValue(
self: Any?,
property: KProperty<*>,
t: T?,
) {
cache = t
if (t == null) {
removeKey(realKey)
} else {
Expand Down Expand Up @@ -160,6 +162,14 @@ object DataStoreHelper {
_resultsSortingMode = value.ordinal
}

var cacheTimeMinutes: Int by UserPreferenceDelegate(
"cache_time_pref",
0
)

val isCacheEnabled: Boolean get() = cacheTimeMinutes > 0
val cacheTimeSeconds: Long get() = cacheTimeMinutes * 60L

@Serializable
data class Account(
@JsonProperty("keyIndex") @SerialName("keyIndex") val keyIndex: Int,
Expand Down
Loading