bazzar.go 15 KB

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