A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 259 lines 7.7 kB view raw
1// SiYuan - Refactor your thinking 2// Copyright (c) 2020-present, b3log.org 3// 4// This program is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Affero General Public License as published by 6// the Free Software Foundation, either version 3 of the License, or 7// (at your option) any later version. 8// 9// This program is distributed in the hope that it will be useful, 10// but WITHOUT ANY WARRANTY; without even the implied warranty of 11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12// GNU Affero General Public License for more details. 13// 14// You should have received a copy of the GNU Affero General Public License 15// along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17package bazaar 18 19import ( 20 "os" 21 "path/filepath" 22 "runtime" 23 "sort" 24 "strings" 25 26 "github.com/88250/go-humanize" 27 "github.com/siyuan-note/logging" 28 "github.com/siyuan-note/siyuan/kernel/util" 29) 30 31type Plugin struct { 32 *Package 33 Enabled bool `json:"enabled"` 34} 35 36// Plugins 返回集市插件列表 37func Plugins(frontend string) (plugins []*Plugin) { 38 plugins = []*Plugin{} 39 result := getStageAndBazaar("plugins") 40 41 if !result.Online { 42 return 43 } 44 if result.StageErr != nil { 45 return 46 } 47 if 1 > len(result.BazaarIndex) { 48 return 49 } 50 51 for _, repo := range result.StageIndex.Repos { 52 if nil == repo.Package { 53 continue 54 } 55 plugin := buildPluginFromStageRepo(repo, frontend, result.BazaarIndex) 56 if nil != plugin { 57 plugins = append(plugins, plugin) 58 } 59 } 60 61 sort.Slice(plugins, func(i, j int) bool { return plugins[i].Updated > plugins[j].Updated }) 62 return 63} 64 65// buildPluginFromStageRepo 使用 stage 内嵌的 package 构建 *Plugin,不发起 HTTP 请求。 66func buildPluginFromStageRepo(repo *StageRepo, frontend string, bazaarIndex map[string]*bazaarPackage) *Plugin { 67 pkg := *repo.Package 68 pkg.URL = strings.TrimSuffix(pkg.URL, "/") 69 repoURLHash := strings.Split(repo.URL, "@") 70 if 2 != len(repoURLHash) { 71 return nil 72 } 73 pkg.RepoURL = "https://github.com/" + repoURLHash[0] 74 pkg.RepoHash = repoURLHash[1] 75 pkg.PreviewURL = util.BazaarOSSServer + "/package/" + repo.URL + "/preview.png?imageslim" 76 pkg.PreviewURLThumb = util.BazaarOSSServer + "/package/" + repo.URL + "/preview.png?imageView2/2/w/436/h/232" 77 pkg.IconURL = util.BazaarOSSServer + "/package/" + repo.URL + "/icon.png" 78 pkg.Updated = repo.Updated 79 pkg.Stars = repo.Stars 80 pkg.OpenIssues = repo.OpenIssues 81 pkg.Size = repo.Size 82 pkg.HSize = humanize.BytesCustomCeil(uint64(pkg.Size), 2) 83 pkg.InstallSize = repo.InstallSize 84 pkg.HInstallSize = humanize.BytesCustomCeil(uint64(pkg.InstallSize), 2) 85 pkg.HUpdated = formatUpdated(pkg.Updated) 86 pkg.PreferredFunding = getPreferredFunding(pkg.Funding) 87 pkg.PreferredName = GetPreferredName(&pkg) 88 pkg.PreferredDesc = getPreferredDesc(pkg.Description) 89 pkg.DisallowInstall = disallowInstallBazaarPackage(&pkg) 90 pkg.DisallowUpdate = disallowInstallBazaarPackage(&pkg) 91 pkg.UpdateRequiredMinAppVer = pkg.MinAppVersion 92 pkg.Incompatible = isIncompatiblePlugin(&Plugin{Package: &pkg}, frontend) 93 if bp := bazaarIndex[repoURLHash[0]]; nil != bp { 94 pkg.Downloads = bp.Downloads 95 } 96 packageInstallSizeCache.SetDefault(pkg.RepoURL, pkg.InstallSize) 97 return &Plugin{Package: &pkg} 98} 99 100func ParseInstalledPlugin(name, frontend string) (found bool, displayName string, incompatible, disabledInPublish, disallowInstall bool) { 101 pluginsPath := filepath.Join(util.DataDir, "plugins") 102 if !util.IsPathRegularDirOrSymlinkDir(pluginsPath) { 103 return 104 } 105 106 pluginDirs, err := os.ReadDir(pluginsPath) 107 if err != nil { 108 logging.LogWarnf("read plugins folder failed: %s", err) 109 return 110 } 111 112 for _, pluginDir := range pluginDirs { 113 if !util.IsDirRegularOrSymlink(pluginDir) { 114 continue 115 } 116 dirName := pluginDir.Name() 117 if name != dirName { 118 continue 119 } 120 121 plugin, parseErr := PluginJSON(dirName) 122 if nil != parseErr || nil == plugin { 123 return 124 } 125 126 found = true 127 displayName = GetPreferredName(plugin.Package) 128 incompatible = isIncompatiblePlugin(plugin, frontend) 129 disabledInPublish = plugin.DisabledInPublish 130 disallowInstall = disallowInstallBazaarPackage(plugin.Package) 131 } 132 return 133} 134 135func InstalledPlugins(frontend string) (ret []*Plugin) { 136 ret = []*Plugin{} 137 138 pluginsPath := filepath.Join(util.DataDir, "plugins") 139 if !util.IsPathRegularDirOrSymlinkDir(pluginsPath) { 140 return 141 } 142 143 pluginDirs, err := os.ReadDir(pluginsPath) 144 if err != nil { 145 logging.LogWarnf("read plugins folder failed: %s", err) 146 return 147 } 148 149 bazaarPlugins := Plugins(frontend) 150 151 for _, pluginDir := range pluginDirs { 152 if !util.IsDirRegularOrSymlink(pluginDir) { 153 continue 154 } 155 dirName := pluginDir.Name() 156 157 plugin, parseErr := PluginJSON(dirName) 158 if nil != parseErr || nil == plugin { 159 continue 160 } 161 162 plugin.RepoURL = plugin.URL 163 plugin.DisallowInstall = disallowInstallBazaarPackage(plugin.Package) 164 if bazaarPkg := getBazaarPlugin(plugin.Name, bazaarPlugins); nil != bazaarPkg { 165 plugin.DisallowUpdate = disallowInstallBazaarPackage(bazaarPkg.Package) 166 plugin.UpdateRequiredMinAppVer = bazaarPkg.MinAppVersion 167 plugin.RepoURL = bazaarPkg.RepoURL 168 } 169 170 installPath := filepath.Join(util.DataDir, "plugins", dirName) 171 plugin.Installed = true 172 plugin.PreviewURL = "/plugins/" + dirName + "/preview.png" 173 plugin.PreviewURLThumb = "/plugins/" + dirName + "/preview.png" 174 plugin.IconURL = "/plugins/" + dirName + "/icon.png" 175 plugin.PreferredFunding = getPreferredFunding(plugin.Funding) 176 plugin.PreferredName = GetPreferredName(plugin.Package) 177 plugin.PreferredDesc = getPreferredDesc(plugin.Description) 178 info, statErr := os.Stat(filepath.Join(installPath, "plugin.json")) 179 if nil != statErr { 180 logging.LogWarnf("stat install plugin.json failed: %s", statErr) 181 continue 182 } 183 plugin.HInstallDate = info.ModTime().Format("2006-01-02") 184 if installSize, ok := packageInstallSizeCache.Get(plugin.RepoURL); ok { 185 plugin.InstallSize = installSize.(int64) 186 } else { 187 is, _ := util.SizeOfDirectory(installPath) 188 plugin.InstallSize = is 189 packageInstallSizeCache.SetDefault(plugin.RepoURL, is) 190 } 191 plugin.HInstallSize = humanize.BytesCustomCeil(uint64(plugin.InstallSize), 2) 192 plugin.PreferredReadme = loadInstalledReadme(installPath, "/plugins/"+dirName+"/", plugin.Readme) 193 plugin.Outdated = isOutdatedPlugin(plugin, bazaarPlugins) 194 plugin.Incompatible = isIncompatiblePlugin(plugin, frontend) 195 ret = append(ret, plugin) 196 } 197 return 198} 199 200func getBazaarPlugin(name string, plugins []*Plugin) *Plugin { 201 for _, p := range plugins { 202 if p.Name == name { 203 return p 204 } 205 } 206 return nil 207} 208 209func InstallPlugin(repoURL, repoHash, installPath string, systemID string) error { 210 repoURLHash := repoURL + "@" + repoHash 211 data, err := downloadPackage(repoURLHash, true, systemID) 212 if err != nil { 213 return err 214 } 215 return installPackage(data, installPath, repoURLHash) 216} 217 218func UninstallPlugin(installPath string) error { 219 return uninstallPackage(installPath) 220} 221 222func isIncompatiblePlugin(plugin *Plugin, currentFrontend string) bool { 223 if 1 > len(plugin.Backends) { 224 return false 225 } 226 227 currentBackend := getCurrentBackend() 228 backendOk := false 229 for _, backend := range plugin.Backends { 230 if backend == currentBackend || "all" == backend { 231 backendOk = true 232 break 233 } 234 } 235 236 frontendOk := false 237 for _, frontend := range plugin.Frontends { 238 if frontend == currentFrontend || "all" == frontend { 239 frontendOk = true 240 break 241 } 242 } 243 return !backendOk || !frontendOk 244} 245 246func getCurrentBackend() string { 247 switch util.Container { 248 case util.ContainerDocker: 249 return "docker" 250 case util.ContainerIOS: 251 return "ios" 252 case util.ContainerAndroid: 253 return "android" 254 case util.ContainerHarmony: 255 return "harmony" 256 default: 257 return runtime.GOOS 258 } 259}