Skip to content
Open
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
20 changes: 7 additions & 13 deletions app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -408,13 +408,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
return true
}

synchronized(apis) {
for (api in apis) {
if (str.startsWith(api.mainUrl)) {
loadResult(str, api.name, "")
return true
}
}
val matchedApi = apis.filter { str.startsWith(it.mainUrl) }.firstOrNull()
if (matchedApi != null) {
loadResult(str, matchedApi.name, "")
return true
}
}
}
Expand Down Expand Up @@ -809,12 +806,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
}


private val pluginsLock = Mutex()
private fun onAllPluginsLoaded(success: Boolean = false) {
ioSafe {
pluginsLock.withLock {
synchronized(allProviders) {
allProviders.withLock {
// Load cloned sites after plugins have been loaded since clones depend on plugins.
try {
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
Expand Down Expand Up @@ -1657,9 +1653,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
ioSafe {
initAll()
// No duplicates (which can happen by registerMainAPI)
apis = synchronized(allProviders) {
allProviders.distinctBy { it }
}
apis = allProviders.distinctBy { it }
}

// val navView: BottomNavigationView = findViewById(R.id.nav_view)
Expand Down Expand Up @@ -1967,7 +1961,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa

if (BuildConfig.DEBUG) {
var providersAndroidManifestString = "Current androidmanifest should be:\n"
synchronized(allProviders) {
allProviders.withLock {
for (api in allProviders) {
providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${
api.mainUrl.removePrefix(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import com.lagradost.cloudstream3.actions.temp.fcast.FcastAction
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.LinkLoadingResult
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.ExtractorLinkType
import com.lagradost.cloudstream3.utils.UiText
import kotlinx.coroutines.Dispatchers
Expand All @@ -43,7 +43,7 @@ import java.util.concurrent.FutureTask
import kotlin.reflect.jvm.jvmName

object VideoClickActionHolder {
val allVideoClickActions = threadSafeListOf(
val allVideoClickActions = atomicListOf(
// Default
PlayInBrowserAction(),
CopyClipboardAction(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.lagradost.cloudstream3.actions.VideoClickAction
import com.lagradost.cloudstream3.actions.VideoClickActionHolder
import kotlin.Throws


abstract class Plugin : BasePlugin() {
/**
* Called when your Plugin is loaded
Expand All @@ -26,9 +25,7 @@ abstract class Plugin : BasePlugin() {
fun registerVideoClickAction(element: VideoClickAction) {
Log.i(PLUGIN_TAG, "Adding ${element.name} VideoClickAction")
element.sourcePlugin = this.filename
synchronized(VideoClickActionHolder.allVideoClickActions) {
VideoClickActionHolder.allVideoClickActions.add(element)
}
VideoClickActionHolder.allVideoClickActions.add(element)
}

/**
Expand All @@ -40,4 +37,4 @@ abstract class Plugin : BasePlugin() {
* This will add a button in the settings allowing you to add custom settings
*/
var openSettings: ((context: Context) -> Unit)? = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -689,22 +689,13 @@ object PluginManager {
}

// remove all registered apis
synchronized(APIHolder.apis) {
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.filename }.forEach {
removePluginMapping(it)
}
}
synchronized(APIHolder.allProviders) {
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.filename }
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.filename }.forEach {
removePluginMapping(it)
}

synchronized(extractorApis) {
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.filename }
}

synchronized(VideoClickActionHolder.allVideoClickActions) {
VideoClickActionHolder.allVideoClickActions.removeIf { action: VideoClickAction -> action.sourcePlugin == plugin.filename }
}
APIHolder.allProviders.removeAll { provider -> provider.sourcePlugin == plugin.filename }
extractorApis.removeAll { provider -> provider.sourcePlugin == plugin.filename }
VideoClickActionHolder.allVideoClickActions.removeAll { action -> action.sourcePlugin == plugin.filename }

synchronized(classLoaders) {
classLoaders.values.removeIf { v -> v == plugin }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import com.lagradost.cloudstream3.syncproviders.providers.SubSourceApi
import com.lagradost.cloudstream3.ui.SyncWatchType
import com.lagradost.cloudstream3.ui.library.ListSorting
import com.lagradost.cloudstream3.utils.AppContextUtils.splitQuery
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.UiText
import com.lagradost.cloudstream3.utils.txt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleEntity
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleSearch
import com.lagradost.cloudstream3.subtitles.SubtitleResource
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf

/** Stateless safe abstraction of SubtitleAPI */
class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) {
Expand All @@ -24,26 +24,30 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) {
)

// maybe make this a generic struct? right now there is a lot of boilerplate
private val searchCache = threadSafeListOf<SavedSearchResponse>()
private val searchCache = atomicListOf<SavedSearchResponse>()
private var searchCacheIndex: Int = 0
private val resourceCache = threadSafeListOf<SavedResourceResponse>()
private val resourceCache = atomicListOf<SavedResourceResponse>()
private var resourceCacheIndex: Int = 0
const val CACHE_SIZE = 20
}

@WorkerThread
suspend fun resource(data: SubtitleEntity): Result<SubtitleResource> = runCatching {
synchronized(resourceCache) {
val cached = resourceCache.withLock {
var found: SubtitleResource? = null
for (item in resourceCache) {
// 20 min save
if (item.query == data && (unixTime - item.unixTime) < 60 * 20) {
return@runCatching item.response
found = item.response
break
}
}
found
}
if (cached != null) return@runCatching cached

val returnValue = api.resource(freshAuth(), data)
synchronized(resourceCache) {
resourceCache.withLock {
val add = SavedResourceResponse(unixTime, returnValue, data)
if (resourceCache.size > CACHE_SIZE) {
resourceCache[resourceCacheIndex] = add // rolling cache
Expand All @@ -58,22 +62,25 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) {
@WorkerThread
suspend fun search(query: SubtitleSearch): Result<List<SubtitleEntity>> {
return runCatching {
synchronized(searchCache) {
val cached = searchCache.withLock {
var found: List<SubtitleEntity>? = null
for (item in searchCache) {
// 120 min save
if (item.query == query && (unixTime - item.unixTime) < 60 * 120) {
return@runCatching item.response
found = item.response
break
}
}
found
}

val returnValue =
api.search(freshAuth(), query) ?: emptyList()
if (cached != null) return@runCatching cached
val returnValue = api.search(freshAuth(), query) ?: emptyList()

// only cache valid return values
if (returnValue.isNotEmpty()) {
val add = SavedSearchResponse(unixTime, returnValue, query)
synchronized(searchCache) {
searchCache.withLock {
if (searchCache.size > CACHE_SIZE) {
searchCache[searchCacheIndex] = add // rolling cache
searchCacheIndex = (searchCacheIndex + 1) % CACHE_SIZE
Expand All @@ -86,4 +93,3 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) {
}
}
}

20 changes: 11 additions & 9 deletions app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +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.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.Coroutines.atomicListOf
import com.lagradost.cloudstream3.utils.ExtractorLink
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
Expand Down Expand Up @@ -55,7 +55,7 @@ class APIRepository(val api: MainAPI) {
val hash: Pair<String, String>
)

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

Expand All @@ -66,9 +66,7 @@ class APIRepository(val api: MainAPI) {

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

Expand All @@ -91,21 +89,25 @@ class APIRepository(val api: MainAPI) {
val fixedUrl = api.fixUrl(url)
val lookingForHash = Pair(api.name, fixedUrl)

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

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)

synchronized(cache) {
cache.withLock {
if (cache.size > CACHE_SIZE) {
cache[cacheIndex] = add // rolling cache
cacheIndex = (cacheIndex + 1) % CACHE_SIZE
Expand Down Expand Up @@ -215,4 +217,4 @@ class APIRepository(val api: MainAPI) {
return false
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class HomeViewModel : ViewModel() {
private var currentShuffledList: List<SearchResponse> = listOf()

private fun autoloadRepo(): APIRepository {
return APIRepository(synchronized(apis) { apis.first { it.hasMainPage } })
return APIRepository(apis.withLock { apis.first { it.hasMainPage } })
}

private val _availableWatchStatusTypes =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,13 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(
syncId: SyncIdName,
apiName: String? = null,
) {
val availableProviders = synchronized(allProviders) {
allProviders.filter {
it.supportedSyncNames.contains(syncId)
}.map { it.name } +
// Add the api if it exists
(APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) }
?: emptyList())
}
val availableProviders = allProviders.filter {
it.supportedSyncNames.contains(syncId)
}.map { it.name } +
// Add the api if it exists
(APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) }
?: emptyList())

val baseOptions = listOf(
LibraryOpenerType.Default,
LibraryOpenerType.None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1685,14 +1685,13 @@ class ResultViewModel2 : ViewModel() {
}

val realRecommendations = ArrayList<SearchResponse>()
val apiNames = synchronized(apis) {
apis.filter {
it.name.contains("gogoanime", true) ||
it.name.contains("9anime", true)
}.map {
it.name
}
val apiNames = apis.filter {
it.name.contains("gogoanime", true) ||
it.name.contains("9anime", true)
}.map {
it.name
}

meta.recommendations?.forEach { rec ->
apiNames.forEach { name ->
realRecommendations.add(rec.copy(apiName = name))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SearchViewModel : ViewModel() {

private var suggestionJob: Job? = null

private var repos = synchronized(apis) { apis.map { APIRepository(it) } }
private var repos = apis.withLock { apis.map { APIRepository(it) } }

fun clearSearch() {
_searchResponse.postValue(Resource.Success(ExpandableSearchList(emptyList(), 0, false)))
Expand All @@ -68,7 +68,7 @@ class SearchViewModel : ViewModel() {
private var onGoingSearch: Job? = null

fun reloadRepos() {
repos = synchronized(apis) { apis.map { APIRepository(it) } }
repos = apis.withLock { apis.map { APIRepository(it) } }
}

fun searchAndCancel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() {
}

fun showAdd() {
val providers = synchronized(allProviders) { allProviders.distinctBy { it.javaClass }.sortedBy { it.name } }
val providers = allProviders.distinctBy { it::class }.sortedBy { it.name }
activity?.showDialog(
providers.map { "${it.name} (${it.mainUrl})" },
-1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ class SettingsProviders : BasePreferenceFragmentCompat() {

getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
activity?.getApiProviderLangSettings()?.let { currentLangTags ->
val languagesTagName = synchronized(APIHolder.apis) {
listOf( Pair(AllLanguagesName, getString(R.string.all_languages_preference)) ) +
APIHolder.apis.map { Pair(it.lang, getNameNextToFlagEmoji(it.lang) ?: it.lang) }
.toSet().sortedBy { it.second.substringAfter("\u00a0").lowercase() } // name ignoring flag emoji
val languagesTagName = APIHolder.apis.withLock {
listOf(Pair(AllLanguagesName, getString(R.string.all_languages_preference))) +
APIHolder.apis.map { Pair(it.lang, getNameNextToFlagEmoji(it.lang) ?: it.lang) }
.toSet().sortedBy { it.second.substringAfter("\u00a0").lowercase() }
}

val currentIndexList = currentLangTags.map { langTag ->
Expand Down
Loading