From e64feeb82f9e919e744ce027ef6f0cabde76e1ca Mon Sep 17 00:00:00 2001 From: koby455 Date: Sun, 21 Jun 2026 20:19:34 +0300 Subject: [PATCH] Fix plugin cloud sync: wire invalidation bus to PluginManager Bug: PluginManager.triggerRemoteSync() always returned early due to dead code (if (true) { return }) from old Supabase auth guard. Plugin adds/removes/toggles were saved locally but never pushed to cloud. Fix: - Add CloudSyncScope.PLUGINS to the invalidation scope enum - Add PLUGINS debounce (1000ms) to CloudSyncCoordinator - Inject CloudSyncInvalidationBus into sideload PluginManager - Replace dead triggerRemoteSync() with invalidationBus.markDirty() - Wire markDirty() to: addRepository, removeRepository, toggleScraper, toggleAllScrapersForRepo, setPluginsEnabled, flushPendingSync --- .../data/repository/CloudSyncCoordinator.kt | 1 + .../repository/CloudSyncInvalidationBus.kt | 3 +- .../arflix/tv/core/plugin/PluginManager.kt | 40 ++++++++----------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncCoordinator.kt b/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncCoordinator.kt index c089e352..b1d3c532 100644 --- a/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncCoordinator.kt +++ b/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncCoordinator.kt @@ -98,6 +98,7 @@ class CloudSyncCoordinator @Inject constructor( return when (scope) { CloudSyncScope.LOCAL_HISTORY -> 2_000L CloudSyncScope.IPTV -> 750L + CloudSyncScope.PLUGINS -> 1_000L else -> 500L } } diff --git a/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncInvalidationBus.kt b/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncInvalidationBus.kt index 67e423f0..b697ec99 100644 --- a/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncInvalidationBus.kt +++ b/app/src/main/kotlin/com/arflix/tv/data/repository/CloudSyncInvalidationBus.kt @@ -15,7 +15,8 @@ enum class CloudSyncScope { IPTV, WATCHLIST, LOCAL_HISTORY, - ACCOUNT + ACCOUNT, + PLUGINS } data class CloudSyncInvalidation( diff --git a/app/src/sideload/kotlin/com/arflix/tv/core/plugin/PluginManager.kt b/app/src/sideload/kotlin/com/arflix/tv/core/plugin/PluginManager.kt index 43c40223..519a24b4 100644 --- a/app/src/sideload/kotlin/com/arflix/tv/core/plugin/PluginManager.kt +++ b/app/src/sideload/kotlin/com/arflix/tv/core/plugin/PluginManager.kt @@ -7,6 +7,8 @@ import com.arflix.tv.core.plugin.cloudstream.ExternalExtensionLoader import com.arflix.tv.core.plugin.cloudstream.ExternalExtensionRunner import com.arflix.tv.core.plugin.cloudstream.ExternalRepoParser import com.arflix.tv.data.local.PluginDataStore +import com.arflix.tv.data.repository.CloudSyncInvalidationBus +import com.arflix.tv.data.repository.CloudSyncScope import com.arflix.tv.domain.model.ExternalPluginEntry import com.arflix.tv.domain.model.LocalScraperResult import com.arflix.tv.domain.model.PluginManifest @@ -65,7 +67,8 @@ class PluginManager @Inject constructor( private val runtime: PluginRuntime, private val externalRepoParser: ExternalRepoParser, private val externalExtensionLoader: ExternalExtensionLoader, - private val externalExtensionRunner: ExternalExtensionRunner + private val externalExtensionRunner: ExternalExtensionRunner, + private val invalidationBus: CloudSyncInvalidationBus ) { private val moshi = Moshi.Builder() .addLast(KotlinJsonAdapterFactory()) @@ -247,28 +250,21 @@ class PluginManager @Inject constructor( fun flushPendingSync() { if (pendingPushAfterSync) { pendingPushAfterSync = false - Log.d(TAG, "flushPendingSync: firing deferred push") - triggerRemoteSync() + Log.d(TAG, "flushPendingSync: firing deferred push after remote sync") + triggerRemoteSync("flush after remote sync") } } private var syncJob: kotlinx.coroutines.Job? = null - private fun triggerRemoteSync() { + private fun triggerRemoteSync(reason: String = "plugin change") { if (isSyncingFromRemote) { - Log.d(TAG, "triggerRemoteSync: skipped (syncing from remote), will push after sync") + Log.d(TAG, "triggerRemoteSync: deferred (syncing from remote)") pendingPushAfterSync = true return } - if (true /* !authManager.isAuthenticated */) { - Log.d(TAG, "triggerRemoteSync: skipped (not authenticated, simulated)") - return - } - Log.d(TAG, "triggerRemoteSync: scheduling push in 500ms") - syncJob?.cancel() - syncJob = syncScope.launch { - kotlinx.coroutines.delay(500) - } + Log.d(TAG, "triggerRemoteSync: marking dirty — $reason") + invalidationBus.markDirty(CloudSyncScope.PLUGINS, null, reason) } // Combined flow of enabled scrapers @@ -399,7 +395,7 @@ class PluginManager @Inject constructor( downloadJsScrapers(repo.id, canonicalManifestUrl, manifest.getActiveScrapers()) Log.d(TAG, "NuvioTV repository added: ${repo.name} with ${manifest.getActiveScrapers().size} scrapers") - triggerRemoteSync() + triggerRemoteSync("repo added: ${repo.name}") return Result.success(repo) } @@ -430,7 +426,7 @@ class PluginManager @Inject constructor( downloadDexExtensions(repo.id, parseResult.plugins) Log.d(TAG, "External repository added: ${repo.name} with ${parseResult.plugins.size} extensions") - triggerRemoteSync() + triggerRemoteSync("repo added: ${repo.name}") return Result.success(repo) } @@ -457,14 +453,7 @@ class PluginManager @Inject constructor( // Remove repository dataStore.removeRepository(repoId) - // Push synchronously when user-initiated (not during reconciliation) - // to prevent the next sync pull from re-adding the removed repo - if (!isSyncingFromRemote && false /* authManager.isAuthenticated */) { - Log.d(TAG, "removeRepository: pushing removal to remote synchronously") - // pluginSyncService.pushToRemote() - } else if (isSyncingFromRemote) { - pendingPushAfterSync = true - } + triggerRemoteSync("repo removed: $repoId") } @@ -603,6 +592,7 @@ class PluginManager @Inject constructor( if (scraper.id == scraperId) scraper.copy(enabled = enabled) else scraper } dataStore.saveScrapers(updatedScrapers) + triggerRemoteSync("scraper toggled: $scraperId=$enabled") } /** @@ -614,6 +604,7 @@ class PluginManager @Inject constructor( if (scraper.repositoryId == repoId) scraper.copy(enabled = enabled) else scraper } dataStore.saveScrapers(updatedScrapers) + triggerRemoteSync("all scrapers toggled for repo: $repoId=$enabled") } /** @@ -621,6 +612,7 @@ class PluginManager @Inject constructor( */ suspend fun setPluginsEnabled(enabled: Boolean) { dataStore.setPluginsEnabled(enabled) + triggerRemoteSync("plugins enabled=$enabled") } suspend fun setGroupStreamsByRepository(enabled: Boolean) {