@@ -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
0 commit comments