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
12 changes: 12 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ android {
"SIMKL_CLIENT_SECRET",
"\"" + (System.getenv("SIMKL_CLIENT_SECRET") ?: localProperties["simkl.secret"]) + "\""
)
buildConfigField(
"String",
"GOOGLE_CLIENT_ID",
"\"" + (localProperties["google.client_id"] ?: "") + "\""
)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

Expand Down Expand Up @@ -238,6 +243,13 @@ dependencies {
implementation(libs.nicehttp) // HTTP Lib

implementation(project(":library"))

// Google Drive Sync
implementation(libs.play.services.auth) // AuthorizationClient for Drive token
implementation(libs.credentials)
implementation(libs.credentials.play.services.auth)
implementation(libs.googleid)
implementation(libs.kotlinx.coroutines.play.services) // .await() on GMS Tasks
}

tasks.register<Jar>("androidSourcesJar") {
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STR
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_SHARE
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.localListApi
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.google.SyncManager
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.SyncWatchType
import com.lagradost.cloudstream3.ui.WatchType
Expand Down Expand Up @@ -515,6 +516,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
R.id.navigation_settings_extensions,
R.id.navigation_settings_plugins,
R.id.navigation_test_providers,
R.id.navigation_settings_sync,
).contains(destination.id)


Expand Down Expand Up @@ -628,6 +630,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
}

override fun onStart() {
super.onStart()
val prefs = getSharedPreferences("cs3_sync_prefs", Context.MODE_PRIVATE)
SyncManager.trySilentAuth(this)
if (prefs.getBoolean("sync_auto_on_launch", false) && SyncManager.isEnabled(this)) {
SyncManager.pull(this)
}
}

override fun onResume() {
super.onResume()
afterPluginsLoadedEvent += ::onAllPluginsLoaded
Expand Down Expand Up @@ -658,6 +669,14 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
}

override fun onStop() {
super.onStop()
val prefs = getSharedPreferences("cs3_sync_prefs", Context.MODE_PRIVATE)
if (prefs.getBoolean("sync_auto_on_close", false) && SyncManager.isEnabled(this)) {
SyncManager.push(this)
}
}

override fun dispatchKeyEvent(event: KeyEvent): Boolean =
CommonActivity.dispatchKeyEvent(this, event) ?: super.dispatchKeyEvent(event)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,13 +474,56 @@ object PluginManager {
}
}

/**
* Reloads all local plugins and forces a page update, used for hot reloading with deployWithAdb
*
* DO NOT USE THIS IN A PLUGIN! It may case an infinite recursive loop lagging or crashing everyone's devices.
* If you use it from a plugin, do not expect a stable jvmName, SO DO NOT USE IT!
*/
@Suppress("FunctionName", "DEPRECATION_ERROR")

@Throws
suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_restoreSyncPlugins(context: Context) {
assertNonRecursiveCallstack()
Log.d(TAG, "Restoring synced plugins...")

val onlinePlugins = getPluginsOnline().toList()
Log.d(TAG, "Found ${onlinePlugins.size} plugins in sync list")

var pluginsChanged = false

val updatedPlugins = onlinePlugins.amap { savedData ->
val oldFile = File(savedData.filePath)
val parentName = oldFile.parentFile?.name
val fileName = oldFile.name
var currentData = savedData

if (parentName != null) {
val newFile = File(context.filesDir, "$ONLINE_PLUGINS_FOLDER/$parentName/$fileName")
Log.d(TAG, "Mapping plugin: ${savedData.internalName} -> ${newFile.absolutePath}")

if (savedData.filePath != newFile.absolutePath) {
currentData = currentData.copy(filePath = newFile.absolutePath)
pluginsChanged = true
}

if (!newFile.exists() && currentData.url != null) {
Log.d(TAG, "Missing plugin file, downloading: ${currentData.internalName}")
val downloadedFile = downloadPluginToFile(
currentData.url,
newFile
)
if (downloadedFile == null) {
Log.e(TAG, "Failed to download plugin ${currentData.internalName}")
}
}
}
currentData
}

if (pluginsChanged) {
setKey(PLUGINS_KEY, updatedPlugins.toTypedArray())
}

___DO_NOT_CALL_FROM_A_PLUGIN_loadAllOnlinePlugins(context)
}

@Suppress("FunctionName", "DEPRECATION_ERROR")

@Throws
@Deprecated(
"Calling this function from a plugin will lead to crashes, use loadPlugin and unloadPlugin",
Expand All @@ -498,14 +541,8 @@ object PluginManager {
___DO_NOT_CALL_FROM_A_PLUGIN_loadAllLocalPlugins(activity, true)
}

/**
* @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins
* and reload all pages even if they are previously valid
*
* DO NOT USE THIS IN A PLUGIN! It may case an infinite recursive loop lagging or crashing everyone's devices.
* If you use it from a plugin, do not expect a stable jvmName, SO DO NOT USE IT!
*/
@Suppress("FunctionName", "DEPRECATION_ERROR")

@Deprecated(
"Calling this function from a plugin will lead to crashes, use loadPlugin and unloadPlugin",
replaceWith = ReplaceWith("loadPlugin"),
Expand All @@ -526,42 +563,31 @@ object PluginManager {
}

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


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

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

// Make sure all local plugins are fully refreshed.
removeKey(PLUGINS_KEY_LOCAL)

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

// Only copy the file if the destination file doesn't exist or if it
// has been modified (check file length and modification time).
if (!destinationFile.exists() ||
destinationFile.length() != file.length() ||
destinationFile.lastModified() != file.lastModified()
) {

// Copy the file to the app-specific plugin directory
file.copyTo(destinationFile, overwrite = true)

// After copying, set the destination file's modification time
// to match the source file. We do this for performance so that we
// can check the modification time and not make redundant writes.
destinationFile.setLastModified(file.lastModified())
}

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

/**
* This can be used to override any extension loading to fix crashes!
* @return true if safe mode file is present
**/
fun checkSafeModeFile(): Boolean {

return safe {
val folder = File(CLOUD_STREAM_FOLDER)
if (!folder.exists()) return@safe false
Expand All @@ -593,9 +616,7 @@ object PluginManager {
} ?: false
}

/**
* @return True if successful, false if not
* */

private suspend fun loadPlugin(context: Context, file: File, data: PluginData): Boolean {
val fileName = file.nameWithoutExtension
val filePath = file.absolutePath
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.plugins

import android.content.Context
import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.CloudStreamApp.Companion.context
import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey
Expand Down Expand Up @@ -144,6 +145,8 @@ object RepositoryManager {
pluginUrl: String,
file: File
): File? {
Log.d("RepositoryManager", "Downloading $pluginUrl to ${file.absolutePath}")

return safeAsync {
file.mkdirs()

Expand Down
Loading