diff --git a/build.sbt b/build.sbt index 3f1a1f3..fb2cb6d 100644 --- a/build.sbt +++ b/build.sbt @@ -170,12 +170,17 @@ // include plugins val pluginsDir = temp / "WEB-INF" / "classes" / "plugins" IO createDirectory (pluginsDir) - IO copyFile (Keys.baseDirectory.value / "plugins.json", pluginsDir / "plugins.json") - val json = IO read (Keys.baseDirectory.value / "plugins.json") - PluginsJson.getUrls(json).foreach { url => - log info s"Download: ${url}" - IO transfer (new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1)) + val plugins = IO readLines (Keys.baseDirectory.value / "src" / "main" / "resources" / "bundle-plugins.txt") + plugins.foreach { plugin => + plugin.trim.split(":") match { + case Array(pluginId, pluginVersion) => + val url = "https://plugins.gitbucket-community.org/releases/" + + s"gitbucket-${pluginId}-plugin/gitbucket-${pluginId}-plugin-gitbucket_${version.value}-${pluginVersion}.jar" + log info s"Download: ${url}" + IO transfer (new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1)) + case _ => () + } } // zip it up diff --git a/plugins.json b/plugins.json deleted file mode 100644 index 3682660..0000000 --- a/plugins.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "id": "notifications", - "name": "Notifications Plugin", - "description": "Provides notifications feature on GitBucket.", - "versions": [ - { - "version": "1.5.0", - "range": ">=4.23.0", - "url": "https://github.com/gitbucket/gitbucket-notifications-plugin/releases/download/1.5.0/gitbucket-notifications-plugin-assembly-1.5.0.jar" - } - ], - "default": true - }, - { - "id": "emoji", - "name": "Emoji Plugin", - "description": "Provides Emoji support for GitBucket.", - "versions": [ - { - "version": "4.5.0", - "range": ">=4.18.0", - "url": "https://github.com/gitbucket/gitbucket-emoji-plugin/releases/download/4.5.0/gitbucket-emoji-plugin_2.12-4.5.0.jar" - } - ], - "default": false - }, - { - "id": "gist", - "name": "Gist Plugin", - "description": "Provides Gist feature on GitBucket.", - "versions": [ - { - "version": "4.15.0", - "range": ">=4.25.0", - "url": "https://github.com/gitbucket/gitbucket-gist-plugin/releases/download/4.15.0/gitbucket-gist-plugin-gitbucket_4.25.0-4.15.0.jar" - } - ], - "default": false - }, - { - "id": "pages", - "name": "Pages Plugin", - "description": "Project pages for gitbucket", - "versions": [ - { - "version": "1.7.0", - "range": ">=4.23.0", - "url": "https://github.com/gitbucket/gitbucket-pages-plugin/releases/download/v1.7.0/gitbucket-pages-plugin_2.12-1.7.0.jar" - } - ], - "default": false - } -] diff --git a/src/main/resources/bundle-plugins.txt b/src/main/resources/bundle-plugins.txt new file mode 100644 index 0000000..e2ab698 --- /dev/null +++ b/src/main/resources/bundle-plugins.txt @@ -0,0 +1 @@ +notifications:1.5.1 diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index 69f0223..b7d369e 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -45,9 +45,11 @@ "gravatar" -> trim(label("Gravatar", boolean())), "notification" -> trim(label("Notification", boolean())), "activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))), - "ssh" -> trim(label("SSH access", boolean())), - "sshHost" -> trim(label("SSH host", optional(text()))), - "sshPort" -> trim(label("SSH port", optional(number()))), + "ssh" -> mapping( + "enabled" -> trim(label("SSH access", boolean())), + "host" -> trim(label("SSH host", optional(text()))), + "port" -> trim(label("SSH port", optional(number()))), + )(Ssh.apply), "useSMTP" -> trim(label("SMTP", boolean())), "smtp" -> optionalIfNotChecked( "useSMTP", @@ -91,13 +93,16 @@ )(OIDC.apply) ), "skinName" -> trim(label("AdminLTE skin name", text(required))), - "showMailAddress" -> trim(label("Show mail address", boolean())) + "showMailAddress" -> trim(label("Show mail address", boolean())), + "pluginNetworkInstall" -> new SingleValueType[Boolean] { + override def convert(value: String, messages: Messages): Boolean = context.settings.pluginNetworkInstall + } )(SystemSettings.apply).verifying { settings => Vector( - if (settings.ssh && settings.baseUrl.isEmpty) { + if (settings.ssh.enabled && settings.baseUrl.isEmpty) { Some("baseUrl" -> "Base URL is required if SSH access is enabled.") } else None, - if (settings.ssh && settings.sshHost.isEmpty) { + if (settings.ssh.enabled && settings.ssh.sshHost.isEmpty) { Some("sshHost" -> "SSH host is required if SSH access is enabled.") } else None ).flatten @@ -322,79 +327,90 @@ get("/admin/plugins")(adminOnly { // Installed plugins val enabledPlugins = PluginRegistry().getPlugins() + val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion - 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)) + // Plugins in the remote repository + val repositoryPlugins = if (context.settings.pluginNetworkInstall) { + PluginRepository + .getPlugins() + .map { meta => + (meta, meta.versions.reverse.find { version => + gitbucketVersion == version.gitbucketVersion && !enabledPlugins.exists { plugin => + plugin.pluginId == meta.id && plugin.pluginVersion == version.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 - ) - } + .collect { + case (meta, Some(version)) => + new PluginInfoBase( + pluginId = meta.id, + pluginName = meta.name, + pluginVersion = version.version, + gitbucketVersion = Some(version.gitbucketVersion), + description = meta.description + ) + } + } else Nil // Merge - val plugins = enabledPlugins.map((_, true)) ++ repositoryPlugins.map((_, false)) + val plugins = (enabledPlugins.map((_, true)) ++ repositoryPlugins.map((_, false))) + .groupBy(_._1.pluginId) + .map { + case (pluginId, plugins) => + val (plugin, enabled) = plugins.head + (plugin, enabled, if (plugins.length > 1) plugins.last._1.pluginVersion else "") + } + .toList html.plugins(plugins, flash.get("info")) }) post("/admin/plugins/_reload")(adminOnly { + // Update configuration + val pluginNetworkInstall = params.get("pluginNetworkInstall").map(_.toBoolean).getOrElse(false) + saveSystemSettings(context.settings.copy(pluginNetworkInstall = pluginNetworkInstall)) + + // Reload plugins PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn) flash += "info" -> "All plugins were reloaded." redirect("/admin/plugins") }) - post("/admin/plugins/:pluginId/:version/_uninstall")(adminOnly { + post("/admin/plugins/:pluginId/_uninstall")(adminOnly { val pluginId = params("pluginId") - val version = params("version") - PluginRegistry() - .getPlugins() - .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." - } + + if (PluginRegistry().getPlugins().exists(_.pluginId == pluginId)) { + PluginRegistry + .uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn) + flash += "info" -> s"${pluginId} was uninstalled." + } + redirect("/admin/plugins") }) post("/admin/plugins/:pluginId/:version/_install")(adminOnly { - val pluginId = params("pluginId") - 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." - } - } + if (context.settings.pluginNetworkInstall) { + val pluginId = params("pluginId") + val version = params("version") + + PluginRepository + .getPlugins() + .collect { case meta if meta.id == pluginId => (meta, meta.versions.find(_.version == version)) } + .foreach { + case (meta, version) => + version.foreach { version => + PluginRegistry.install( + pluginId, + new java.net.URL(version.url), + request.getServletContext, + loadSystemSettings(), + request2Session(request).conn + ) + flash += "info" -> s"${pluginId}:${version.version} was installed." + } + } + } + redirect("/admin/plugins") }) diff --git a/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala b/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala index 287a5bc..62acace 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala @@ -4,11 +4,11 @@ import java.net.URLClassLoader import java.nio.file.{Files, Paths, StandardWatchEventKinds} import java.util.Base64 -import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentHashMap import javax.servlet.ServletContext +import com.github.zafarkhaja.semver.Version import gitbucket.core.controller.{Context, ControllerBase} import gitbucket.core.model.{Account, Issue} import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook @@ -202,7 +202,6 @@ private var watcher: PluginWatchThread = null private var extraWatcher: PluginWatchThread = null - private val initializing = new AtomicBoolean(false) /** * Returns the PluginRegistry singleton instance. @@ -223,30 +222,39 @@ */ def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized { - instance - .getPlugins() - .collect { case plugin if plugin.pluginId == pluginId => plugin } - .foreach { plugin => -// try { -// plugin.pluginClass.uninstall(instance, context, settings) -// } catch { -// 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) + + new File(PluginHome) + .listFiles((_: File, name: String) => { + name.startsWith(s"gitbucket-${pluginId}-plugin") && name.endsWith(".jar") + }) + .foreach(_.delete()) + + instance = new PluginRegistry() + initialize(context, settings, conn) } /** * Install a plugin from a specified jar file. */ - def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = + def install( + pluginId: String, + url: java.net.URL, + context: ServletContext, + settings: SystemSettings, + conn: java.sql.Connection + ): Unit = synchronized { shutdown(context, settings) - FileUtils.copyFile(file, new File(PluginHome, file.getName)) + + new File(PluginHome) + .listFiles((_: File, name: String) => { + name.startsWith(s"gitbucket-${pluginId}-plugin") && name.endsWith(".jar") + }) + .foreach(_.delete()) + + val in = url.openStream() + FileUtils.copyToFile(in, new File(PluginHome, new File(url.getFile).getName)) instance = new PluginRegistry() initialize(context, settings, conn) } @@ -257,12 +265,27 @@ override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") }) .toSeq - .sortBy(_.getName) + .sortBy(x => Version.valueOf(getPluginVersion(x.getName))) .reverse } lazy val extraPluginDir: Option[String] = Option(System.getProperty("gitbucket.pluginDir")) + def getGitBucketVersion(pluginJarFileName: String): Option[String] = { + val regex = ".+-gitbucket\\_(\\d+\\.\\d+\\.\\d+)-.+".r + pluginJarFileName match { + case regex(x) => Some(x) + case _ => None + } + } + + def getPluginVersion(pluginJarFileName: String): String = { + val regex = ".+-(\\d+\\.\\d+\\.\\d+)\\.jar$".r + pluginJarFileName match { + case regex(x) => x + } + } + /** * Initializes all installed plugins. */ @@ -278,6 +301,7 @@ installedDir.mkdirs() val pluginJars = listPluginJars(pluginDir) + val extraJars = extraPluginDir .map { extraDir => listPluginJars(new File(extraDir)) @@ -288,9 +312,9 @@ val installedJar = new File(installedDir, pluginJar.getName) FileUtils.copyFile(pluginJar, installedJar) - logger.info(s"Initialize ${pluginJar.getName}") - val classLoader = new URLClassLoader(Array(installedJar.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 @@ -304,7 +328,12 @@ // Migration val solidbase = new Solidbase() solidbase - .migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*)) + .migrate( + conn, + classLoader, + DatabaseConfig.liquiDriver, + new Module(plugin.pluginId, plugin.versions: _*) + ) conn.commit() // Check database version @@ -323,6 +352,7 @@ pluginId = plugin.pluginId, pluginName = plugin.pluginName, pluginVersion = plugin.versions.last.getVersion, + gitbucketVersion = getGitBucketVersion(installedJar.getName), description = plugin.description, pluginClass = plugin, pluginJar = pluginJar, @@ -384,6 +414,7 @@ val pluginId: String, val pluginName: String, val pluginVersion: String, + val gitbucketVersion: Option[String], val description: String ) @@ -391,11 +422,12 @@ override val pluginId: String, override val pluginName: String, override val pluginVersion: String, + override val gitbucketVersion: Option[String], override val description: String, pluginClass: Plugin, pluginJar: File, classLoader: URLClassLoader -) extends PluginInfoBase(pluginId, pluginName, pluginVersion, description) +) extends PluginInfoBase(pluginId, pluginName, pluginVersion, gitbucketVersion, description) class PluginWatchThread(context: ServletContext, dir: String) 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 index e3d9d1c..22208ea 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRepository.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRepository.scala @@ -1,8 +1,7 @@ package gitbucket.core.plugin import org.json4s._ -import gitbucket.core.util.Directory._ -import org.apache.commons.io.FileUtils +import org.apache.commons.io.IOUtils object PluginRepository { implicit val formats = DefaultFormats @@ -11,13 +10,10 @@ 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 + val url = new java.net.URL("https://plugins.gitbucket-community.org/releases/plugins.json") + val str = IOUtils.toString(url, "UTF-8") + parsePluginJson(str) } } @@ -36,7 +32,5 @@ case class VersionDef( version: String, url: String, - range: String -) { - lazy val file = url.substring(url.lastIndexOf("/") + 1) -} + gitbucketVersion: String +) diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 70e2387..60f2e53 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -773,7 +773,7 @@ def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git" def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] = - if (context.settings.ssh) { + if (context.settings.ssh.enabled) { context.settings.sshAddress.map { x => s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" } diff --git a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala index 7ac861e..df9109a 100644 --- a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala +++ b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala @@ -23,9 +23,9 @@ props.setProperty(Gravatar, settings.gravatar.toString) props.setProperty(Notification, settings.notification.toString) settings.activityLogLimit.foreach(x => props.setProperty(ActivityLogLimit, x.toString)) - props.setProperty(Ssh, settings.ssh.toString) - settings.sshHost.foreach(x => props.setProperty(SshHost, x.trim)) - settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) + props.setProperty(SshEnabled, settings.ssh.enabled.toString) + settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim)) + settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) props.setProperty(UseSMTP, settings.useSMTP.toString) if (settings.useSMTP) { settings.smtp.foreach { smtp => @@ -69,6 +69,8 @@ } props.setProperty(SkinName, settings.skinName.toString) props.setProperty(ShowMailAddress, settings.showMailAddress.toString) + props.setProperty(PluginNetworkInstall, settings.pluginNetworkInstall.toString) + using(new java.io.FileOutputStream(GitBucketConf)) { out => props.store(out, null) } @@ -91,9 +93,11 @@ getValue(props, Gravatar, false), getValue(props, Notification, false), getOptionValue[Int](props, ActivityLogLimit, None), - getValue(props, Ssh, false), - getOptionValue[String](props, SshHost, None).map(_.trim), - getOptionValue(props, SshPort, Some(DefaultSshPort)), + Ssh( + getValue(props, SshEnabled, false), + getOptionValue[String](props, SshHost, None).map(_.trim), + getOptionValue(props, SshPort, Some(DefaultSshPort)) + ), getValue(props, UseSMTP, getValue(props, Notification, false)), // handle migration scenario from only notification to useSMTP if (getValue(props, UseSMTP, getValue(props, Notification, false))) { Some( @@ -146,7 +150,8 @@ None }, getValue(props, SkinName, "skin-blue"), - getValue(props, ShowMailAddress, false) + getValue(props, ShowMailAddress, false), + getValue(props, PluginNetworkInstall, false) ) } } @@ -167,9 +172,7 @@ gravatar: Boolean, notification: Boolean, activityLogLimit: Option[Int], - ssh: Boolean, - sshHost: Option[String], - sshPort: Option[Int], + ssh: Ssh, useSMTP: Boolean, smtp: Option[Smtp], ldapAuthentication: Boolean, @@ -177,7 +180,8 @@ oidcAuthentication: Boolean, oidc: Option[OIDC], skinName: String, - showMailAddress: Boolean + showMailAddress: Boolean, + pluginNetworkInstall: Boolean ) { def baseUrl(request: HttpServletRequest): String = @@ -196,12 +200,18 @@ .fold(base)(_ + base.dropWhile(_ != ':')) } - def sshAddress: Option[SshAddress] = sshHost.collect { - case host if ssh => - SshAddress(host, sshPort.getOrElse(DefaultSshPort), "git") + def sshAddress: Option[SshAddress] = ssh.sshHost.collect { + case host if ssh.enabled => + SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git") } } + case class Ssh( + enabled: Boolean, + sshHost: Option[String], + sshPort: Option[Int] + ) + case class Ldap( host: String, port: Option[Int], @@ -255,7 +265,7 @@ private val Gravatar = "gravatar" private val Notification = "notification" private val ActivityLogLimit = "activity_log_limit" - private val Ssh = "ssh" + private val SshEnabled = "ssh" private val SshHost = "ssh.host" private val SshPort = "ssh.port" private val UseSMTP = "useSMTP" @@ -287,6 +297,7 @@ private val OidcJwsAlgorithm = "oidc.jws_algorithm" private val SkinName = "skinName" private val ShowMailAddress = "showMailAddress" + private val PluginNetworkInstall = "plugin.networkInstall" private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = { getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse { diff --git a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala index 326608b..74c043c 100644 --- a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala +++ b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala @@ -5,7 +5,7 @@ import akka.event.Logging import com.typesafe.config.ConfigFactory import gitbucket.core.GitBucketCoreModule -import gitbucket.core.plugin.{PluginRegistry, PluginRepository} +import gitbucket.core.plugin.PluginRegistry import gitbucket.core.service.{ActivityService, SystemSettingsService} import gitbucket.core.util.DatabaseConfig import gitbucket.core.util.Directory._ @@ -20,7 +20,6 @@ import org.slf4j.LoggerFactory import akka.actor.{Actor, ActorSystem, Props} import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension -import com.github.zafarkhaja.semver.{Version => Semver} import scala.collection.JavaConverters._ @@ -136,40 +135,30 @@ } private def extractBundledPlugins(gitbucketVersion: String): Unit = { - logger.info("Extract bundled plugins") + logger.info("Extract bundled plugins...") val cl = Thread.currentThread.getContextClassLoader try { - using(cl.getResourceAsStream("plugins/plugins.json")) { pluginsFile => + using(cl.getResourceAsStream("bundle-plugins.txt")) { pluginsFile => if (pluginsFile != null) { - val pluginsJson = IOUtils.toString(pluginsFile, "UTF-8") + val plugins = IOUtils.readLines(pluginsFile, "UTF-8") + val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion - FileUtils.forceMkdir(PluginRepository.LocalRepositoryDir) - FileUtils.write(PluginRepository.LocalRepositoryIndexFile, pluginsJson, "UTF-8") + plugins.asScala.foreach { plugin => + plugin.trim.split(":") match { + case Array(pluginId, pluginVersion) => + val fileName = s"gitbucket-${pluginId}-plugin-gitbucket_${gitbucketVersion}-${pluginVersion}.jar" + val in = cl.getResourceAsStream("plugins/" + fileName) + if (in != null) { + val file = new File(PluginHome, fileName) + logger.info(s"Extract to ${file.getAbsolutePath}") - val plugins = PluginRepository.parsePluginJson(pluginsJson) - plugins.foreach { plugin => - 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 && i == 0) { - logger.info(s"Enable ${file.getName} in default") - FileUtils.copyFile(file, new File(PluginHome, version.file)) - } + FileUtils.forceMkdirParent(file) + using(in, new FileOutputStream(file)) { + case (in, out) => IOUtils.copy(in, out) } - } + } + case _ => () + } } } } diff --git a/src/main/twirl/gitbucket/core/account/menu.scala.html b/src/main/twirl/gitbucket/core/account/menu.scala.html index 16e0341..07fc46b 100644 --- a/src/main/twirl/gitbucket/core/account/menu.scala.html +++ b/src/main/twirl/gitbucket/core/account/menu.scala.html @@ -19,7 +19,7 @@ Profile - @if(context.settings.ssh){ + @if(context.settings.ssh.enabled){