From 93a9e24686811e87a84ac4e3712b7fb00fabcc6e Mon Sep 17 00:00:00 2001 From: Ankan Pal Date: Sun, 23 Nov 2025 10:45:30 +0530 Subject: [PATCH 1/6] Do not auto create local plugins path --- .../lagradost/cloudstream3/plugins/PluginManager.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 1cffa7c1bfb..58b87d1d2bc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -517,11 +517,11 @@ object PluginManager { val dir = File(LOCAL_PLUGINS_PATH) if (!dir.exists()) { - val res = dir.mkdirs() - if (!res) { - Log.w(TAG, "Failed to create local directories") - return - } + Log.d(TAG, "No local plugins folder found at '${LOCAL_PLUGINS_PATH}'") + + // Since there are no local plugins, we can consider them loaded + loadedLocalPlugins = true + return } val sortedPlugins = dir.listFiles() From c2eadaa1f2ec655167d3db9935e4f18321531d4d Mon Sep 17 00:00:00 2001 From: Ankan Pal Date: Sun, 23 Nov 2025 10:48:55 +0530 Subject: [PATCH 2/6] Refactor plugin directory handling to use internal files directory and clean up stale plugins --- .../java/com/lagradost/cloudstream3/plugins/PluginManager.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 58b87d1d2bc..b2336047b5b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -532,13 +532,15 @@ object PluginManager { // 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") + val pluginDirectory = File(context.filesDir, "plugins") if (!pluginDirectory.exists()) { pluginDirectory.mkdirs() // Ensure the plugins directory exists } // Make sure all local plugins are fully refreshed. removeKey(PLUGINS_KEY_LOCAL) + // Clean up all existing files to avoid stale plugins + pluginDirectory.listFiles()?.forEach { it.delete() } sortedPlugins?.sortedBy { it.name }?.amap { file -> try { From 613b503a002902fec9b2ec66f674d044608af11d Mon Sep 17 00:00:00 2001 From: Ankan Pal Date: Sun, 23 Nov 2025 12:36:40 +0530 Subject: [PATCH 3/6] Update comment --- .../java/com/lagradost/cloudstream3/plugins/PluginManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index b2336047b5b..f2e971d7641 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -529,9 +529,9 @@ object PluginManager { Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: ${sortedPlugins?.size}") - // Use app-specific external files directory and copy the file there. + // Use app-specific 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. + // due to dex files and setReadOnly seems to have no effect unless it is here. val pluginDirectory = File(context.filesDir, "plugins") if (!pluginDirectory.exists()) { pluginDirectory.mkdirs() // Ensure the plugins directory exists From ab51093b69b40edebc43f640970da56ad280be44 Mon Sep 17 00:00:00 2001 From: Ankan Pal Date: Sun, 23 Nov 2025 12:43:40 +0530 Subject: [PATCH 4/6] First clean copied plugin directory then scan for local plugins --- .../cloudstream3/plugins/PluginManager.kt | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index f2e971d7641..e2102195d76 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -514,21 +514,6 @@ object PluginManager { suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_loadAllLocalPlugins(context: Context, forceReload: Boolean) { assertNonRecursiveCallstack() - val dir = File(LOCAL_PLUGINS_PATH) - - if (!dir.exists()) { - Log.d(TAG, "No local plugins folder found at '${LOCAL_PLUGINS_PATH}'") - - // Since there are no local plugins, we can consider them loaded - loadedLocalPlugins = true - return - } - - val sortedPlugins = dir.listFiles() - // Always sort plugins alphabetically for reproducible results - - Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: ${sortedPlugins?.size}") - // Use app-specific 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 is here. @@ -539,9 +524,23 @@ object PluginManager { // Make sure all local plugins are fully refreshed. removeKey(PLUGINS_KEY_LOCAL) + // Clean up all existing files to avoid stale plugins pluginDirectory.listFiles()?.forEach { it.delete() } + val dir = File(LOCAL_PLUGINS_PATH) + + if (!dir.exists()) { + Log.d(TAG, "No local plugins folder found at '${LOCAL_PLUGINS_PATH}'") + // Since there are no local plugins, we can consider them loaded + loadedLocalPlugins = true + return + } + + // Always sort plugins alphabetically for reproducible results + val sortedPlugins = dir.listFiles() + Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: ${sortedPlugins?.size}") + sortedPlugins?.sortedBy { it.name }?.amap { file -> try { val destinationFile = File(pluginDirectory, file.name) From c0cc95a525d6d17d43775cc7d3e3e2de2c231f02 Mon Sep 17 00:00:00 2001 From: Ankan Pal Date: Sun, 23 Nov 2025 14:45:58 +0530 Subject: [PATCH 5/6] Remove stale plugins only if they do not exist in local plugins folder --- .../cloudstream3/plugins/PluginManager.kt | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index e2102195d76..293e8d2309b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -525,23 +525,35 @@ object PluginManager { // Make sure all local plugins are fully refreshed. removeKey(PLUGINS_KEY_LOCAL) - // Clean up all existing files to avoid stale plugins - pluginDirectory.listFiles()?.forEach { it.delete() } - val dir = File(LOCAL_PLUGINS_PATH) if (!dir.exists()) { - Log.d(TAG, "No local plugins folder found at '${LOCAL_PLUGINS_PATH}'") - // Since there are no local plugins, we can consider them loaded - loadedLocalPlugins = true - return + Log.d(TAG, "No local plugins folder found at '${LOCAL_PLUGINS_PATH}'") + // Clean up all existing plugins since the local plugins folder doesn't exist + // This is required since the user might stop using local plugins but still have old + // plugins lying around taking up space. + // These cannot be deleted without manually by the user without root access + pluginDirectory.listFiles()?.forEach { it.delete() } + + // Since there are no local plugins, we can consider them loaded + loadedLocalPlugins = true + return } // Always sort plugins alphabetically for reproducible results - val sortedPlugins = dir.listFiles() + val sortedPlugins = dir.listFiles()?.sortedBy { it.name } Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: ${sortedPlugins?.size}") - sortedPlugins?.sortedBy { it.name }?.amap { file -> + val sortedPluginNames = sortedPlugins?.mapTo(HashSet()) { it.name } + pluginDirectory.listFiles()?.sortedBy { it.name }?.amap { + // If the plugin doesn't exist in the local plugins folder, delete it + if (!it.isDirectory && sortedPluginNames?.contains(it.name) == false) { + Log.d(TAG, "Deleting removed local plugin: ${it.name}") + it.delete() + } + } + + sortedPlugins?.amap { file -> try { val destinationFile = File(pluginDirectory, file.name) From a01493abd297ce3392f03fb96e077bdde4dcc7b9 Mon Sep 17 00:00:00 2001 From: Ankan Pal Date: Sun, 23 Nov 2025 14:58:59 +0530 Subject: [PATCH 6/6] fix: Typo in comment --- .../java/com/lagradost/cloudstream3/plugins/PluginManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 293e8d2309b..816a67dc68a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -532,7 +532,7 @@ object PluginManager { // Clean up all existing plugins since the local plugins folder doesn't exist // This is required since the user might stop using local plugins but still have old // plugins lying around taking up space. - // These cannot be deleted without manually by the user without root access + // These cannot be deleted manually by the user without root access pluginDirectory.listFiles()?.forEach { it.delete() } // Since there are no local plugins, we can consider them loaded