From d8fe6a0a55cca81e1993153d8cf6253fe9963cab Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Fri, 7 Jul 2017 11:43:25 +0900 Subject: [PATCH] Capability of installing from the local repository --- .../gitbucket-gist-plugin_2.12-4.9.0.jar | Bin 207555 -> 207564 bytes src/main/resources/plugins/plugins.json | 13 ++- .../controller/SystemSettingsController.scala | 66 +++++++++--- .../core/plugin/PluginRegistory.scala | 94 ++++++------------ .../core/plugin/PluginRepository.scala | 43 ++++++++ .../core/servlet/InitializeListener.scala | 43 ++++---- .../gitbucket/core/admin/plugins.scala.html | 6 +- 7 files changed, 165 insertions(+), 100 deletions(-) create mode 100644 src/main/scala/gitbucket/core/plugin/PluginRepository.scala 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 index 88f789da0e1d01af1c95597156f770eae4f634a5..b803b4e9b7159b1972cf402ed9aed03940c7ecb3 100644 GIT binary patch delta 10972 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 bE(DAqV?hsb9)fP7h6hZFZBB;%-~j#)Iy-nW diff --git a/src/main/resources/plugins/plugins.json b/src/main/resources/plugins/plugins.json index 3cde81508..93a4f4011 100644 --- a/src/main/resources/plugins/plugins.json +++ b/src/main/resources/plugins/plugins.json @@ -1,6 +1,17 @@ [ { - "filename": "gitbucket-gist-plugin_2.12-4.9.0.jar", + "id": "gist", + "name": "Gist Plugin", + "description": "Provides Gist feature on GitBucket.", + "provider": "GitBucket Organization", + "homepage": "https://github.com/gitbucket/gitbucket-gist-plugin", + "versions": [ + { + "version": "4.9.0", + "range": ">4.14.0", + "file": "gitbucket-gist-plugin_2.12-4.9.0.jar" + } + ], "default": true } ] diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index babda929c..784172180 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -6,7 +6,7 @@ import gitbucket.core.admin.html import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService} import gitbucket.core.util.{AdminAuthenticator, Mailer} import gitbucket.core.ssh.SshServer -import gitbucket.core.plugin.PluginRegistry +import gitbucket.core.plugin.{PluginInfoBase, PluginRegistry, PluginRepository} import SystemSettingsService._ import gitbucket.core.util.Implicits._ import gitbucket.core.util.SyntaxSugars._ @@ -15,6 +15,10 @@ import gitbucket.core.util.StringUtil._ import io.github.gitbucket.scalatra.forms._ import org.apache.commons.io.{FileUtils, IOUtils} import org.scalatra.i18n.Messages +import com.github.zafarkhaja.semver.{Version => Semver} +import gitbucket.core.GitBucketCoreModule +import scala.collection.JavaConverters._ + class SystemSettingsController extends SystemSettingsControllerBase with AccountService with RepositoryService with AdminAuthenticator @@ -181,7 +185,32 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { }) get("/admin/plugins")(adminOnly { - html.plugins(PluginRegistry().getPlugins(), flash.get("info")) + // Installed plugins + val enabledPlugins = PluginRegistry().getPlugins() + + val gitbucketVersion = Semver.valueOf(GitBucketCoreModule.getVersions.asScala.last.getVersion) + + // Plugins in the local repository + val repositoryPlugins = PluginRepository.getPlugins() + .filterNot { meta => + enabledPlugins.exists { plugin => plugin.pluginId == meta.id && + Semver.valueOf(plugin.pluginVersion).greaterThanOrEqualTo(Semver.valueOf(meta.latestVersion.version)) + } + }.map { meta => + (meta, meta.versions.reverse.find { version => gitbucketVersion.satisfies(version.range) }) + }.collect { case (meta, Some(version)) => + new PluginInfoBase( + pluginId = meta.id, + pluginName = meta.name, + pluginVersion = version.version, + description = meta.description + ) + } + + // Merge + val plugins = enabledPlugins.map((_, true)) ++ repositoryPlugins.map((_, false)) + + html.plugins(plugins, flash.get("info")) }) post("/admin/plugins/_reload")(adminOnly { @@ -190,24 +219,35 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { redirect("/admin/plugins") }) - post("/admin/plugins/:pluginId/_uninstall")(adminOnly { + post("/admin/plugins/:pluginId/:version/_uninstall")(adminOnly { val pluginId = params("pluginId") + val version = params("version") PluginRegistry().getPlugins() - .collect { case (plugin, true) if plugin.pluginId == pluginId => plugin } + .collect { case plugin if (plugin.pluginId == pluginId && plugin.pluginVersion == version) => plugin } .foreach { _ => - PluginRegistry.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn) - flash += "info" -> s"${pluginId} was uninstalled." - } + PluginRegistry.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn) + flash += "info" -> s"${pluginId} was uninstalled." + } redirect("/admin/plugins") }) - post("/admin/plugins/:pluginId/_install")(adminOnly { + post("/admin/plugins/:pluginId/:version/_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." + val version = params("version") + /// TODO!!!! + PluginRepository.getPlugins() + .collect { case meta if meta.id == pluginId => (meta, meta.versions.find(_.version == version) )} + .foreach { case (meta, version) => + version.foreach { version => + // TODO Install version! + PluginRegistry.install( + new java.io.File(PluginHome, s".repository/${version.file}"), + request.getServletContext, + loadSystemSettings(), + request2Session(request).conn + ) + flash += "info" -> s"${pluginId} was installed." + } } redirect("/admin/plugins") }) diff --git a/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala b/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala index 1750af206..715bd919d 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala @@ -24,11 +24,11 @@ import play.twirl.api.Html import scala.collection.mutable import scala.collection.mutable.ListBuffer -import com.github.zafarkhaja.semver.Version +import com.github.zafarkhaja.semver.{Version => Semver} class PluginRegistry { - private val plugins = new ListBuffer[(PluginInfo, Boolean)] + private val plugins = new ListBuffer[PluginInfo] private val javaScripts = new ListBuffer[(String, String)] private val controllers = new ListBuffer[(ControllerBase, String)] private val images = mutable.Map[String, String]() @@ -62,9 +62,9 @@ class PluginRegistry { private val suggestionProviders = new ListBuffer[SuggestionProvider] suggestionProviders += new UserNameSuggestionProvider() - def addPlugin(pluginInfo: PluginInfo, enabled: Boolean): Unit = plugins += ((pluginInfo, enabled)) + def addPlugin(pluginInfo: PluginInfo): Unit = plugins += pluginInfo - def getPlugins(): List[(PluginInfo, Boolean)] = plugins.toList + def getPlugins(): List[PluginInfo] = plugins.toList def addImage(id: String, bytes: Array[Byte]): Unit = { val encoded = Base64.getEncoder.encodeToString(bytes) @@ -207,7 +207,7 @@ object PluginRegistry { */ def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized { instance.getPlugins() - .collect { case (plugin, true) if plugin.pluginId == pluginId => plugin } + .collect { case plugin if plugin.pluginId == pluginId => plugin } .foreach { plugin => // try { // plugin.pluginClass.uninstall(instance, context, settings) @@ -223,18 +223,14 @@ object PluginRegistry { } /** - * Install a specified plugin from local repository. + * Install a plugin from a specified jar file. */ - 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)) + def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized { + FileUtils.copyFile(file, new File(PluginHome, file.getName)) - shutdown(context, settings) - instance = new PluginRegistry() - initialize(context, settings, conn) - } + shutdown(context, settings) + instance = new PluginRegistry() + initialize(context, settings, conn) } private class PluginJarFileFilter extends FilenameFilter { @@ -244,7 +240,7 @@ object PluginRegistry { 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) + (name, Semver.valueOf(version.replaceFirst("\\.jar$", "")), file) }.groupBy { case (name, _, _) => name }.map { case (name, versions) => @@ -303,44 +299,10 @@ object PluginRegistry { pluginClass = plugin, pluginJar = pluginJar, classLoader = classLoader - ), true) + )) } catch { - case e: Throwable => { - logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e) - } - } - } - - // 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) - } - } + case e: Throwable => logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e) } } } @@ -352,9 +314,7 @@ object PluginRegistry { } def shutdown(context: ServletContext, settings: SystemSettings): Unit = synchronized { - instance.getPlugins() - .collect { case (plugin, true) => plugin } - .foreach { plugin => + instance.getPlugins().foreach { plugin => try { plugin.pluginClass.shutdown(instance, context, settings) } catch { @@ -369,17 +329,29 @@ object PluginRegistry { } -case class Link(id: String, label: String, path: String, icon: Option[String] = None) +case class Link( + id: String, + label: String, + path: String, + icon: Option[String] = None +) + +class PluginInfoBase( + val pluginId: String, + val pluginName: String, + val pluginVersion: String, + val description: String +) case class PluginInfo( - pluginId: String, - pluginName: String, - pluginVersion: String, - description: String, + override val pluginId: String, + override val pluginName: String, + override val pluginVersion: String, + override val description: String, pluginClass: Plugin, pluginJar: File, classLoader: URLClassLoader -) +) extends PluginInfoBase(pluginId, pluginName, pluginVersion, description) class PluginWatchThread(context: ServletContext) extends Thread with SystemSettingsService { import gitbucket.core.model.Profile.profile.blockingApi._ diff --git a/src/main/scala/gitbucket/core/plugin/PluginRepository.scala b/src/main/scala/gitbucket/core/plugin/PluginRepository.scala new file mode 100644 index 000000000..d1b98ff75 --- /dev/null +++ b/src/main/scala/gitbucket/core/plugin/PluginRepository.scala @@ -0,0 +1,43 @@ +package gitbucket.core.plugin + +import org.json4s._ +import gitbucket.core.util.Directory._ +import org.apache.commons.io.FileUtils + +object PluginRepository { + implicit val formats = DefaultFormats + + def parsePluginJson(json: String): Seq[PluginMetadata] = { + org.json4s.jackson.JsonMethods.parse(json).extract[Seq[PluginMetadata]] + } + + lazy val LocalRepositoryDir = new java.io.File(PluginHome, ".repository") + lazy val LocalRepositoryIndexFile = new java.io.File(LocalRepositoryDir, "plugins.json") + + def getPlugins(): Seq[PluginMetadata] = { + if(LocalRepositoryIndexFile.exists){ + parsePluginJson(FileUtils.readFileToString(LocalRepositoryIndexFile, "UTF-8")) + } else Nil + } + +} + +// Mapped from plugins.json +case class PluginMetadata( + id: String, + name: String, + description: String, + provider: String, + homepage: String, + versions: Seq[VersionDef], + default: Boolean = false +){ + lazy val latestVersion: VersionDef = versions.last +} + +case class VersionDef( + version: String, + file: String, + range: String +) + diff --git a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala index 2597eae0e..9e46f65e3 100644 --- a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala +++ b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala @@ -5,7 +5,7 @@ import java.io.{File, FileOutputStream} import akka.event.Logging import com.typesafe.config.ConfigFactory import gitbucket.core.GitBucketCoreModule -import gitbucket.core.plugin.PluginRegistry +import gitbucket.core.plugin.{PluginRegistry, PluginRepository} import gitbucket.core.service.{ActivityService, SystemSettingsService} import gitbucket.core.util.DatabaseConfig import gitbucket.core.util.Directory._ @@ -18,10 +18,9 @@ import javax.servlet.{ServletContextEvent, ServletContextListener} import org.apache.commons.io.{FileUtils, IOUtils} import org.slf4j.LoggerFactory -import org.json4s._ -import org.json4s.jackson.JsonMethods._ import akka.actor.{Actor, ActorSystem, Props} import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension +import com.github.zafarkhaja.semver.{Version => Semver} import scala.collection.JavaConverters._ @@ -80,7 +79,7 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi } // Install bundled plugins - installBundledPlugins() + extractBundledPlugins(gitbucketVersion) // Load plugins logger.info("Initialize plugins") @@ -130,38 +129,38 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi } } - private def installBundledPlugins(): Unit = { - logger.info("Install bundled plugins") + private def extractBundledPlugins(gitbucketVersion: String): Unit = { + logger.info("Extract bundled plugins") val cl = Thread.currentThread.getContextClassLoader try { using(cl.getResourceAsStream("plugins/plugins.json")){ pluginsFile => - val pluginRepositoryDir = new File(PluginHome, ".repository") - if(!pluginRepositoryDir.exists){ - pluginRepositoryDir.mkdirs() - } + val pluginsJson = IOUtils.toString(pluginsFile, "UTF-8") - implicit val formats = DefaultFormats - val plugins = parse(IOUtils.toString(pluginsFile, "UTF-8")).extract[Seq[Plugin]] + FileUtils.forceMkdir(PluginRepository.LocalRepositoryDir) + FileUtils.write(PluginRepository.LocalRepositoryIndexFile, pluginsJson, "UTF-8") + + val plugins = PluginRepository.parsePluginJson(pluginsJson) plugins.foreach { plugin => - val file = new File(pluginRepositoryDir, plugin.filename) - if(!file.exists){ - logger.info(s"Copy ${plugin} to ${file.getAbsolutePath}") - using(cl.getResourceAsStream("plugins/" + plugin), new FileOutputStream(file)){ case (in, out) => IOUtils.copy(in, out) } + plugin.versions.sortBy { x => Semver.valueOf(x.version) }.reverse.zipWithIndex.foreach { case (version, i) => + val file = new File(PluginRepository.LocalRepositoryDir, version.file) + if(!file.exists) { + logger.info(s"Copy ${plugin} to ${file.getAbsolutePath}") + FileUtils.forceMkdirParent(file) + using(cl.getResourceAsStream("plugins/" + version.file), new FileOutputStream(file)){ case (in, out) => IOUtils.copy(in, out) } - if(plugin.default){ - logger.info(s"Enable ${file.getName} in default") - FileUtils.copyFile(file, new File(PluginHome, plugin.filename)) + if(plugin.default && i == 0){ + logger.info(s"Enable ${file.getName} in default") + FileUtils.copyFile(file, new File(PluginHome, version.file)) + } } } } } } catch { - case e: Exception => logger.error("Error in installing bundled plugin", e) + case e: Exception => logger.error("Error in extracting bundled plugin", e) } } - case class Plugin(filename: String, default: Boolean = false) - override def contextDestroyed(event: ServletContextEvent): Unit = { // Shutdown Quartz scheduler system.terminate() diff --git a/src/main/twirl/gitbucket/core/admin/plugins.scala.html b/src/main/twirl/gitbucket/core/admin/plugins.scala.html index 4d65531bc..9f37b45ed 100644 --- a/src/main/twirl/gitbucket/core/admin/plugins.scala.html +++ b/src/main/twirl/gitbucket/core/admin/plugins.scala.html @@ -1,4 +1,4 @@ -@(plugins: List[(gitbucket.core.plugin.PluginInfo, Boolean)], info: Option[Any])(implicit context: gitbucket.core.controller.Context) +@(plugins: List[(gitbucket.core.plugin.PluginInfoBase, Boolean)], info: Option[Any])(implicit context: gitbucket.core.controller.Context) @gitbucket.core.html.main("Plugins"){ @gitbucket.core.admin.html.menu("plugins") { @gitbucket.core.helper.html.information(info) @@ -17,11 +17,11 @@
@if(enabled){ -
+
} else { -
+
}