diff --git a/src/main/resources/plugins/gitbucket-gist-plugin_2.12-4.9.0.jar b/src/main/resources/plugins/gitbucket-gist-plugin_2.12-4.9.0.jar new file mode 100644 index 0000000..88f789d --- /dev/null +++ b/src/main/resources/plugins/gitbucket-gist-plugin_2.12-4.9.0.jar Binary files differ diff --git a/src/main/resources/plugins/plugins b/src/main/resources/plugins/plugins index a550c0e..633f81f 100644 --- a/src/main/resources/plugins/plugins +++ b/src/main/resources/plugins/plugins @@ -1 +1 @@ -#gitbucket-gist-plugin_2.12-4.9.0.jar +gitbucket-gist-plugin_2.12-4.9.0.jar diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index f25b481..babda92 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -186,7 +186,7 @@ post("/admin/plugins/_reload")(adminOnly { PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn) - flash += "info" -> "All plugins are reloaded." + flash += "info" -> "All plugins were reloaded." redirect("/admin/plugins") }) @@ -194,13 +194,25 @@ val pluginId = params("pluginId") PluginRegistry().getPlugins() .collect { case (plugin, true) if plugin.pluginId == pluginId => plugin } - .foreach { plugin => + .foreach { _ => PluginRegistry.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn) flash += "info" -> s"${pluginId} was uninstalled." } redirect("/admin/plugins") }) + post("/admin/plugins/:pluginId/_install")(adminOnly { + val pluginId = params("pluginId") + PluginRegistry().getPlugins() + .collect { case (plugin, false) if plugin.pluginId == pluginId => plugin } + .foreach { _ => + PluginRegistry.install(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn) + flash += "info" -> s"${pluginId} was installed." + } + redirect("/admin/plugins") + }) + + get("/admin/users")(adminOnly { val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false) val users = getAllUsers(includeRemoved) diff --git a/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala b/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala index 329bdb0..02cc13c 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala @@ -208,7 +208,7 @@ */ def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized { instance.getPlugins() - .collect { case (plugin, true) if plugin.pluginId == plugin => plugin } + .collect { case (plugin, true) if plugin.pluginId == pluginId => plugin } .foreach { plugin => // try { // plugin.pluginClass.uninstall(instance, context, settings) @@ -216,30 +216,61 @@ // case e: Exception => // logger.error(s"Error during uninstalling plugin: ${plugin.pluginJar.getName}", e) // } - shutdown(context, settings) - plugin.pluginJar.delete() - instance = new PluginRegistry() - initialize(context, settings, conn) - } + shutdown(context, settings) + plugin.pluginJar.delete() + instance = new PluginRegistry() + initialize(context, settings, conn) + } } - private def copyFile(from: File, to: File, retry: Int = 0): Unit = { - using(FileChannel.open(from.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE)){ fc => - using(fc.tryLock()){ lock => - if(lock == null){ - if(retry >= 3){ // Retry max 3 times - logger.info(s"Retire to install plugin: ${from.getAbsolutePath}") - } else { - logger.info(s"Retry ${retry + 1} to install plugin: ${from.getAbsolutePath}") - Thread.sleep(500) - copyFile(from, to, retry + 1) - } - } else { - logger.info(s"Install plugin: ${from.getAbsolutePath}") - FileUtils.copyFile(from, to) - } + /** + * Install a specified plugin from local repository. + */ + def install(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized { + instance.getPlugins() + .collect { case (plugin, false) if plugin.pluginId == pluginId => plugin } + .foreach { plugin => + FileUtils.copyFile(plugin.pluginJar, new File(PluginHome, plugin.pluginJar.getName)) + + shutdown(context, settings) + instance = new PluginRegistry() + initialize(context, settings, conn) } - } + } + +// private def copyFile(from: File, to: File, retry: Int = 0): Unit = { +// using(FileChannel.open(from.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE)){ fc => +// using(fc.tryLock()){ lock => +// if(lock == null){ +// if(retry >= 3){ // Retry max 3 times +// logger.info(s"Retire to install plugin: ${from.getAbsolutePath}") +// } else { +// logger.info(s"Retry ${retry + 1} to install plugin: ${from.getAbsolutePath}") +// Thread.sleep(500) +// copyFile(from, to, retry + 1) +// } +// } else { +// logger.info(s"Install plugin: ${from.getAbsolutePath}") +// FileUtils.copyFile(from, to) +// } +// } +// } +// } + + private class PluginJarFileFilter extends FilenameFilter { + override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") + } + + private def listPluginJars(dir: File): Seq[File] = { + dir.listFiles(new PluginJarFileFilter()).map { file => + val Array(name, version) = file.getName.split("_2.12-") + (name, Version.valueOf(version.replaceFirst("\\.jar$", "")), file) + }.groupBy { case (name, _, _) => + name + }.map { case (name, versions) => + // Adopt the latest version + versions.sortBy { case (name, version, file) => version }.reverse.head._3 + }.toSeq.sortBy(_.getName) } /** @@ -256,28 +287,20 @@ } installedDir.mkdir() - if(pluginDir.exists && pluginDir.isDirectory){ - val files = pluginDir.listFiles(new FilenameFilter { - override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") - }).map { file => - val Array(name, version) = file.getName.split("_2.12-") - (name, Version.valueOf(version.replaceFirst("\\.jar$", "")), file) - }.groupBy { case (name, _, _) => - name - }.map { case (name, versions) => - // Adopt the latest version - versions.sortBy { case (name, version, file) => version }.reverse.head._3 - }.toSeq.sortBy(_.getName).foreach { pluginJar => + if(pluginDir.exists && pluginDir.isDirectory) { + listPluginJars(pluginDir).foreach { pluginJar => + val installedJar = new File(installedDir, pluginJar.getName) + FileUtils.copyFile(pluginJar, installedJar) + logger.info(s"Initialize ${pluginJar.getName}") - val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader) + val classLoader = new URLClassLoader(Array(installedJar.toURI.toURL), Thread.currentThread.getContextClassLoader) try { val plugin = classLoader.loadClass("Plugin").getDeclaredConstructor().newInstance().asInstanceOf[Plugin] - val pluginId = plugin.pluginId - -// // Check duplication -// instance.getPlugins().find(_.pluginId == pluginId).foreach { x => -// throw new IllegalStateException(s"Plugin ${pluginId} is duplicated. ${x.pluginJar.getName} is available.") -// } + // val pluginId = plugin.pluginId + // // Check duplication + // instance.getPlugins().find(_.pluginId == pluginId).foreach { x => + // throw new IllegalStateException(s"Plugin ${pluginId} is duplicated. ${x.pluginJar.getName} is available.") + // } // Migration val solidbase = new Solidbase() @@ -286,7 +309,7 @@ // Check database version val databaseVersion = manager.getCurrentVersion(plugin.pluginId) val pluginVersion = plugin.versions.last.getVersion - if(databaseVersion != pluginVersion){ + if (databaseVersion != pluginVersion) { throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}") } @@ -308,6 +331,38 @@ } } } + + // Scan repository + val repositoryDir = new File(PluginHome, ".repository") + if (repositoryDir.exists) { + listPluginJars(repositoryDir).foreach { pluginJar => + val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader) + try { + val plugin = classLoader.loadClass("Plugin").getDeclaredConstructor().newInstance().asInstanceOf[Plugin] + + val enableSameOrNewer = instance.plugins.exists { case (installedPlugin, true) => + installedPlugin.pluginId == plugin.pluginId && + Version.valueOf(installedPlugin.pluginVersion).greaterThanOrEqualTo(Version.valueOf(plugin.versions.last.getVersion)) + } + + if(!enableSameOrNewer){ + instance.addPlugin(PluginInfo( + pluginId = plugin.pluginId, + pluginName = plugin.pluginName, + pluginVersion = plugin.versions.last.getVersion, + description = plugin.description, + pluginClass = plugin, + pluginJar = pluginJar, + classLoader = classLoader + ), false) + } + } catch { + case e: Throwable => { + logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e) + } + } + } + } } if(watcher == null){ diff --git a/src/main/twirl/gitbucket/core/admin/plugins.scala.html b/src/main/twirl/gitbucket/core/admin/plugins.scala.html index e7e6084..4d65531 100644 --- a/src/main/twirl/gitbucket/core/admin/plugins.scala.html +++ b/src/main/twirl/gitbucket/core/admin/plugins.scala.html @@ -3,9 +3,9 @@ @gitbucket.core.admin.html.menu("plugins") { @gitbucket.core.helper.html.information(info)
-