mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 22:50: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) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
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/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)
|
||||
|
|
|
|||
|
|
@ -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
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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue