template.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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 bazaar
  17. import (
  18. "errors"
  19. "os"
  20. "path/filepath"
  21. "sort"
  22. "strings"
  23. "sync"
  24. "time"
  25. "github.com/dustin/go-humanize"
  26. "github.com/panjf2000/ants/v2"
  27. "github.com/siyuan-note/filelock"
  28. "github.com/siyuan-note/httpclient"
  29. "github.com/siyuan-note/logging"
  30. "github.com/siyuan-note/siyuan/kernel/util"
  31. )
  32. type Template struct {
  33. *Package
  34. }
  35. func Templates() (templates []*Template) {
  36. templates = []*Template{}
  37. stageIndex, err := getStageIndex("templates")
  38. if nil != err {
  39. return
  40. }
  41. bazaarIndex := getBazaarIndex()
  42. waitGroup := &sync.WaitGroup{}
  43. lock := &sync.Mutex{}
  44. p, _ := ants.NewPoolWithFunc(2, func(arg interface{}) {
  45. defer waitGroup.Done()
  46. repo := arg.(*StageRepo)
  47. repoURL := repo.URL
  48. template := &Template{}
  49. innerU := util.BazaarOSSServer + "/package/" + repoURL + "/template.json"
  50. innerResp, innerErr := httpclient.NewBrowserRequest().SetSuccessResult(template).Get(innerU)
  51. if nil != innerErr {
  52. logging.LogErrorf("get community template [%s] failed: %s", repoURL, innerErr)
  53. return
  54. }
  55. if 200 != innerResp.StatusCode {
  56. logging.LogErrorf("get bazaar package [%s] failed: %d", innerU, innerResp.StatusCode)
  57. return
  58. }
  59. if disallowDisplayBazaarPackage(template.Package) {
  60. return
  61. }
  62. template.URL = strings.TrimSuffix(template.URL, "/")
  63. repoURLHash := strings.Split(repoURL, "@")
  64. template.RepoURL = "https://github.com/" + repoURLHash[0]
  65. template.RepoHash = repoURLHash[1]
  66. template.PreviewURL = util.BazaarOSSServer + "/package/" + repoURL + "/preview.png?imageslim"
  67. template.PreviewURLThumb = util.BazaarOSSServer + "/package/" + repoURL + "/preview.png?imageView2/2/w/436/h/232"
  68. template.IconURL = util.BazaarOSSServer + "/package/" + repoURL + "/icon.png"
  69. template.Funding = repo.Package.Funding
  70. template.PreferredFunding = getPreferredFunding(template.Funding)
  71. template.PreferredName = GetPreferredName(template.Package)
  72. template.PreferredDesc = getPreferredDesc(template.Description)
  73. template.Updated = repo.Updated
  74. template.Stars = repo.Stars
  75. template.OpenIssues = repo.OpenIssues
  76. template.Size = repo.Size
  77. template.HSize = humanize.Bytes(uint64(template.Size))
  78. template.HUpdated = formatUpdated(template.Updated)
  79. pkg := bazaarIndex[strings.Split(repoURL, "@")[0]]
  80. if nil != pkg {
  81. template.Downloads = pkg.Downloads
  82. }
  83. lock.Lock()
  84. templates = append(templates, template)
  85. lock.Unlock()
  86. })
  87. for _, repo := range stageIndex.Repos {
  88. waitGroup.Add(1)
  89. p.Invoke(repo)
  90. }
  91. waitGroup.Wait()
  92. p.Release()
  93. templates = filterLegacyTemplates(templates)
  94. sort.Slice(templates, func(i, j int) bool { return templates[i].Updated > templates[j].Updated })
  95. return
  96. }
  97. func InstalledTemplates() (ret []*Template) {
  98. ret = []*Template{}
  99. templatesPath := filepath.Join(util.DataDir, "templates")
  100. if !util.IsPathRegularDirOrSymlinkDir(templatesPath) {
  101. return
  102. }
  103. templateDirs, err := os.ReadDir(templatesPath)
  104. if nil != err {
  105. logging.LogWarnf("read templates folder failed: %s", err)
  106. return
  107. }
  108. bazaarTemplates := Templates()
  109. for _, templateDir := range templateDirs {
  110. if !util.IsDirRegularOrSymlink(templateDir) {
  111. continue
  112. }
  113. dirName := templateDir.Name()
  114. template, parseErr := TemplateJSON(dirName)
  115. if nil != parseErr || nil == template {
  116. continue
  117. }
  118. installPath := filepath.Join(util.DataDir, "templates", dirName)
  119. template.Installed = true
  120. template.RepoURL = template.URL
  121. template.PreviewURL = "/templates/" + dirName + "/preview.png"
  122. template.PreviewURLThumb = "/templates/" + dirName + "/preview.png"
  123. template.IconURL = "/templates/" + dirName + "/icon.png"
  124. template.PreferredFunding = getPreferredFunding(template.Funding)
  125. template.PreferredName = GetPreferredName(template.Package)
  126. template.PreferredDesc = getPreferredDesc(template.Description)
  127. info, statErr := os.Stat(filepath.Join(installPath, "README.md"))
  128. if nil != statErr {
  129. logging.LogWarnf("stat install theme README.md failed: %s", statErr)
  130. continue
  131. }
  132. template.HInstallDate = info.ModTime().Format("2006-01-02")
  133. installSize, _ := util.SizeOfDirectory(installPath)
  134. template.InstallSize = installSize
  135. template.HInstallSize = humanize.Bytes(uint64(installSize))
  136. readmeFilename := getPreferredReadme(template.Readme)
  137. readme, readErr := os.ReadFile(filepath.Join(installPath, readmeFilename))
  138. if nil != readErr {
  139. logging.LogWarnf("read installed README.md failed: %s", readErr)
  140. continue
  141. }
  142. template.PreferredReadme, _ = renderREADME(template.URL, readme)
  143. template.Outdated = isOutdatedTemplate(template, bazaarTemplates)
  144. ret = append(ret, template)
  145. }
  146. return
  147. }
  148. func InstallTemplate(repoURL, repoHash, installPath string, systemID string) error {
  149. repoURLHash := repoURL + "@" + repoHash
  150. data, err := downloadPackage(repoURLHash, true, systemID)
  151. if nil != err {
  152. return err
  153. }
  154. return installPackage(data, installPath)
  155. }
  156. func UninstallTemplate(installPath string) error {
  157. if err := filelock.Remove(installPath); nil != err {
  158. logging.LogErrorf("remove template [%s] failed: %s", installPath, err)
  159. return errors.New("remove community template failed")
  160. }
  161. return nil
  162. }
  163. func filterLegacyTemplates(templates []*Template) (ret []*Template) {
  164. verTime, _ := time.Parse("2006-01-02T15:04:05", "2021-05-12T00:00:00")
  165. for _, theme := range templates {
  166. if "" != theme.Updated {
  167. updated := theme.Updated[:len("2006-01-02T15:04:05")]
  168. t, err := time.Parse("2006-01-02T15:04:05", updated)
  169. if nil != err {
  170. logging.LogErrorf("convert update time [%s] failed: %s", updated, err)
  171. continue
  172. }
  173. if t.After(verTime) {
  174. ret = append(ret, theme)
  175. }
  176. }
  177. }
  178. return
  179. }