mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-17 23:20:13 +01:00
🎨 Community marketplace adds plugin section https://github.com/siyuan-note/siyuan/issues/8043
This commit is contained in:
parent
0384261c95
commit
4a5f3ea104
5 changed files with 320 additions and 0 deletions
|
|
@ -41,6 +41,71 @@ func getBazaarPackageREAME(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBazaarPlugin(c *gin.Context) {
|
||||||
|
ret := gulu.Ret.NewResult()
|
||||||
|
defer c.JSON(http.StatusOK, ret)
|
||||||
|
|
||||||
|
ret.Data = map[string]interface{}{
|
||||||
|
"packages": model.BazaarPlugins(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInstalledPlugin(c *gin.Context) {
|
||||||
|
ret := gulu.Ret.NewResult()
|
||||||
|
defer c.JSON(http.StatusOK, ret)
|
||||||
|
|
||||||
|
ret.Data = map[string]interface{}{
|
||||||
|
"packages": model.InstalledPlugins(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func installBazaarPlugin(c *gin.Context) {
|
||||||
|
ret := gulu.Ret.NewResult()
|
||||||
|
defer c.JSON(http.StatusOK, ret)
|
||||||
|
|
||||||
|
arg, ok := util.JsonArg(c, ret)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repoURL := arg["repoURL"].(string)
|
||||||
|
repoHash := arg["repoHash"].(string)
|
||||||
|
packageName := arg["packageName"].(string)
|
||||||
|
err := model.InstallBazaarPlugin(repoURL, repoHash, packageName)
|
||||||
|
if nil != err {
|
||||||
|
ret.Code = 1
|
||||||
|
ret.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.PushMsg(model.Conf.Language(69), 3000)
|
||||||
|
ret.Data = map[string]interface{}{
|
||||||
|
"packages": model.BazaarPlugins(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstallBazaarPlugin(c *gin.Context) {
|
||||||
|
ret := gulu.Ret.NewResult()
|
||||||
|
defer c.JSON(http.StatusOK, ret)
|
||||||
|
|
||||||
|
arg, ok := util.JsonArg(c, ret)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
packageName := arg["packageName"].(string)
|
||||||
|
err := model.UninstallBazaarPlugin(packageName)
|
||||||
|
if nil != err {
|
||||||
|
ret.Code = -1
|
||||||
|
ret.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Data = map[string]interface{}{
|
||||||
|
"packages": model.BazaarPlugins(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getBazaarWidget(c *gin.Context) {
|
func getBazaarWidget(c *gin.Context) {
|
||||||
ret := gulu.Ret.NewResult()
|
ret := gulu.Ret.NewResult()
|
||||||
defer c.JSON(http.StatusOK, ret)
|
defer c.JSON(http.StatusOK, ret)
|
||||||
|
|
|
||||||
|
|
@ -275,6 +275,10 @@ func ServeAPI(ginServer *gin.Engine) {
|
||||||
ginServer.Handle("POST", "/api/graph/getGraph", model.CheckAuth, getGraph)
|
ginServer.Handle("POST", "/api/graph/getGraph", model.CheckAuth, getGraph)
|
||||||
ginServer.Handle("POST", "/api/graph/getLocalGraph", model.CheckAuth, getLocalGraph)
|
ginServer.Handle("POST", "/api/graph/getLocalGraph", model.CheckAuth, getLocalGraph)
|
||||||
|
|
||||||
|
ginServer.Handle("POST", "/api/bazaar/getBazaarPlugin", model.CheckAuth, getBazaarPlugin)
|
||||||
|
ginServer.Handle("POST", "/api/bazaar/getInstalledPlugin", model.CheckAuth, getInstalledPlugin)
|
||||||
|
ginServer.Handle("POST", "/api/bazaar/installBazaarPlugin", model.CheckAuth, model.CheckReadonly, installBazaarPlugin)
|
||||||
|
ginServer.Handle("POST", "/api/bazaar/uninstallBazaarPlugin", model.CheckAuth, model.CheckReadonly, uninstallBazaarPlugin)
|
||||||
ginServer.Handle("POST", "/api/bazaar/getBazaarWidget", model.CheckAuth, getBazaarWidget)
|
ginServer.Handle("POST", "/api/bazaar/getBazaarWidget", model.CheckAuth, getBazaarWidget)
|
||||||
ginServer.Handle("POST", "/api/bazaar/getInstalledWidget", model.CheckAuth, getInstalledWidget)
|
ginServer.Handle("POST", "/api/bazaar/getInstalledWidget", model.CheckAuth, getInstalledWidget)
|
||||||
ginServer.Handle("POST", "/api/bazaar/installBazaarWidget", model.CheckAuth, model.CheckReadonly, installBazaarWidget)
|
ginServer.Handle("POST", "/api/bazaar/installBazaarWidget", model.CheckAuth, model.CheckReadonly, installBazaarWidget)
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,28 @@ type Package struct {
|
||||||
Downloads int `json:"downloads"`
|
Downloads int `json:"downloads"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PluginJSON(pluginDirName string) (ret map[string]interface{}, err error) {
|
||||||
|
p := filepath.Join(util.DataDir, "plugins", pluginDirName, "plugin.json")
|
||||||
|
if !gulu.File.IsExist(p) {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(p)
|
||||||
|
if nil != err {
|
||||||
|
logging.LogErrorf("read plugin.json [%s] failed: %s", p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err {
|
||||||
|
logging.LogErrorf("parse plugin.json [%s] failed: %s", p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if 4 > len(ret) {
|
||||||
|
logging.LogWarnf("invalid plugin.json [%s]", p)
|
||||||
|
return nil, errors.New("invalid plugin.json")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func WidgetJSON(widgetDirName string) (ret map[string]interface{}, err error) {
|
func WidgetJSON(widgetDirName string) (ret map[string]interface{}, err error) {
|
||||||
p := filepath.Join(util.DataDir, "widgets", widgetDirName, "widget.json")
|
p := filepath.Join(util.DataDir, "widgets", widgetDirName, "widget.json")
|
||||||
if !gulu.File.IsExist(p) {
|
if !gulu.File.IsExist(p) {
|
||||||
|
|
@ -216,6 +238,26 @@ func isOutdatedIcon(icon *Icon, bazaarIcons []*Icon) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isOutdatedPlugin(plugin *Plugin, bazaarPlugins []*Plugin) bool {
|
||||||
|
if !strings.HasPrefix(plugin.URL, "https://github.com/") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := strings.TrimPrefix(plugin.URL, "https://github.com/")
|
||||||
|
parts := strings.Split(repo, "/")
|
||||||
|
if 2 != len(parts) || "" == strings.TrimSpace(parts[1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range bazaarPlugins {
|
||||||
|
if plugin.URL == pkg.URL && plugin.Name == pkg.Name && plugin.Author == pkg.Author && plugin.Version < pkg.Version {
|
||||||
|
plugin.RepoHash = pkg.RepoHash
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func isOutdatedWidget(widget *Widget, bazaarWidgets []*Widget) bool {
|
func isOutdatedWidget(widget *Widget, bazaarWidgets []*Widget) bool {
|
||||||
if !strings.HasPrefix(widget.URL, "https://github.com/") {
|
if !strings.HasPrefix(widget.URL, "https://github.com/") {
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
169
kernel/bazaar/plugin.go
Normal file
169
kernel/bazaar/plugin.go
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
// SiYuan - Build Your Eternal Digital Garden
|
||||||
|
// Copyright (c) 2020-present, b3log.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bazaar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
ants "github.com/panjf2000/ants/v2"
|
||||||
|
"github.com/siyuan-note/httpclient"
|
||||||
|
"github.com/siyuan-note/logging"
|
||||||
|
"github.com/siyuan-note/siyuan/kernel/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Plugin struct {
|
||||||
|
Package
|
||||||
|
}
|
||||||
|
|
||||||
|
func Plugins() (plugins []*Plugin) {
|
||||||
|
plugins = []*Plugin{}
|
||||||
|
|
||||||
|
pkgIndex, err := getPkgIndex("plugins")
|
||||||
|
if nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bazaarIndex := getBazaarIndex()
|
||||||
|
|
||||||
|
repos := pkgIndex["repos"].([]interface{})
|
||||||
|
waitGroup := &sync.WaitGroup{}
|
||||||
|
lock := &sync.Mutex{}
|
||||||
|
p, _ := ants.NewPoolWithFunc(8, func(arg interface{}) {
|
||||||
|
defer waitGroup.Done()
|
||||||
|
|
||||||
|
repo := arg.(map[string]interface{})
|
||||||
|
repoURL := repo["url"].(string)
|
||||||
|
|
||||||
|
plugin := &Plugin{}
|
||||||
|
innerU := util.BazaarOSSServer + "/package/" + repoURL + "/plugin.json"
|
||||||
|
innerResp, innerErr := httpclient.NewBrowserRequest().SetSuccessResult(plugin).Get(innerU)
|
||||||
|
if nil != innerErr {
|
||||||
|
logging.LogErrorf("get bazaar package [%s] failed: %s", repoURL, innerErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if 200 != innerResp.StatusCode {
|
||||||
|
logging.LogErrorf("get bazaar package [%s] failed: %d", innerU, innerResp.StatusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
plugin.URL = strings.TrimSuffix(plugin.URL, "/")
|
||||||
|
|
||||||
|
repoURLHash := strings.Split(repoURL, "@")
|
||||||
|
plugin.RepoURL = "https://github.com/" + repoURLHash[0]
|
||||||
|
plugin.RepoHash = repoURLHash[1]
|
||||||
|
plugin.PreviewURL = util.BazaarOSSServer + "/package/" + repoURL + "/preview.png?imageslim"
|
||||||
|
plugin.PreviewURLThumb = util.BazaarOSSServer + "/package/" + repoURL + "/preview.png?imageView2/2/w/436/h/232"
|
||||||
|
plugin.Updated = repo["updated"].(string)
|
||||||
|
plugin.Stars = int(repo["stars"].(float64))
|
||||||
|
plugin.OpenIssues = int(repo["openIssues"].(float64))
|
||||||
|
plugin.Size = int64(repo["size"].(float64))
|
||||||
|
plugin.HSize = humanize.Bytes(uint64(plugin.Size))
|
||||||
|
plugin.HUpdated = formatUpdated(plugin.Updated)
|
||||||
|
pkg := bazaarIndex[strings.Split(repoURL, "@")[0]]
|
||||||
|
if nil != pkg {
|
||||||
|
plugin.Downloads = pkg.Downloads
|
||||||
|
}
|
||||||
|
lock.Lock()
|
||||||
|
plugins = append(plugins, plugin)
|
||||||
|
lock.Unlock()
|
||||||
|
})
|
||||||
|
for _, repo := range repos {
|
||||||
|
waitGroup.Add(1)
|
||||||
|
p.Invoke(repo)
|
||||||
|
}
|
||||||
|
waitGroup.Wait()
|
||||||
|
p.Release()
|
||||||
|
|
||||||
|
sort.Slice(plugins, func(i, j int) bool { return plugins[i].Updated > plugins[j].Updated })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstalledPlugins() (ret []*Plugin) {
|
||||||
|
ret = []*Plugin{}
|
||||||
|
pluginDirs, err := os.ReadDir(filepath.Join(util.DataDir, "plugins"))
|
||||||
|
if nil != err {
|
||||||
|
logging.LogWarnf("read plugins folder failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bazaarPlugins := Plugins()
|
||||||
|
|
||||||
|
for _, pluginDir := range pluginDirs {
|
||||||
|
if !pluginDir.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dirName := pluginDir.Name()
|
||||||
|
|
||||||
|
pluginConf, parseErr := PluginJSON(dirName)
|
||||||
|
if nil != parseErr || nil == pluginConf {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
installPath := filepath.Join(util.DataDir, "plugins", dirName)
|
||||||
|
|
||||||
|
plugin := &Plugin{}
|
||||||
|
plugin.Installed = true
|
||||||
|
plugin.Name = pluginConf["name"].(string)
|
||||||
|
plugin.Author = pluginConf["author"].(string)
|
||||||
|
plugin.URL = pluginConf["url"].(string)
|
||||||
|
plugin.URL = strings.TrimSuffix(plugin.URL, "/")
|
||||||
|
plugin.Version = pluginConf["version"].(string)
|
||||||
|
plugin.RepoURL = plugin.URL
|
||||||
|
plugin.PreviewURL = "/plugins/" + dirName + "/preview.png"
|
||||||
|
plugin.PreviewURLThumb = "/plugins/" + dirName + "/preview.png"
|
||||||
|
info, statErr := os.Stat(filepath.Join(installPath, "README.md"))
|
||||||
|
if nil != statErr {
|
||||||
|
logging.LogWarnf("stat install theme README.md failed: %s", statErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
plugin.HInstallDate = info.ModTime().Format("2006-01-02")
|
||||||
|
installSize, _ := util.SizeOfDirectory(installPath)
|
||||||
|
plugin.InstallSize = installSize
|
||||||
|
plugin.HInstallSize = humanize.Bytes(uint64(installSize))
|
||||||
|
readme, readErr := os.ReadFile(filepath.Join(installPath, "README.md"))
|
||||||
|
if nil != readErr {
|
||||||
|
logging.LogWarnf("read install plugin README.md failed: %s", readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
plugin.README, _ = renderREADME(plugin.URL, readme)
|
||||||
|
plugin.Outdated = isOutdatedPlugin(plugin, bazaarPlugins)
|
||||||
|
ret = append(ret, plugin)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstallPlugin(repoURL, repoHash, installPath string, systemID string) error {
|
||||||
|
repoURLHash := repoURL + "@" + repoHash
|
||||||
|
data, err := downloadPackage(repoURLHash, true, systemID)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return installPackage(data, installPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UninstallPlugin(installPath string) error {
|
||||||
|
if err := os.RemoveAll(installPath); nil != err {
|
||||||
|
logging.LogErrorf("remove plugin [%s] failed: %s", installPath, err)
|
||||||
|
return errors.New("remove community plugin failed")
|
||||||
|
}
|
||||||
|
//logging.Logger.Infof("uninstalled plugin [%s]", installPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,46 @@ func GetPackageREADME(repoURL, repoHash string) (ret string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BazaarPlugins() (widgets []*bazaar.Widget) {
|
||||||
|
widgets = bazaar.Widgets()
|
||||||
|
for _, widget := range widgets {
|
||||||
|
widget.Installed = gulu.File.IsDir(filepath.Join(util.DataDir, "widgets", widget.Name))
|
||||||
|
if widget.Installed {
|
||||||
|
if widget.Installed {
|
||||||
|
if widgetConf, err := widgetJSON(widget.Name); nil == err && nil != widget {
|
||||||
|
if widget.Version != widgetConf["version"].(string) {
|
||||||
|
widget.Outdated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstalledPlugins() (plugins []*bazaar.Plugin) {
|
||||||
|
plugins = bazaar.InstalledPlugins()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstallBazaarPlugin(repoURL, repoHash, pluginName string) error {
|
||||||
|
installPath := filepath.Join(util.DataDir, "plugins", pluginName)
|
||||||
|
err := bazaar.InstallPlugin(repoURL, repoHash, installPath, Conf.System.ID)
|
||||||
|
if nil != err {
|
||||||
|
return errors.New(fmt.Sprintf(Conf.Language(46), pluginName))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UninstallBazaarPlugin(pluginName string) error {
|
||||||
|
installPath := filepath.Join(util.DataDir, "plugins", pluginName)
|
||||||
|
err := bazaar.UninstallPlugin(installPath)
|
||||||
|
if nil != err {
|
||||||
|
return errors.New(fmt.Sprintf(Conf.Language(47), err.Error()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func BazaarWidgets() (widgets []*bazaar.Widget) {
|
func BazaarWidgets() (widgets []*bazaar.Widget) {
|
||||||
widgets = bazaar.Widgets()
|
widgets = bazaar.Widgets()
|
||||||
for _, widget := range widgets {
|
for _, widget := range widgets {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue