A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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 "errors"
21 "fmt"
22 "path"
23 "path/filepath"
24 "strings"
25 "sync"
26 "time"
27
28 "github.com/88250/gulu"
29 "github.com/emirpasic/gods/sets/hashset"
30 "github.com/siyuan-note/logging"
31 "github.com/siyuan-note/siyuan/kernel/bazaar"
32 "github.com/siyuan-note/siyuan/kernel/task"
33 "github.com/siyuan-note/siyuan/kernel/util"
34 "golang.org/x/mod/semver"
35)
36
37func BatchUpdateBazaarPackages(frontend string) {
38 plugins, widgets, icons, themes, templates := UpdatedPackages(frontend)
39
40 total := len(plugins) + len(widgets) + len(icons) + len(themes) + len(templates)
41 if 1 > total {
42 return
43 }
44
45 util.PushEndlessProgress(fmt.Sprintf(Conf.language(235), 1, total))
46 defer util.PushClearProgress()
47 count := 1
48 for _, plugin := range plugins {
49 err := bazaar.InstallPlugin(plugin.RepoURL, plugin.RepoHash, filepath.Join(util.DataDir, "plugins", plugin.Name), Conf.System.ID)
50 if err != nil {
51 logging.LogErrorf("update plugin [%s] failed: %s", plugin.Name, err)
52 util.PushErrMsg(fmt.Sprintf(Conf.language(238), plugin.Name), 5000)
53 return
54 }
55
56 count++
57 util.PushEndlessProgress(fmt.Sprintf(Conf.language(236), count, total, plugin.Name))
58 }
59
60 for _, widget := range widgets {
61 err := bazaar.InstallWidget(widget.RepoURL, widget.RepoHash, filepath.Join(util.DataDir, "widgets", widget.Name), Conf.System.ID)
62 if err != nil {
63 logging.LogErrorf("update widget [%s] failed: %s", widget.Name, err)
64 util.PushErrMsg(fmt.Sprintf(Conf.language(238), widget.Name), 5000)
65 return
66 }
67
68 count++
69 util.PushEndlessProgress(fmt.Sprintf(Conf.language(236), count, total, widget.Name))
70 }
71
72 for _, icon := range icons {
73 err := bazaar.InstallIcon(icon.RepoURL, icon.RepoHash, filepath.Join(util.IconsPath, icon.Name), Conf.System.ID)
74 if err != nil {
75 logging.LogErrorf("update icon [%s] failed: %s", icon.Name, err)
76 util.PushErrMsg(fmt.Sprintf(Conf.language(238), icon.Name), 5000)
77 return
78 }
79
80 count++
81 util.PushEndlessProgress(fmt.Sprintf(Conf.language(236), count, total, icon.Name))
82 }
83
84 for _, template := range templates {
85 err := bazaar.InstallTemplate(template.RepoURL, template.RepoHash, filepath.Join(util.DataDir, "templates", template.Name), Conf.System.ID)
86 if err != nil {
87 logging.LogErrorf("update template [%s] failed: %s", template.Name, err)
88 util.PushErrMsg(fmt.Sprintf(Conf.language(238), template.Name), 5000)
89 return
90 }
91
92 count++
93 util.PushEndlessProgress(fmt.Sprintf(Conf.language(236), count, total, template.Name))
94 }
95
96 for _, theme := range themes {
97 err := bazaar.InstallTheme(theme.RepoURL, theme.RepoHash, filepath.Join(util.ThemesPath, theme.Name), Conf.System.ID)
98 if err != nil {
99 logging.LogErrorf("update theme [%s] failed: %s", theme.Name, err)
100 util.PushErrMsg(fmt.Sprintf(Conf.language(238), theme.Name), 5000)
101 return
102 }
103
104 count++
105 util.PushEndlessProgress(fmt.Sprintf(Conf.language(236), count, total, theme.Name))
106 }
107
108 util.ReloadUI()
109 task.AppendAsyncTaskWithDelay(task.PushMsg, 3*time.Second, util.PushMsg, fmt.Sprintf(Conf.language(237), total), 5000)
110 return
111}
112
113func UpdatedPackages(frontend string) (plugins []*bazaar.Plugin, widgets []*bazaar.Widget, icons []*bazaar.Icon, themes []*bazaar.Theme, templates []*bazaar.Template) {
114 wg := &sync.WaitGroup{}
115 wg.Add(5)
116 go func() {
117 defer wg.Done()
118 tmp := InstalledPlugins(frontend, "")
119 for _, plugin := range tmp {
120 if plugin.Outdated {
121 plugins = append(plugins, plugin)
122 }
123 }
124 }()
125
126 go func() {
127 defer wg.Done()
128 tmp := InstalledWidgets("")
129 for _, widget := range tmp {
130 if widget.Outdated {
131 widgets = append(widgets, widget)
132 }
133 }
134 }()
135
136 go func() {
137 defer wg.Done()
138 tmp := InstalledIcons("")
139 for _, icon := range tmp {
140 if icon.Outdated {
141 icons = append(icons, icon)
142 }
143 }
144 }()
145
146 go func() {
147 defer wg.Done()
148 tmp := InstalledThemes("")
149 for _, theme := range tmp {
150 if theme.Outdated {
151 themes = append(themes, theme)
152 }
153 }
154 }()
155
156 go func() {
157 defer wg.Done()
158 tmp := InstalledTemplates("")
159 for _, template := range tmp {
160 if template.Outdated {
161 templates = append(templates, template)
162 }
163 }
164 }()
165
166 wg.Wait()
167
168 if 1 > len(plugins) {
169 plugins = []*bazaar.Plugin{}
170 }
171
172 if 1 > len(widgets) {
173 widgets = []*bazaar.Widget{}
174 }
175
176 if 1 > len(icons) {
177 icons = []*bazaar.Icon{}
178 }
179
180 if 1 > len(themes) {
181 themes = []*bazaar.Theme{}
182 }
183
184 if 1 > len(templates) {
185 templates = []*bazaar.Template{}
186 }
187 return
188}
189
190func GetPackageREADME(repoURL, repoHash, packageType string) (ret string) {
191 ret = bazaar.GetPackageREADME(repoURL, repoHash, packageType)
192 return
193}
194
195func BazaarPlugins(frontend, keyword string) (plugins []*bazaar.Plugin) {
196 plugins = bazaar.Plugins(frontend)
197 plugins = filterPlugins(plugins, keyword)
198 for _, plugin := range plugins {
199 plugin.Installed = util.IsPathRegularDirOrSymlinkDir(filepath.Join(util.DataDir, "plugins", plugin.Name))
200 if plugin.Installed {
201 if pluginConf, err := bazaar.PluginJSON(plugin.Name); err == nil && nil != plugin {
202 plugin.Outdated = 0 > semver.Compare("v"+pluginConf.Version, "v"+plugin.Version)
203 }
204 }
205 }
206 return
207}
208
209func filterPlugins(plugins []*bazaar.Plugin, keyword string) (ret []*bazaar.Plugin) {
210 ret = []*bazaar.Plugin{}
211 keywords := getSearchKeywords(keyword)
212 for _, plugin := range plugins {
213 if matchPackage(keywords, plugin.Package) {
214 ret = append(ret, plugin)
215 }
216 }
217 return
218}
219
220func InstalledPlugins(frontend, keyword string) (plugins []*bazaar.Plugin) {
221 plugins = bazaar.InstalledPlugins(frontend, true)
222 plugins = filterPlugins(plugins, keyword)
223 petals := getPetals()
224 for _, plugin := range plugins {
225 petal := getPetalByName(plugin.Name, petals)
226 if nil != petal {
227 plugin.Enabled = petal.Enabled
228 }
229 }
230 return
231}
232
233func InstallBazaarPlugin(repoURL, repoHash, pluginName string) error {
234 installPath := filepath.Join(util.DataDir, "plugins", pluginName)
235 err := bazaar.InstallPlugin(repoURL, repoHash, installPath, Conf.System.ID)
236 if err != nil {
237 return errors.New(fmt.Sprintf(Conf.Language(46), pluginName, err))
238 }
239 return nil
240}
241
242func UninstallBazaarPlugin(pluginName, frontend string) error {
243 installPath := filepath.Join(util.DataDir, "plugins", pluginName)
244 err := bazaar.UninstallPlugin(installPath)
245 if err != nil {
246 return errors.New(fmt.Sprintf(Conf.Language(47), err.Error()))
247 }
248
249 petals := getPetals()
250 var tmp []*Petal
251 for i, petal := range petals {
252 if petal.Name != pluginName {
253 tmp = append(tmp, petals[i])
254 }
255 }
256 petals = tmp
257 savePetals(petals)
258
259 removePluginSet := hashset.New(pluginName)
260 pushReloadPlugin(nil, removePluginSet, "")
261 return nil
262}
263
264func BazaarWidgets(keyword string) (widgets []*bazaar.Widget) {
265 widgets = bazaar.Widgets()
266 widgets = filterWidgets(widgets, keyword)
267 for _, widget := range widgets {
268 widget.Installed = util.IsPathRegularDirOrSymlinkDir(filepath.Join(util.DataDir, "widgets", widget.Name))
269 if widget.Installed {
270 if widgetConf, err := bazaar.WidgetJSON(widget.Name); err == nil && nil != widget {
271 widget.Outdated = 0 > semver.Compare("v"+widgetConf.Version, "v"+widget.Version)
272 }
273 }
274 }
275 return
276}
277
278func filterWidgets(widgets []*bazaar.Widget, keyword string) (ret []*bazaar.Widget) {
279 ret = []*bazaar.Widget{}
280 keywords := getSearchKeywords(keyword)
281 for _, w := range widgets {
282 if matchPackage(keywords, w.Package) {
283 ret = append(ret, w)
284 }
285 }
286 return
287}
288
289func InstalledWidgets(keyword string) (widgets []*bazaar.Widget) {
290 widgets = bazaar.InstalledWidgets()
291 widgets = filterWidgets(widgets, keyword)
292 return
293}
294
295func InstallBazaarWidget(repoURL, repoHash, widgetName string) error {
296 installPath := filepath.Join(util.DataDir, "widgets", widgetName)
297 err := bazaar.InstallWidget(repoURL, repoHash, installPath, Conf.System.ID)
298 if err != nil {
299 return errors.New(fmt.Sprintf(Conf.Language(46), widgetName, err))
300 }
301 return nil
302}
303
304func UninstallBazaarWidget(widgetName string) error {
305 installPath := filepath.Join(util.DataDir, "widgets", widgetName)
306 err := bazaar.UninstallWidget(installPath)
307 if err != nil {
308 return errors.New(fmt.Sprintf(Conf.Language(47), err.Error()))
309 }
310 return nil
311}
312
313func BazaarIcons(keyword string) (icons []*bazaar.Icon) {
314 icons = bazaar.Icons()
315 icons = filterIcons(icons, keyword)
316 for _, installed := range Conf.Appearance.Icons {
317 for _, icon := range icons {
318 if installed == icon.Name {
319 icon.Installed = true
320 if iconConf, err := bazaar.IconJSON(icon.Name); err == nil {
321 icon.Outdated = 0 > semver.Compare("v"+iconConf.Version, "v"+icon.Version)
322 }
323 }
324 icon.Current = icon.Name == Conf.Appearance.Icon
325 }
326 }
327 return
328}
329
330func filterIcons(icons []*bazaar.Icon, keyword string) (ret []*bazaar.Icon) {
331 ret = []*bazaar.Icon{}
332 keywords := getSearchKeywords(keyword)
333 for _, i := range icons {
334 if matchPackage(keywords, i.Package) {
335 ret = append(ret, i)
336 }
337 }
338 return
339}
340
341func InstalledIcons(keyword string) (icons []*bazaar.Icon) {
342 icons = bazaar.InstalledIcons()
343 icons = filterIcons(icons, keyword)
344 for _, icon := range icons {
345 icon.Current = icon.Name == Conf.Appearance.Icon
346 }
347 return
348}
349
350func InstallBazaarIcon(repoURL, repoHash, iconName string) error {
351 installPath := filepath.Join(util.IconsPath, iconName)
352 err := bazaar.InstallIcon(repoURL, repoHash, installPath, Conf.System.ID)
353 if err != nil {
354 return errors.New(fmt.Sprintf(Conf.Language(46), iconName, err))
355 }
356 Conf.Appearance.Icon = iconName
357 Conf.Save()
358 InitAppearance()
359 return nil
360}
361
362func UninstallBazaarIcon(iconName string) error {
363 installPath := filepath.Join(util.IconsPath, iconName)
364 err := bazaar.UninstallIcon(installPath)
365 if err != nil {
366 return errors.New(fmt.Sprintf(Conf.Language(47), err.Error()))
367 }
368
369 InitAppearance()
370 return nil
371}
372
373func BazaarThemes(keyword string) (ret []*bazaar.Theme) {
374 ret = bazaar.Themes()
375 ret = filterThemes(ret, keyword)
376 installs := Conf.Appearance.DarkThemes
377 installs = append(installs, Conf.Appearance.LightThemes...)
378 for _, installed := range installs {
379 for _, theme := range ret {
380 if installed.Name == theme.Name {
381 theme.Installed = true
382 if themeConf, err := bazaar.ThemeJSON(theme.Name); err == nil {
383 theme.Outdated = 0 > semver.Compare("v"+themeConf.Version, "v"+theme.Version)
384 }
385 theme.Current = theme.Name == Conf.Appearance.ThemeDark || theme.Name == Conf.Appearance.ThemeLight
386 }
387 }
388 }
389 return
390}
391
392func filterThemes(themes []*bazaar.Theme, keyword string) (ret []*bazaar.Theme) {
393 ret = []*bazaar.Theme{}
394 keywords := getSearchKeywords(keyword)
395 for _, t := range themes {
396 if matchPackage(keywords, t.Package) {
397 ret = append(ret, t)
398 }
399 }
400 return
401}
402
403func InstalledThemes(keyword string) (ret []*bazaar.Theme) {
404 ret = bazaar.InstalledThemes()
405 ret = filterThemes(ret, keyword)
406 for _, theme := range ret {
407 theme.Current = theme.Name == Conf.Appearance.ThemeDark || theme.Name == Conf.Appearance.ThemeLight
408 }
409 return
410}
411
412func InstallBazaarTheme(repoURL, repoHash, themeName string, mode int, update bool) error {
413 closeThemeWatchers()
414
415 installPath := filepath.Join(util.ThemesPath, themeName)
416 err := bazaar.InstallTheme(repoURL, repoHash, installPath, Conf.System.ID)
417 if err != nil {
418 return errors.New(fmt.Sprintf(Conf.Language(46), themeName, err))
419 }
420
421 if !update {
422 // 更新主题后不需要对该主题进行切换 https://github.com/siyuan-note/siyuan/issues/4966
423 if 0 == mode {
424 Conf.Appearance.ThemeLight = themeName
425 } else {
426 Conf.Appearance.ThemeDark = themeName
427 }
428 Conf.Appearance.Mode = mode
429 Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(installPath, "theme.js"))
430 Conf.Save()
431 }
432
433 InitAppearance()
434 return nil
435}
436
437func UninstallBazaarTheme(themeName string) error {
438 closeThemeWatchers()
439
440 installPath := filepath.Join(util.ThemesPath, themeName)
441 err := bazaar.UninstallTheme(installPath)
442 if err != nil {
443 return errors.New(fmt.Sprintf(Conf.Language(47), err.Error()))
444 }
445
446 InitAppearance()
447 return nil
448}
449
450func BazaarTemplates(keyword string) (templates []*bazaar.Template) {
451 templates = bazaar.Templates()
452 templates = filterTemplates(templates, keyword)
453 for _, template := range templates {
454 template.Installed = util.IsPathRegularDirOrSymlinkDir(filepath.Join(util.DataDir, "templates", template.Name))
455 if template.Installed {
456 if templateConf, err := bazaar.TemplateJSON(template.Name); err == nil && nil != templateConf {
457 template.Outdated = 0 > semver.Compare("v"+templateConf.Version, "v"+template.Version)
458 }
459 }
460 }
461 return
462}
463
464func filterTemplates(templates []*bazaar.Template, keyword string) (ret []*bazaar.Template) {
465 ret = []*bazaar.Template{}
466 keywords := getSearchKeywords(keyword)
467 for _, t := range templates {
468 if matchPackage(keywords, t.Package) {
469 ret = append(ret, t)
470 }
471 }
472 return
473}
474
475func InstalledTemplates(keyword string) (templates []*bazaar.Template) {
476 templates = bazaar.InstalledTemplates()
477 templates = filterTemplates(templates, keyword)
478 return
479}
480
481func InstallBazaarTemplate(repoURL, repoHash, templateName string) error {
482 installPath := filepath.Join(util.DataDir, "templates", templateName)
483 err := bazaar.InstallTemplate(repoURL, repoHash, installPath, Conf.System.ID)
484 if err != nil {
485 return errors.New(fmt.Sprintf(Conf.Language(46), templateName, err))
486 }
487 return nil
488}
489
490func UninstallBazaarTemplate(templateName string) error {
491 installPath := filepath.Join(util.DataDir, "templates", templateName)
492 err := bazaar.UninstallTemplate(installPath)
493 if err != nil {
494 return errors.New(fmt.Sprintf(Conf.Language(47), err.Error()))
495 }
496 return nil
497}
498
499func matchPackage(keywords []string, pkg *bazaar.Package) bool {
500 if 1 > len(keywords) {
501 return true
502 }
503
504 if nil == pkg || nil == pkg.DisplayName || nil == pkg.Description {
505 return false
506 }
507
508 hits := map[string]bool{}
509 for _, keyword := range keywords {
510 if strings.Contains(strings.ToLower(pkg.DisplayName.Default), keyword) ||
511 strings.Contains(strings.ToLower(pkg.DisplayName.ZhCN), keyword) ||
512 strings.Contains(strings.ToLower(pkg.DisplayName.ZhCHT), keyword) ||
513 strings.Contains(strings.ToLower(pkg.DisplayName.EnUS), keyword) ||
514 strings.Contains(strings.ToLower(pkg.Description.Default), keyword) ||
515 strings.Contains(strings.ToLower(pkg.Description.ZhCN), keyword) ||
516 strings.Contains(strings.ToLower(pkg.Description.ZhCHT), keyword) ||
517 strings.Contains(strings.ToLower(pkg.Description.EnUS), keyword) ||
518 strings.Contains(strings.ToLower(path.Base(pkg.RepoURL)), keyword) ||
519 strings.Contains(strings.ToLower(pkg.Author), keyword) {
520 hits[keyword] = true
521 continue
522 }
523
524 for _, pkgKeyword := range pkg.Keywords {
525 if strings.Contains(strings.ToLower(pkgKeyword), keyword) {
526 hits[keyword] = true
527 break
528 }
529 }
530 }
531 return len(hits) == len(keywords)
532}
533
534func getSearchKeywords(query string) (ret []string) {
535 query = strings.TrimSpace(query)
536 if "" == query {
537 return
538 }
539
540 keywords := strings.Split(query, " ")
541 for _, k := range keywords {
542 if "" != k {
543 ret = append(ret, strings.ToLower(k))
544 }
545 }
546 return
547}