Skip to content

Commit bca171c

Browse files
committed
Sharded Sync: Data is split into Tracking, Progress, and Metadata shards for maximum reliability
1 parent a5ea67d commit bca171c

9 files changed

Lines changed: 266 additions & 179 deletions

File tree

app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
632632

633633
override fun onStart() {
634634
super.onStart()
635-
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
635+
val prefs = getSharedPreferences("cs3_sync_prefs", Context.MODE_PRIVATE)
636+
SyncManager.trySilentAuth(this)
636637
if (prefs.getBoolean("sync_auto_on_launch", false) && SyncManager.isEnabled(this)) {
637638
SyncManager.pull(this)
638639
}
@@ -670,7 +671,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
670671

671672
override fun onStop() {
672673
super.onStop()
673-
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
674+
val prefs = getSharedPreferences("cs3_sync_prefs", Context.MODE_PRIVATE)
674675
if (prefs.getBoolean("sync_auto_on_close", false) && SyncManager.isEnabled(this)) {
675676
SyncManager.push(this)
676677
}

app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -474,13 +474,56 @@ object PluginManager {
474474
}
475475
}
476476

477-
/**
478-
* Reloads all local plugins and forces a page update, used for hot reloading with deployWithAdb
479-
*
480-
* DO NOT USE THIS IN A PLUGIN! It may case an infinite recursive loop lagging or crashing everyone's devices.
481-
* If you use it from a plugin, do not expect a stable jvmName, SO DO NOT USE IT!
482-
*/
483477
@Suppress("FunctionName", "DEPRECATION_ERROR")
478+
479+
@Throws
480+
suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_restoreSyncPlugins(context: Context) {
481+
assertNonRecursiveCallstack()
482+
Log.d(TAG, "Restoring synced plugins...")
483+
484+
val onlinePlugins = getPluginsOnline().toList()
485+
Log.d(TAG, "Found ${onlinePlugins.size} plugins in sync list")
486+
487+
var pluginsChanged = false
488+
489+
val updatedPlugins = onlinePlugins.amap { savedData ->
490+
val oldFile = File(savedData.filePath)
491+
val parentName = oldFile.parentFile?.name
492+
val fileName = oldFile.name
493+
var currentData = savedData
494+
495+
if (parentName != null) {
496+
val newFile = File(context.filesDir, "$ONLINE_PLUGINS_FOLDER/$parentName/$fileName")
497+
Log.d(TAG, "Mapping plugin: ${savedData.internalName} -> ${newFile.absolutePath}")
498+
499+
if (savedData.filePath != newFile.absolutePath) {
500+
currentData = currentData.copy(filePath = newFile.absolutePath)
501+
pluginsChanged = true
502+
}
503+
504+
if (!newFile.exists() && currentData.url != null) {
505+
Log.d(TAG, "Missing plugin file, downloading: ${currentData.internalName}")
506+
val downloadedFile = downloadPluginToFile(
507+
currentData.url,
508+
newFile
509+
)
510+
if (downloadedFile == null) {
511+
Log.e(TAG, "Failed to download plugin ${currentData.internalName}")
512+
}
513+
}
514+
}
515+
currentData
516+
}
517+
518+
if (pluginsChanged) {
519+
setKey(PLUGINS_KEY, updatedPlugins.toTypedArray())
520+
}
521+
522+
___DO_NOT_CALL_FROM_A_PLUGIN_loadAllOnlinePlugins(context)
523+
}
524+
525+
@Suppress("FunctionName", "DEPRECATION_ERROR")
526+
484527
@Throws
485528
@Deprecated(
486529
"Calling this function from a plugin will lead to crashes, use loadPlugin and unloadPlugin",
@@ -498,14 +541,8 @@ object PluginManager {
498541
___DO_NOT_CALL_FROM_A_PLUGIN_loadAllLocalPlugins(activity, true)
499542
}
500543

501-
/**
502-
* @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins
503-
* and reload all pages even if they are previously valid
504-
*
505-
* DO NOT USE THIS IN A PLUGIN! It may case an infinite recursive loop lagging or crashing everyone's devices.
506-
* If you use it from a plugin, do not expect a stable jvmName, SO DO NOT USE IT!
507-
*/
508544
@Suppress("FunctionName", "DEPRECATION_ERROR")
545+
509546
@Deprecated(
510547
"Calling this function from a plugin will lead to crashes, use loadPlugin and unloadPlugin",
511548
replaceWith = ReplaceWith("loadPlugin"),
@@ -526,42 +563,31 @@ object PluginManager {
526563
}
527564

528565
val sortedPlugins = dir.listFiles()
529-
// Always sort plugins alphabetically for reproducible results
566+
Log.d(TAG, "Found ${sortedPlugins?.size ?: 0} local files in $LOCAL_PLUGINS_PATH")
567+
530568

531569
Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: ${sortedPlugins?.size}")
532570

533-
// Use app-specific external files directory and copy the file there.
534-
// We have to do this because on Android 14+, it otherwise gives SecurityException
535-
// due to dex files and setReadOnly seems to have no effect unless it it here.
536571
val pluginDirectory = File(context.getExternalFilesDir(null), "plugins")
537572
if (!pluginDirectory.exists()) {
538-
pluginDirectory.mkdirs() // Ensure the plugins directory exists
573+
pluginDirectory.mkdirs()
539574
}
540575

541-
// Make sure all local plugins are fully refreshed.
542576
removeKey(PLUGINS_KEY_LOCAL)
543577

544578
sortedPlugins?.sortedBy { it.name }?.amap { file ->
579+
Log.d(TAG, "Processing local file: ${file.name}")
545580
try {
546581
val destinationFile = File(pluginDirectory, file.name)
547582

548-
// Only copy the file if the destination file doesn't exist or if it
549-
// has been modified (check file length and modification time).
550583
if (!destinationFile.exists() ||
551584
destinationFile.length() != file.length() ||
552585
destinationFile.lastModified() != file.lastModified()
553586
) {
554-
555-
// Copy the file to the app-specific plugin directory
556587
file.copyTo(destinationFile, overwrite = true)
557-
558-
// After copying, set the destination file's modification time
559-
// to match the source file. We do this for performance so that we
560-
// can check the modification time and not make redundant writes.
561588
destinationFile.setLastModified(file.lastModified())
562589
}
563590

564-
// Load the plugin after it has been copied
565591
maybeLoadPlugin(context, destinationFile)
566592
} catch (t: Throwable) {
567593
Log.e(TAG, "Failed to copy the file")
@@ -578,11 +604,8 @@ object PluginManager {
578604
return checkSafeModeFile() || lastError != null
579605
}
580606

581-
/**
582-
* This can be used to override any extension loading to fix crashes!
583-
* @return true if safe mode file is present
584-
**/
585607
fun checkSafeModeFile(): Boolean {
608+
586609
return safe {
587610
val folder = File(CLOUD_STREAM_FOLDER)
588611
if (!folder.exists()) return@safe false
@@ -593,9 +616,7 @@ object PluginManager {
593616
} ?: false
594617
}
595618

596-
/**
597-
* @return True if successful, false if not
598-
* */
619+
599620
private suspend fun loadPlugin(context: Context, file: File, data: PluginData): Boolean {
600621
val fileName = file.nameWithoutExtension
601622
val filePath = file.absolutePath

app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.lagradost.cloudstream3.plugins
22

33
import android.content.Context
4+
import android.util.Log
45
import com.fasterxml.jackson.annotation.JsonProperty
56
import com.lagradost.cloudstream3.CloudStreamApp.Companion.context
67
import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey
@@ -144,6 +145,8 @@ object RepositoryManager {
144145
pluginUrl: String,
145146
file: File
146147
): File? {
148+
Log.d("RepositoryManager", "Downloading $pluginUrl to ${file.absolutePath}")
149+
147150
return safeAsync {
148151
file.mkdirs()
149152

0 commit comments

Comments
 (0)