A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 547 lines 16 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 "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}