A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 269 lines 7.3 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 model 18 19import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "sync" 24 25 "github.com/88250/gulu" 26 "github.com/siyuan-note/filelock" 27 "github.com/siyuan-note/logging" 28 "github.com/siyuan-note/siyuan/kernel/bazaar" 29 "github.com/siyuan-note/siyuan/kernel/util" 30) 31 32// Petal represents a plugin's management status. 33type Petal struct { 34 Name string `json:"name"` // Plugin name 35 DisplayName string `json:"displayName"` // Plugin display name 36 Enabled bool `json:"enabled"` // Whether enabled 37 Incompatible bool `json:"incompatible"` // Whether incompatible 38 DisabledInPublish bool `json:"disabledInPublish"` // Whether disabled in publish mode 39 40 JS string `json:"js"` // JS code 41 CSS string `json:"css"` // CSS code 42 I18n map[string]interface{} `json:"i18n"` // i18n text 43} 44 45func SetPetalEnabled(name string, enabled bool, frontend string) (ret *Petal, err error) { 46 petals := getPetals() 47 48 found, displayName, incompatible, disabledInPublish := bazaar.ParseInstalledPlugin(name, frontend) 49 if !found { 50 logging.LogErrorf("plugin [%s] not found", name) 51 return 52 } 53 54 ret = getPetalByName(name, petals) 55 if nil == ret { 56 ret = &Petal{ 57 Name: name, 58 } 59 petals = append(petals, ret) 60 } 61 ret.DisplayName = displayName 62 ret.Enabled = enabled 63 ret.Incompatible = incompatible 64 ret.DisabledInPublish = disabledInPublish 65 66 if incompatible { 67 err = fmt.Errorf(Conf.Language(205)) 68 logging.LogInfof("plugin [%s] is incompatible [%s]", name, frontend) 69 return 70 } 71 72 savePetals(petals) 73 loadCode(ret) 74 return 75} 76 77func LoadPetals(frontend string, isPublish bool) (ret []*Petal) { 78 ret = []*Petal{} 79 80 if Conf.Bazaar.PetalDisabled { 81 return 82 } 83 84 if !Conf.Bazaar.Trust { 85 // 移动端没有集市模块,所以要默认开启,桌面端和 Docker 容器需要用户手动确认过信任后才能开启 86 if util.ContainerStd == util.Container || util.ContainerDocker == util.Container { 87 return 88 } 89 } 90 91 petals := getPetals() 92 for _, petal := range petals { 93 installPath := filepath.Join(util.DataDir, "plugins", petal.Name) 94 if !filelock.IsExist(installPath) { 95 continue 96 } 97 98 _, petal.DisplayName, petal.Incompatible, petal.DisabledInPublish = bazaar.ParseInstalledPlugin(petal.Name, frontend) 99 if !petal.Enabled || petal.Incompatible || (isPublish && petal.DisabledInPublish) { 100 continue 101 } 102 103 loadCode(petal) 104 ret = append(ret, petal) 105 } 106 return 107} 108 109func loadCode(petal *Petal) { 110 pluginDir := filepath.Join(util.DataDir, "plugins", petal.Name) 111 jsPath := filepath.Join(pluginDir, "index.js") 112 if !filelock.IsExist(jsPath) { 113 logging.LogErrorf("plugin [%s] js not found", petal.Name) 114 return 115 } 116 117 data, err := filelock.ReadFile(jsPath) 118 if err != nil { 119 logging.LogErrorf("read plugin [%s] js failed: %s", petal.Name, err) 120 return 121 } 122 petal.JS = string(data) 123 124 cssPath := filepath.Join(pluginDir, "index.css") 125 if filelock.IsExist(cssPath) { 126 data, err = filelock.ReadFile(cssPath) 127 if err != nil { 128 logging.LogErrorf("read plugin [%s] css failed: %s", petal.Name, err) 129 } else { 130 petal.CSS = string(data) 131 } 132 } 133 134 i18nDir := filepath.Join(pluginDir, "i18n") 135 if gulu.File.IsDir(i18nDir) { 136 langJSONs, readErr := os.ReadDir(i18nDir) 137 if nil != readErr { 138 logging.LogErrorf("read plugin [%s] i18n failed: %s", petal.Name, readErr) 139 } else if 0 < len(langJSONs) { 140 preferredLang := Conf.Lang + ".json" 141 foundPreferredLang := false 142 foundEnUS := false 143 foundZhCN := false 144 for _, langJSON := range langJSONs { 145 if langJSON.Name() == preferredLang { 146 foundPreferredLang = true 147 break 148 } 149 if langJSON.Name() == "en_US.json" { 150 foundEnUS = true 151 } 152 if langJSON.Name() == "zh_CN.json" { 153 foundZhCN = true 154 } 155 } 156 157 if !foundPreferredLang { 158 if foundEnUS { 159 preferredLang = "en_US.json" 160 } else if foundZhCN { 161 preferredLang = "zh_CN.json" 162 } else { 163 preferredLang = langJSONs[0].Name() 164 } 165 } 166 167 if langFilePath := filepath.Join(i18nDir, preferredLang); gulu.File.IsExist(langFilePath) { 168 data, err = filelock.ReadFile(langFilePath) 169 if err != nil { 170 logging.LogErrorf("read plugin [%s] i18n failed: %s", petal.Name, err) 171 } else { 172 petal.I18n = map[string]interface{}{} 173 if err = gulu.JSON.UnmarshalJSON(data, &petal.I18n); err != nil { 174 logging.LogErrorf("unmarshal plugin [%s] i18n failed: %s", petal.Name, err) 175 } 176 } 177 } 178 } 179 } 180} 181 182var petalsStoreLock = sync.Mutex{} 183 184func savePetals(petals []*Petal) { 185 petalsStoreLock.Lock() 186 defer petalsStoreLock.Unlock() 187 savePetals0(petals) 188} 189 190func savePetals0(petals []*Petal) { 191 if 1 > len(petals) { 192 petals = []*Petal{} 193 } 194 195 petalDir := filepath.Join(util.DataDir, "storage", "petal") 196 confPath := filepath.Join(petalDir, "petals.json") 197 data, err := gulu.JSON.MarshalIndentJSON(petals, "", "\t") 198 if err != nil { 199 logging.LogErrorf("marshal petals failed: %s", err) 200 return 201 } 202 if err = filelock.WriteFile(confPath, data); err != nil { 203 logging.LogErrorf("write petals [%s] failed: %s", confPath, err) 204 return 205 } 206} 207 208func getPetals() (ret []*Petal) { 209 petalsStoreLock.Lock() 210 defer petalsStoreLock.Unlock() 211 212 ret = []*Petal{} 213 petalDir := filepath.Join(util.DataDir, "storage", "petal") 214 if err := os.MkdirAll(petalDir, 0755); err != nil { 215 logging.LogErrorf("create petal dir [%s] failed: %s", petalDir, err) 216 return 217 } 218 219 confPath := filepath.Join(petalDir, "petals.json") 220 if !filelock.IsExist(confPath) { 221 data, err := gulu.JSON.MarshalIndentJSON(ret, "", "\t") 222 if err != nil { 223 logging.LogErrorf("marshal petals failed: %s", err) 224 return 225 } 226 if err = filelock.WriteFile(confPath, data); err != nil { 227 logging.LogErrorf("write petals [%s] failed: %s", confPath, err) 228 return 229 } 230 return 231 } 232 233 data, err := filelock.ReadFile(confPath) 234 if err != nil { 235 logging.LogErrorf("read petal file [%s] failed: %s", confPath, err) 236 return 237 } 238 239 if err = gulu.JSON.UnmarshalJSON(data, &ret); err != nil { 240 logging.LogErrorf("unmarshal petals failed: %s", err) 241 return 242 } 243 244 var tmp []*Petal 245 pluginsDir := filepath.Join(util.DataDir, "plugins") 246 for _, petal := range ret { 247 if petal.Enabled && filelock.IsExist(filepath.Join(pluginsDir, petal.Name)) { 248 tmp = append(tmp, petal) 249 } 250 } 251 if len(tmp) != len(ret) { 252 savePetals0(tmp) 253 ret = tmp 254 } 255 if 1 > len(ret) { 256 ret = []*Petal{} 257 } 258 return 259} 260 261func getPetalByName(name string, petals []*Petal) (ret *Petal) { 262 for _, p := range petals { 263 if name == p.Name { 264 ret = p 265 break 266 } 267 } 268 return 269}