🎨 Community marketplace adds plugin section https://github.com/siyuan-note/siyuan/issues/8043

This commit is contained in:
Liang Ding 2023-04-25 18:52:19 +08:00
parent 0384261c95
commit 4a5f3ea104
No known key found for this signature in database
GPG key ID: 136F30F901A2231D
5 changed files with 320 additions and 0 deletions

View file

@ -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) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)

View file

@ -275,6 +275,10 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/graph/getGraph", model.CheckAuth, getGraph)
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/getInstalledWidget", model.CheckAuth, getInstalledWidget)
ginServer.Handle("POST", "/api/bazaar/installBazaarWidget", model.CheckAuth, model.CheckReadonly, installBazaarWidget)

View file

@ -66,6 +66,28 @@ type Package struct {
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) {
p := filepath.Join(util.DataDir, "widgets", widgetDirName, "widget.json")
if !gulu.File.IsExist(p) {
@ -216,6 +238,26 @@ func isOutdatedIcon(icon *Icon, bazaarIcons []*Icon) bool {
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 {
if !strings.HasPrefix(widget.URL, "https://github.com/") {
return false

169
kernel/bazaar/plugin.go Normal file
View 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
}

View file

@ -32,6 +32,46 @@ func GetPackageREADME(repoURL, repoHash string) (ret string) {
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) {
widgets = bazaar.Widgets()
for _, widget := range widgets {