Skip to content

Commit 25151fd

Browse files
committed
fix(provider): incorrect provider order when updates are available
Fixes #129
1 parent 67efc26 commit 25151fd

File tree

5 files changed

+124
-76
lines changed

5 files changed

+124
-76
lines changed

data/provider/src/main/kotlin/com/flixclusive/data/provider/ProviderManager.kt

Lines changed: 110 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.compose.runtime.snapshotFlow
66
import com.flixclusive.core.datastore.AppSettingsManager
77
import com.flixclusive.core.ui.common.util.showToast
88
import com.flixclusive.core.util.coroutines.AppDispatchers
9+
import com.flixclusive.core.util.coroutines.AppDispatchers.Companion.withDefaultContext
910
import com.flixclusive.core.util.coroutines.AppDispatchers.Companion.withIOContext
1011
import com.flixclusive.core.util.exception.safeCall
1112
import com.flixclusive.core.util.log.errorLog
@@ -36,6 +37,7 @@ import okhttp3.OkHttpClient
3637
import java.io.File
3738
import java.io.IOException
3839
import java.io.InputStreamReader
40+
import java.util.Collections
3941
import javax.inject.Inject
4042
import javax.inject.Singleton
4143
import com.flixclusive.core.locale.R as LocaleR
@@ -52,10 +54,10 @@ class ProviderManager @Inject constructor(
5254
private val providerApiRepository: ProviderApiRepository
5355
) {
5456
/** Map containing all loaded providers */
55-
val providers: MutableMap<String, Provider> = LinkedHashMap()
56-
private val classLoaders: MutableMap<PathClassLoader, Provider> = HashMap()
57+
val providers: MutableMap<String, Provider> = Collections.synchronizedMap(LinkedHashMap())
58+
private val classLoaders: MutableMap<PathClassLoader, Provider> = Collections.synchronizedMap(HashMap())
5759

58-
private var notificationChannelHasBeensInitialized = false
60+
private var notificationChannelHasBeenInitialized = false
5961

6062
/**
6163
* An observable map of provider data
@@ -72,6 +74,7 @@ class ProviderManager @Inject constructor(
7274
private val updaterJsonMap = HashMap<String, List<ProviderData>>()
7375

7476
private val dynamicResourceLoader = DynamicResourceLoader(context = context)
77+
private val LOCAL_PATH_PREFIX = context.getExternalFilesDir(null)?.absolutePath + "/providers/"
7578

7679
val workingApis = snapshotFlow {
7780
providerDataList
@@ -108,18 +111,17 @@ class ProviderManager @Inject constructor(
108111

109112
if (failedToLoad.isNotEmpty()) {
110113
context.notifyOnError(
111-
shouldInitializeChannel = !notificationChannelHasBeensInitialized,
114+
shouldInitializeChannel = !notificationChannelHasBeenInitialized,
112115
providers = failedToLoad.keys,
113116
)
114117

115-
notificationChannelHasBeensInitialized = true
118+
notificationChannelHasBeenInitialized = true
116119
}
117120
}
118121
}
119122

120123
private suspend fun initializeLocalProviders() {
121-
val localPath = context.getExternalFilesDir(null)?.absolutePath + "/providers/"
122-
val localDir = File(localPath)
124+
val localDir = File(LOCAL_PATH_PREFIX)
123125

124126
if (!localDir.exists()) {
125127
val isSuccess = localDir.mkdirs()
@@ -275,11 +277,11 @@ class ProviderManager @Inject constructor(
275277
val hasNewErrors = needsDownload && failedToLoad.size - initialFailedToLoadProviders > 0
276278
if (hasNewErrors) {
277279
context.notifyOnError(
278-
shouldInitializeChannel = !notificationChannelHasBeensInitialized,
280+
shouldInitializeChannel = !notificationChannelHasBeenInitialized,
279281
providers = failedToLoad.keys,
280282
)
281283

282-
notificationChannelHasBeensInitialized = true
284+
notificationChannelHasBeenInitialized = true
283285
}
284286
}
285287

@@ -290,11 +292,16 @@ class ProviderManager @Inject constructor(
290292
* @param providerData The provider information
291293
*/
292294
@Suppress("UNCHECKED_CAST")
293-
private suspend fun loadProvider(file: File, providerData: ProviderData) {
295+
private suspend fun loadProvider(
296+
file: File,
297+
providerData: ProviderData
298+
) {
294299
val name = file.nameWithoutExtension
295300
val filePath = file.absolutePath
296301

297-
var providerPreference = getProviderPreference(name)
302+
val providerPosition = getPositionIndexFromSettings(
303+
name = name
304+
)
298305

299306
infoLog("Loading provider: $name")
300307

@@ -343,14 +350,16 @@ class ProviderManager @Inject constructor(
343350
}
344351
}
345352

346-
if (providerPreference == null) {
347-
providerPreference = ProviderPreference(
353+
val providerPreference = if (providerPosition > -1) {
354+
appSettingsManager.cachedProviderSettings.providers[providerPosition]
355+
} else {
356+
ProviderPreference(
348357
name = name,
349358
filePath = filePath,
350359
isDisabled = false
351-
)
352-
353-
loadProviderOnSettings(providerPreference)
360+
).also {
361+
loadProviderOnSettings(it)
362+
}
354363
}
355364

356365
if (!providerPreference.isDisabled) {
@@ -362,9 +371,16 @@ class ProviderManager @Inject constructor(
362371
)
363372
}
364373

365-
providerDataList.add(providerData)
366374
providers[name] = providerInstance
367375
classLoaders[loader] = providerInstance
376+
377+
if (providerPosition > -1) {
378+
providerDataList.add(
379+
index = providerPosition, element = providerData
380+
)
381+
} else {
382+
providerDataList.add(element = providerData)
383+
}
368384
} catch (e: Throwable) {
369385
if (isCrashingOnGetApiMethod(e)) {
370386
val message = context.getApiCrashMessage(provider = name)
@@ -384,10 +400,21 @@ class ProviderManager @Inject constructor(
384400
}
385401
}
386402

387-
private suspend fun loadProviderOnSettings(providerPreference: ProviderPreference) {
403+
private suspend fun loadProviderOnSettings(
404+
provider: ProviderPreference,
405+
index: Int = -1
406+
) {
388407
appSettingsManager.updateProviderSettings {
408+
val providersList = it.providers.toMutableList()
409+
410+
if (index > -1) {
411+
providersList[index] = provider
412+
} else {
413+
providersList.add(provider)
414+
}
415+
389416
it.copy(
390-
providers = it.providers + listOf(providerPreference)
417+
providers = providersList.toList()
391418
)
392419
}
393420
}
@@ -402,15 +429,13 @@ class ProviderManager @Inject constructor(
402429
providerData: ProviderData,
403430
unloadOnSettings: Boolean = true
404431
) {
405-
val provider = providers[providerData.name]
406-
val file = context.provideValidProviderPath(providerData)
407-
408-
if (provider == null || !file.exists()) {
409-
errorLog("Provider [${providerData.name}] not found. Cannot be unloaded")
410-
return
411-
}
432+
val index = getPositionIndexFromSettings(providerData.name)
433+
val providerPreference = appSettingsManager.cachedProviderSettings.providers[index]
412434

413-
unloadProvider(provider, file, unloadOnSettings)
435+
unloadProvider(
436+
providerPreference = providerPreference,
437+
unloadOnSettings = unloadOnSettings
438+
)
414439
}
415440

416441
/**
@@ -453,7 +478,10 @@ class ProviderManager @Inject constructor(
453478
providerApiRepository.remove(provider.name)
454479
providers.remove(provider.name)
455480
if (unloadOnSettings) {
456-
unloadProviderOnSettings(file.absolutePath)
481+
unloadProviderOnSettings(
482+
name = provider.name,
483+
path = file.absolutePath
484+
)
457485
}
458486
file.delete()
459487

@@ -469,51 +497,60 @@ class ProviderManager @Inject constructor(
469497
}
470498
}
471499

472-
private suspend fun unloadProviderOnSettings(path: String) {
500+
private suspend fun unloadProviderOnSettings(name: String, path: String) {
473501
appSettingsManager.updateProviderSettings {
474502
val newList = it.providers.toMutableList()
475503
newList.removeIf { providerPref ->
476-
providerPref.equals(path)
504+
providerPref.filePath == path
505+
&& providerPref.name == name
477506
}
478507

479508
it.copy(providers = newList)
480509
}
481510
}
482-
483-
private suspend fun reloadProviderOnSettings(providerPreference: ProviderPreference) {
484-
appSettingsManager.updateProviderSettings {
485-
val newList = it.providers.toMutableList()
486-
val indexOfProviderToReload = newList.indexOfFirst { savedProviderPreference ->
487-
providerPreference.name.equals(savedProviderPreference.name, true)
488-
&& providerPreference.filePath.equals(savedProviderPreference.filePath, true)
489-
}
490511

491-
newList[indexOfProviderToReload] = newList[indexOfProviderToReload].copy(
492-
name = providerPreference.name // Need to do this because name changes might occur for some instances.
493-
)
512+
private suspend fun reloadProviderOnSettings(
513+
oldProviderData: ProviderData,
514+
newProviderData: ProviderData
515+
) {
516+
val oldOrderPosition = getPositionIndexFromSettings(oldProviderData.name)
517+
val oldPreference = appSettingsManager.cachedProviderSettings.providers[oldOrderPosition]
494518

495-
it.copy(providers = newList)
496-
}
519+
val localPrefix = if (oldPreference.filePath.contains(LOCAL_PATH_PREFIX)) LOCAL_PATH_PREFIX else null
520+
val newPath = context.provideValidProviderPath(
521+
newProviderData,
522+
localPrefix = localPrefix
523+
)
524+
525+
loadProviderOnSettings(
526+
provider = oldPreference.copy(
527+
name = newProviderData.name,
528+
filePath = newPath.absolutePath
529+
),
530+
index = oldOrderPosition
531+
)
497532
}
498533

499-
suspend fun reloadProvider(providerData: ProviderData) {
500-
if (!providers.containsKey(providerData.name)) throw IllegalArgumentException("No such provider: ${providerData.name}")
501-
534+
suspend fun reloadProvider(
535+
oldProviderData: ProviderData,
536+
newProviderData: ProviderData
537+
) {
538+
if (!providers.containsKey(oldProviderData.name))
539+
throw IllegalArgumentException("No such provider: ${oldProviderData.name}")
540+
502541
unloadProvider(
503-
providerData = providerData,
542+
providerData = oldProviderData,
504543
unloadOnSettings = false
505544
)
506-
loadProvider(
507-
providerData = providerData,
508-
needsDownload = true
509-
)
510545

511546
reloadProviderOnSettings(
512-
providerPreference = ProviderPreference(
513-
name = providerData.name,
514-
filePath = context.provideValidProviderPath(providerData).absolutePath,
515-
isDisabled = false
516-
)
547+
oldProviderData = oldProviderData,
548+
newProviderData = newProviderData
549+
)
550+
551+
loadProvider(
552+
providerData = newProviderData,
553+
needsDownload = true
517554
)
518555
}
519556

@@ -557,7 +594,8 @@ class ProviderManager @Inject constructor(
557594
providerApiRepository.remove(providerData.name)
558595
} else {
559596
try {
560-
val api = providers[providerData.name]?.getApi(context, client)
597+
val api = providers[providerData.name]
598+
?.getApi(context, client)
561599

562600
if (api != null) {
563601
providerApiRepository.add(
@@ -580,23 +618,27 @@ class ProviderManager @Inject constructor(
580618
isDisabled: Boolean
581619
) {
582620
appSettingsManager.updateProviderSettings {
583-
val listOfSavedProviders = it.providers.toMutableList()
621+
withDefaultContext {
622+
val listOfSavedProviders = it.providers.toMutableList()
584623

585-
val indexOfProvider = listOfSavedProviders.indexOfFirst { provider ->
586-
provider.name.equals(name, true)
587-
}
588-
val provider = listOfSavedProviders[indexOfProvider]
624+
val indexOfProvider = listOfSavedProviders.indexOfFirst { provider ->
625+
provider.name.equals(name, true)
626+
}
627+
val provider = listOfSavedProviders[indexOfProvider]
589628

590-
listOfSavedProviders[indexOfProvider] = provider.copy(isDisabled = isDisabled)
629+
listOfSavedProviders[indexOfProvider] = provider.copy(isDisabled = isDisabled)
591630

592-
it.copy(providers = listOfSavedProviders.toList())
631+
it.copy(providers = listOfSavedProviders.toList())
632+
}
593633
}
594634
}
595635

596-
private fun getProviderPreference(name: String): ProviderPreference? {
597-
return appSettingsManager.cachedProviderSettings
598-
.providers
599-
.find { it.name.equals(name, true) }
636+
private suspend fun getPositionIndexFromSettings(name: String): Int {
637+
return withDefaultContext {
638+
appSettingsManager.cachedProviderSettings
639+
.providers
640+
.indexOfFirst { it.name.equals(name, true) }
641+
}
600642
}
601643

602644
/**

data/provider/src/main/kotlin/com/flixclusive/data/provider/util/FileHelper.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ internal fun rmrf(file: File) {
2424
}
2525

2626
fun Context.provideValidProviderPath(
27-
providerData: ProviderData
28-
) = File("${filesDir}/$PROVIDERS_FOLDER/${buildValidFilename(providerData.repositoryUrl!!)}/${buildValidFilename(providerData.name)}.flx")
27+
providerData: ProviderData,
28+
localPrefix: String? = null
29+
) = File("${localPrefix ?: "${filesDir}/$PROVIDERS_FOLDER/"}${buildValidFilename(providerData.repositoryUrl!!)}/${buildValidFilename(providerData.name)}.flx")
2930

3031
/**
3132
* Mutate the given filename to make it valid for a FAT filesystem,

domain/updater/src/main/kotlin/com/flixclusive/domain/updater/ProviderUpdaterUseCase.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import java.util.Collections
2020
import java.util.concurrent.TimeUnit
2121
import javax.inject.Inject
2222
import javax.inject.Singleton
23-
import com.flixclusive.core.ui.common.R as UiCommonR
2423
import com.flixclusive.core.locale.R as LocaleR
24+
import com.flixclusive.core.ui.common.R as UiCommonR
2525

2626

2727
private typealias VersionCode = Long
@@ -180,15 +180,18 @@ class ProviderUpdaterUseCase @Inject constructor(
180180
}
181181

182182
suspend fun updateProvider(providerName: String): Boolean {
183-
val providerData = providerManager.providerDataList.find {
183+
val oldProviderData = providerManager.providerDataList.find {
184184
it.name.equals(providerName, true)
185185
} ?: throw NoSuchElementException("No such provider data: $providerName")
186186

187-
val updateInfo = getLatestProviderData(providerName)
187+
val newProviderData = getLatestProviderData(providerName)
188188
?: return false
189189

190-
providerManager.reloadProvider(providerData)
191-
updatedProvidersMap[providerName] = updateInfo.versionCode
190+
providerManager.reloadProvider(
191+
oldProviderData,
192+
newProviderData
193+
)
194+
updatedProvidersMap[providerName] = newProviderData.versionCode
192195
return true
193196
}
194197

feature/mobile/provider-list/src/main/kotlin/com/flixclusive/feature/mobile/provider/ProvidersScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import androidx.compose.ui.text.withStyle
5050
import androidx.compose.ui.tooling.preview.Preview
5151
import androidx.compose.ui.unit.dp
5252
import androidx.compose.ui.unit.sp
53+
import androidx.compose.ui.util.fastFilter
5354
import androidx.hilt.navigation.compose.hiltViewModel
5455
import androidx.lifecycle.compose.collectAsStateWithLifecycle
5556
import com.flixclusive.core.theme.FlixclusiveTheme
@@ -112,7 +113,7 @@ internal fun ProvidersScreen(
112113
val filteredProviders by remember {
113114
derivedStateOf {
114115
when (viewModel.searchQuery.isNotEmpty() && searchExpanded.value) {
115-
true -> viewModel.providerDataList.filter {
116+
true -> viewModel.providerDataList.fastFilter {
116117
it.name.contains(viewModel.searchQuery, true)
117118
}
118119
false -> null

model/datastore/src/main/kotlin/com/flixclusive/model/datastore/provider/ProviderPreference.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ data class ProviderPreference(
1414
val name: String,
1515
val filePath: String,
1616
val isDisabled: Boolean,
17+
// TODO: Add lastUsedForSearching property
1718
) {
1819
override fun equals(other: Any?): Boolean {
1920
return when(other) {

0 commit comments

Comments
 (0)