mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-07 22:15:51 +01:00
Add plugin updating capability
This commit is contained in:
@@ -10,7 +10,7 @@ import ssh.SshServer
|
|||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import plugin.PluginSystem
|
import plugin.{Plugin, PluginSystem}
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
with AccountService with AdminAuthenticator
|
with AccountService with AdminAuthenticator
|
||||||
@@ -83,36 +83,32 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/plugins")(adminOnly {
|
get("/admin/plugins")(adminOnly {
|
||||||
admin.plugins.html.installed(plugin.PluginSystem.plugins)
|
val installedPlugins = plugin.PluginSystem.plugins
|
||||||
|
val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable")
|
||||||
|
admin.plugins.html.installed(installedPlugins, updatablePlugins)
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/plugins/_update", pluginForm)(adminOnly { form =>
|
||||||
|
deletePlugins(form.pluginIds)
|
||||||
|
installPlugins(form.pluginIds)
|
||||||
|
redirect("/admin/plugins")
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/plugins/_delete", pluginForm)(adminOnly { form =>
|
post("/admin/plugins/_delete", pluginForm)(adminOnly { form =>
|
||||||
form.pluginIds.foreach { pluginId =>
|
deletePlugins(form.pluginIds)
|
||||||
plugin.PluginSystem.uninstall(pluginId)
|
|
||||||
val dir = new java.io.File(PluginHome, pluginId)
|
|
||||||
if(dir.exists && dir.isDirectory){
|
|
||||||
FileUtils.deleteQuietly(dir)
|
|
||||||
PluginSystem.uninstall(pluginId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
redirect("/admin/plugins")
|
redirect("/admin/plugins")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/plugins/available")(adminOnly {
|
get("/admin/plugins/available")(adminOnly {
|
||||||
// TODO Do periodical and asynchronous...?
|
// TODO Do periodical and asynchronous...?
|
||||||
PluginSystem.updateAllRepositories()
|
PluginSystem.updateAllRepositories()
|
||||||
admin.plugins.html.available(getAvailablePlugins())
|
val installedPlugins = plugin.PluginSystem.plugins
|
||||||
|
val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available")
|
||||||
|
admin.plugins.html.available(availablePlugins)
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/plugins/_install", pluginForm)(adminOnly { form =>
|
post("/admin/plugins/_install", pluginForm)(adminOnly { form =>
|
||||||
val dir = getPluginCacheDir()
|
installPlugins(form.pluginIds)
|
||||||
getAvailablePlugins().filter(x => form.pluginIds.contains(x.id)).foreach { plugin =>
|
|
||||||
val pluginDir = new java.io.File(PluginHome, plugin.id)
|
|
||||||
if(!pluginDir.exists){
|
|
||||||
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
|
|
||||||
}
|
|
||||||
PluginSystem.installPlugin(plugin.id)
|
|
||||||
}
|
|
||||||
redirect("/admin/plugins")
|
redirect("/admin/plugins")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -126,9 +122,31 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO Move to PluginSystem or Service?
|
// TODO Move these methods to PluginSystem or Service?
|
||||||
private def getAvailablePlugins(): List[SystemSettingsControllerBase.AvailablePlugin] = {
|
private def deletePlugins(pluginIds: List[String]): Unit = {
|
||||||
|
pluginIds.foreach { pluginId =>
|
||||||
|
plugin.PluginSystem.uninstall(pluginId)
|
||||||
|
val dir = new java.io.File(PluginHome, pluginId)
|
||||||
|
if(dir.exists && dir.isDirectory){
|
||||||
|
FileUtils.deleteQuietly(dir)
|
||||||
|
PluginSystem.uninstall(pluginId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def installPlugins(pluginIds: List[String]): Unit = {
|
||||||
|
val dir = getPluginCacheDir()
|
||||||
val installedPlugins = plugin.PluginSystem.plugins
|
val installedPlugins = plugin.PluginSystem.plugins
|
||||||
|
getAvailablePlugins(installedPlugins).filter(x => pluginIds.contains(x.id)).foreach { plugin =>
|
||||||
|
val pluginDir = new java.io.File(PluginHome, plugin.id)
|
||||||
|
if(!pluginDir.exists){
|
||||||
|
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
|
||||||
|
}
|
||||||
|
PluginSystem.installPlugin(plugin.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def getAvailablePlugins(installedPlugins: List[Plugin]): List[SystemSettingsControllerBase.AvailablePlugin] = {
|
||||||
val repositoryRoot = getPluginCacheDir()
|
val repositoryRoot = getPluginCacheDir()
|
||||||
|
|
||||||
if(repositoryRoot.exists && repositoryRoot.isDirectory){
|
if(repositoryRoot.exists && repositoryRoot.isDirectory){
|
||||||
@@ -144,16 +162,20 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SystemSettingsControllerBase.AvailablePlugin(
|
SystemSettingsControllerBase.AvailablePlugin(
|
||||||
repo.id,
|
repository = repo.id,
|
||||||
properties.getProperty("id"),
|
id = properties.getProperty("id"),
|
||||||
properties.getProperty("version"),
|
version = properties.getProperty("version"),
|
||||||
properties.getProperty("author"),
|
author = properties.getProperty("author"),
|
||||||
properties.getProperty("url"),
|
url = properties.getProperty("url"),
|
||||||
properties.getProperty("description"),
|
description = properties.getProperty("description"),
|
||||||
if(installedPlugins.exists(_.id == properties.getProperty("id"))) "installed" else "available")
|
status = installedPlugins.find(_.id == properties.getProperty("id")) match {
|
||||||
|
case Some(x) if(PluginSystem.isUpdatable(x.version, properties.getProperty("version")))=> "updatable"
|
||||||
|
case Some(x) => "installed"
|
||||||
|
case None => "available"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else Nil
|
} else Nil
|
||||||
}.filter(x => !installedPlugins.exists(_.id == x.id))
|
}
|
||||||
} else Nil
|
} else Nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,28 @@ object PluginSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the plugin is updatable.
|
||||||
|
*/
|
||||||
|
def isUpdatable(oldVersion: String, newVersion: String): Boolean = {
|
||||||
|
if(oldVersion == newVersion){
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
val dim1 = oldVersion.split("\\.").map(_.toInt)
|
||||||
|
val dim2 = newVersion.split("\\.").map(_.toInt)
|
||||||
|
dim1.zip(dim2).foreach { case (a, b) =>
|
||||||
|
if(a < b){
|
||||||
|
return true
|
||||||
|
} else if(a > b){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// TODO This is a test
|
// TODO This is a test
|
||||||
// addGlobalMenu("Google", "http://www.google.co.jp/", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEvwAABL8BkeKJvAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIgSURBVEiJtdZNiI1hFAfw36ORhSFFPgYLszOKJAsWRLGzks1gYyFZKFs7C7K2Y2XDRiwmq9kIJWQjJR9Tk48xRtTIRwjH4p473nm99yLNqdNTz/mf//+555x7ektEmEmbNaPs6OkUKKX0YBmWp6/IE8bwIs8xjEfEt0aiiJBl6sEuXMRLfEf8pX/PnIvJ0TPFWxE4+w+Ef/Kzbd5qDx5l8H8tkku7LG17gH7sxWatevdhEUoXsjda5RnDTZzH6jagtMe0lHIa23AJw3iOiSRZlmJ9mfcyfTzFl2AldmI3rkbEkbrAYKrX7S1eVRyWVnxhQ87eiLjQ+o2/mtyve+PuYy3W4+EfsP2/TVGKTHRI+Iz9Fdx8XOmAnZjGWRMYqoF/4ESW4hpOYk1iZ2WsLjDUTeBYBfgeuyux2XiNT5hXud+DD5W8Y90EtifoSfultfjx7MVtrKzcr8No5m7vJtCLx1hQJ8/4IZzClpyoy5ibsYUYQW81Z9o2jYgPeKr15+poEXE9+1XF9WIkOaasaV2P4k4pZUdDbEm+VEQcjIgtEfGxlLIVd/Gs6TX1MhzQquU3HK1t23f4IsuS94fxNXMO/MbXIDBg+tidw5yMbcCmylSdqWEH/kagYLKWeAt9Fcxi3KhhJuXq6SqQBMO15NDalvswmLWux4cbuToIbMS9BpJOfg8bm7imtmmTlVJWaa3hpnU9nufziBjtyDHTny0/AaA7Qnb4AM4aAAAAAElFTkSuQmCC")
|
// addGlobalMenu("Google", "http://www.google.co.jp/", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEvwAABL8BkeKJvAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIgSURBVEiJtdZNiI1hFAfw36ORhSFFPgYLszOKJAsWRLGzks1gYyFZKFs7C7K2Y2XDRiwmq9kIJWQjJR9Tk48xRtTIRwjH4p473nm99yLNqdNTz/mf//+555x7ektEmEmbNaPs6OkUKKX0YBmWp6/IE8bwIs8xjEfEt0aiiJBl6sEuXMRLfEf8pX/PnIvJ0TPFWxE4+w+Ef/Kzbd5qDx5l8H8tkku7LG17gH7sxWatevdhEUoXsjda5RnDTZzH6jagtMe0lHIa23AJw3iOiSRZlmJ9mfcyfTzFl2AldmI3rkbEkbrAYKrX7S1eVRyWVnxhQ87eiLjQ+o2/mtyve+PuYy3W4+EfsP2/TVGKTHRI+Iz9Fdx8XOmAnZjGWRMYqoF/4ESW4hpOYk1iZ2WsLjDUTeBYBfgeuyux2XiNT5hXud+DD5W8Y90EtifoSfultfjx7MVtrKzcr8No5m7vJtCLx1hQJ8/4IZzClpyoy5ibsYUYQW81Z9o2jYgPeKr15+poEXE9+1XF9WIkOaasaV2P4k4pZUdDbEm+VEQcjIgtEfGxlLIVd/Gs6TX1MhzQquU3HK1t23f4IsuS94fxNXMO/MbXIDBg+tidw5yMbcCmylSdqWEH/kagYLKWeAt9Fcxi3KhhJuXq6SqQBMO15NDalvswmLWux4cbuToIbMS9BpJOfg8bm7imtmmTlVJWaa3hpnU9nufziBjtyDHTny0/AaA7Qnb4AM4aAAAAAElFTkSuQmCC")
|
||||||
// { context => context.loginAccount.isDefined }
|
// { context => context.loginAccount.isDefined }
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</table>
|
</table>
|
||||||
<input type="submit" id="install-plugins" class="btn btn-primary" value="Install selected plugins"/>
|
<input type="submit" id="install-plugins" class="btn btn-success" value="Install selected plugins"/>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
@(plugins: List[plugin.Plugin])(implicit context: app.Context)
|
@(plugins: List[plugin.Plugin],
|
||||||
|
updatablePlugins: List[app.SystemSettingsControllerBase.AvailablePlugin])(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main("Plugins"){
|
@html.main("Plugins"){
|
||||||
@admin.html.menu("plugins"){
|
@admin.html.menu("plugins"){
|
||||||
@tab("installed")
|
@tab("installed")
|
||||||
<form action="@path/admin/plugins/_delete" method="POST" validate="true">
|
<form method="POST" validate="true">
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
@@ -18,18 +19,27 @@
|
|||||||
<input type="checkbox" name="pluginId[@i]" value="@plugin.id"/>
|
<input type="checkbox" name="pluginId[@i]" value="@plugin.id"/>
|
||||||
@plugin.id
|
@plugin.id
|
||||||
</td>
|
</td>
|
||||||
<td>@plugin.version</td>
|
<td>
|
||||||
|
@plugin.version
|
||||||
|
@updatablePlugins.find(_.id == plugin.id).map { x =>
|
||||||
|
(@x.version is available)
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td><a href="@plugin.url">@plugin.author</a></td>
|
<td><a href="@plugin.url">@plugin.author</a></td>
|
||||||
<td>@plugin.description</td>
|
<td>@plugin.description</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</table>
|
</table>
|
||||||
<input type="submit" id="delete-plugins" class="btn btn-danger" value="Uninstall selected plugins"/>
|
<input type="submit" id="update-plugins" class="btn btn-success" value="Update selected plugins" formaction="@path/admin/plugins/_update"/>
|
||||||
|
<input type="submit" id="delete-plugins" class="btn btn-danger" value="Uninstall selected plugins" formaction="@path/admin/plugins/_delete"/>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
|
$('#update-plugins').click(function(){
|
||||||
|
return confirm('Selected plugin will be updated. Are you sure?');
|
||||||
|
});
|
||||||
$('#delete-plugins').click(function(){
|
$('#delete-plugins').click(function(){
|
||||||
return confirm('Selected plugin will be removed permanently. Are you sure?');
|
return confirm('Selected plugin will be removed permanently. Are you sure?');
|
||||||
});
|
});
|
||||||
|
|||||||
22
src/test/scala/plugin/PluginSystemSpec.scala
Normal file
22
src/test/scala/plugin/PluginSystemSpec.scala
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import org.specs2.mutable._
|
||||||
|
|
||||||
|
class PluginSystemSpec extends Specification {
|
||||||
|
|
||||||
|
"isUpdatable" should {
|
||||||
|
"return true for updattable plugin" in {
|
||||||
|
PluginSystem.isUpdatable("1.0.0", "1.0.1") must beTrue
|
||||||
|
PluginSystem.isUpdatable("1.0.0", "1.1.0") must beTrue
|
||||||
|
PluginSystem.isUpdatable("1.1.1", "1.2.0") must beTrue
|
||||||
|
PluginSystem.isUpdatable("1.2.1", "2.0.0") must beTrue
|
||||||
|
}
|
||||||
|
"return false for not updattable plugin" in {
|
||||||
|
PluginSystem.isUpdatable("1.0.0", "1.0.0") must beFalse
|
||||||
|
PluginSystem.isUpdatable("1.0.1", "1.0.0") must beFalse
|
||||||
|
PluginSystem.isUpdatable("1.1.1", "1.1.0") must beFalse
|
||||||
|
PluginSystem.isUpdatable("2.0.0", "1.2.1") must beFalse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user