package.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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. "bytes"
  19. "errors"
  20. "os"
  21. "path/filepath"
  22. "strings"
  23. "sync"
  24. "time"
  25. "github.com/88250/gulu"
  26. "github.com/88250/lute"
  27. "github.com/araddon/dateparse"
  28. "github.com/imroc/req/v3"
  29. "github.com/siyuan-note/filelock"
  30. "github.com/siyuan-note/httpclient"
  31. "github.com/siyuan-note/logging"
  32. "github.com/siyuan-note/siyuan/kernel/util"
  33. "golang.org/x/mod/semver"
  34. textUnicode "golang.org/x/text/encoding/unicode"
  35. "golang.org/x/text/transform"
  36. )
  37. type DisplayName struct {
  38. Default string `json:"default"`
  39. ZhCN string `json:"zh_CN"`
  40. EnUS string `json:"en_US"`
  41. ZhCHT string `json:"zh_CHT"`
  42. }
  43. type Description struct {
  44. Default string `json:"default"`
  45. ZhCN string `json:"zh_CN"`
  46. EnUS string `json:"en_US"`
  47. ZhCHT string `json:"zh_CHT"`
  48. }
  49. type Readme struct {
  50. Default string `json:"default"`
  51. ZhCN string `json:"zh_CN"`
  52. EnUS string `json:"en_US"`
  53. ZhCHT string `json:"zh_CHT"`
  54. }
  55. type Funding struct {
  56. OpenCollective string `json:"openCollective"`
  57. Patreon string `json:"patreon"`
  58. GitHub string `json:"github"`
  59. Custom []string `json:"custom"`
  60. }
  61. type Package struct {
  62. Author string `json:"author"`
  63. URL string `json:"url"`
  64. Version string `json:"version"`
  65. MinAppVersion string `json:"minAppVersion"`
  66. Backends []string `json:"backends"`
  67. Frontends []string `json:"frontends"`
  68. DisplayName *DisplayName `json:"displayName"`
  69. Description *Description `json:"description"`
  70. Readme *Readme `json:"readme"`
  71. Funding *Funding `json:"funding"`
  72. Keywords []string `json:"keywords"`
  73. PreferredFunding string `json:"preferredFunding"`
  74. PreferredName string `json:"preferredName"`
  75. PreferredDesc string `json:"preferredDesc"`
  76. PreferredReadme string `json:"preferredReadme"`
  77. Name string `json:"name"`
  78. RepoURL string `json:"repoURL"`
  79. RepoHash string `json:"repoHash"`
  80. PreviewURL string `json:"previewURL"`
  81. PreviewURLThumb string `json:"previewURLThumb"`
  82. IconURL string `json:"iconURL"`
  83. Installed bool `json:"installed"`
  84. Outdated bool `json:"outdated"`
  85. Current bool `json:"current"`
  86. Updated string `json:"updated"`
  87. Stars int `json:"stars"`
  88. OpenIssues int `json:"openIssues"`
  89. Size int64 `json:"size"`
  90. HSize string `json:"hSize"`
  91. InstallSize int64 `json:"installSize"`
  92. HInstallSize string `json:"hInstallSize"`
  93. HInstallDate string `json:"hInstallDate"`
  94. HUpdated string `json:"hUpdated"`
  95. Downloads int `json:"downloads"`
  96. Incompatible bool `json:"incompatible"`
  97. }
  98. type StagePackage struct {
  99. Author string `json:"author"`
  100. URL string `json:"url"`
  101. Version string `json:"version"`
  102. Description *Description `json:"description"`
  103. Readme *Readme `json:"readme"`
  104. I18N []string `json:"i18n"`
  105. Funding *Funding `json:"funding"`
  106. }
  107. type StageRepo struct {
  108. URL string `json:"url"`
  109. Updated string `json:"updated"`
  110. Stars int `json:"stars"`
  111. OpenIssues int `json:"openIssues"`
  112. Size int64 `json:"size"`
  113. Package *StagePackage `json:"package"`
  114. }
  115. type StageIndex struct {
  116. Repos []*StageRepo `json:"repos"`
  117. }
  118. func getPreferredReadme(readme *Readme) string {
  119. if nil == readme {
  120. return "README.md"
  121. }
  122. ret := readme.Default
  123. switch util.Lang {
  124. case "zh_CN":
  125. if "" != readme.ZhCN {
  126. ret = readme.ZhCN
  127. }
  128. case "zh_CHT":
  129. if "" != readme.ZhCHT {
  130. ret = readme.ZhCHT
  131. } else if "" != readme.ZhCN {
  132. ret = readme.ZhCN
  133. }
  134. case "en_US":
  135. if "" != readme.EnUS {
  136. ret = readme.EnUS
  137. }
  138. default:
  139. if "" != readme.EnUS {
  140. ret = readme.EnUS
  141. }
  142. }
  143. return ret
  144. }
  145. func GetPreferredName(pkg *Package) string {
  146. if nil == pkg.DisplayName {
  147. return pkg.Name
  148. }
  149. ret := pkg.DisplayName.Default
  150. switch util.Lang {
  151. case "zh_CN":
  152. if "" != pkg.DisplayName.ZhCN {
  153. ret = pkg.DisplayName.ZhCN
  154. }
  155. case "zh_CHT":
  156. if "" != pkg.DisplayName.ZhCHT {
  157. ret = pkg.DisplayName.ZhCHT
  158. } else if "" != pkg.DisplayName.ZhCN {
  159. ret = pkg.DisplayName.ZhCN
  160. }
  161. case "en_US":
  162. if "" != pkg.DisplayName.EnUS {
  163. ret = pkg.DisplayName.EnUS
  164. }
  165. default:
  166. if "" != pkg.DisplayName.EnUS {
  167. ret = pkg.DisplayName.EnUS
  168. }
  169. }
  170. return ret
  171. }
  172. func getPreferredDesc(desc *Description) string {
  173. if nil == desc {
  174. return ""
  175. }
  176. ret := desc.Default
  177. switch util.Lang {
  178. case "zh_CN":
  179. if "" != desc.ZhCN {
  180. ret = desc.ZhCN
  181. }
  182. case "zh_CHT":
  183. if "" != desc.ZhCHT {
  184. ret = desc.ZhCHT
  185. } else if "" != desc.ZhCN {
  186. ret = desc.ZhCN
  187. }
  188. case "en_US":
  189. if "" != desc.EnUS {
  190. ret = desc.EnUS
  191. }
  192. default:
  193. if "" != desc.EnUS {
  194. ret = desc.EnUS
  195. }
  196. }
  197. return ret
  198. }
  199. func getPreferredFunding(funding *Funding) string {
  200. if nil == funding {
  201. return ""
  202. }
  203. if "" != funding.OpenCollective {
  204. return "https://opencollective.com/" + funding.OpenCollective
  205. }
  206. if "" != funding.Patreon {
  207. return "https://www.patreon.com/" + funding.Patreon
  208. }
  209. if "" != funding.GitHub {
  210. return "https://github.com/sponsors/" + funding.GitHub
  211. }
  212. if 0 < len(funding.Custom) {
  213. return funding.Custom[0]
  214. }
  215. return ""
  216. }
  217. func PluginJSON(pluginDirName string) (ret *Plugin, err error) {
  218. p := filepath.Join(util.DataDir, "plugins", pluginDirName, "plugin.json")
  219. if !filelock.IsExist(p) {
  220. err = os.ErrNotExist
  221. return
  222. }
  223. data, err := filelock.ReadFile(p)
  224. if nil != err {
  225. logging.LogErrorf("read plugin.json [%s] failed: %s", p, err)
  226. return
  227. }
  228. if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err {
  229. logging.LogErrorf("parse plugin.json [%s] failed: %s", p, err)
  230. return
  231. }
  232. ret.URL = strings.TrimSuffix(ret.URL, "/")
  233. return
  234. }
  235. func WidgetJSON(widgetDirName string) (ret *Widget, err error) {
  236. p := filepath.Join(util.DataDir, "widgets", widgetDirName, "widget.json")
  237. if !filelock.IsExist(p) {
  238. err = os.ErrNotExist
  239. return
  240. }
  241. data, err := filelock.ReadFile(p)
  242. if nil != err {
  243. logging.LogErrorf("read widget.json [%s] failed: %s", p, err)
  244. return
  245. }
  246. if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err {
  247. logging.LogErrorf("parse widget.json [%s] failed: %s", p, err)
  248. return
  249. }
  250. ret.URL = strings.TrimSuffix(ret.URL, "/")
  251. return
  252. }
  253. func IconJSON(iconDirName string) (ret *Icon, err error) {
  254. p := filepath.Join(util.IconsPath, iconDirName, "icon.json")
  255. if !gulu.File.IsExist(p) {
  256. err = os.ErrNotExist
  257. return
  258. }
  259. data, err := os.ReadFile(p)
  260. if nil != err {
  261. logging.LogErrorf("read icon.json [%s] failed: %s", p, err)
  262. return
  263. }
  264. if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err {
  265. logging.LogErrorf("parse icon.json [%s] failed: %s", p, err)
  266. return
  267. }
  268. ret.URL = strings.TrimSuffix(ret.URL, "/")
  269. return
  270. }
  271. func TemplateJSON(templateDirName string) (ret *Template, err error) {
  272. p := filepath.Join(util.DataDir, "templates", templateDirName, "template.json")
  273. if !filelock.IsExist(p) {
  274. err = os.ErrNotExist
  275. return
  276. }
  277. data, err := filelock.ReadFile(p)
  278. if nil != err {
  279. logging.LogErrorf("read template.json [%s] failed: %s", p, err)
  280. return
  281. }
  282. if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err {
  283. logging.LogErrorf("parse template.json [%s] failed: %s", p, err)
  284. return
  285. }
  286. ret.URL = strings.TrimSuffix(ret.URL, "/")
  287. return
  288. }
  289. func ThemeJSON(themeDirName string) (ret *Theme, err error) {
  290. p := filepath.Join(util.ThemesPath, themeDirName, "theme.json")
  291. if !gulu.File.IsExist(p) {
  292. err = os.ErrNotExist
  293. return
  294. }
  295. data, err := os.ReadFile(p)
  296. if nil != err {
  297. logging.LogErrorf("read theme.json [%s] failed: %s", p, err)
  298. return
  299. }
  300. ret = &Theme{}
  301. if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err {
  302. logging.LogErrorf("parse theme.json [%s] failed: %s", p, err)
  303. return
  304. }
  305. ret.URL = strings.TrimSuffix(ret.URL, "/")
  306. return
  307. }
  308. var cachedStageIndex = map[string]*StageIndex{}
  309. var stageIndexCacheTime int64
  310. var stageIndexLock = sync.Mutex{}
  311. func getStageIndex(pkgType string) (ret *StageIndex, err error) {
  312. rhyRet, err := util.GetRhyResult(false)
  313. if nil != err {
  314. return
  315. }
  316. stageIndexLock.Lock()
  317. defer stageIndexLock.Unlock()
  318. now := time.Now().Unix()
  319. if 3600 >= now-stageIndexCacheTime && nil != cachedStageIndex[pkgType] {
  320. ret = cachedStageIndex[pkgType]
  321. return
  322. }
  323. bazaarHash := rhyRet["bazaar"].(string)
  324. ret = &StageIndex{}
  325. request := httpclient.NewBrowserRequest()
  326. u := util.BazaarOSSServer + "/bazaar@" + bazaarHash + "/stage/" + pkgType + ".json"
  327. resp, reqErr := request.SetSuccessResult(ret).Get(u)
  328. if nil != reqErr {
  329. logging.LogErrorf("get community stage index [%s] failed: %s", u, reqErr)
  330. return
  331. }
  332. if 200 != resp.StatusCode {
  333. logging.LogErrorf("get community stage index [%s] failed: %d", u, resp.StatusCode)
  334. return
  335. }
  336. stageIndexCacheTime = now
  337. cachedStageIndex[pkgType] = ret
  338. return
  339. }
  340. func isOutdatedTheme(theme *Theme, bazaarThemes []*Theme) bool {
  341. if !strings.HasPrefix(theme.URL, "https://github.com/") {
  342. return false
  343. }
  344. repo := strings.TrimPrefix(theme.URL, "https://github.com/")
  345. parts := strings.Split(repo, "/")
  346. if 2 != len(parts) || "" == strings.TrimSpace(parts[1]) {
  347. return false
  348. }
  349. for _, pkg := range bazaarThemes {
  350. if theme.URL == pkg.URL && theme.Name == pkg.Name && theme.Author == pkg.Author && 0 > semver.Compare("v"+theme.Version, "v"+pkg.Version) {
  351. theme.RepoHash = pkg.RepoHash
  352. return true
  353. }
  354. }
  355. return false
  356. }
  357. func isOutdatedIcon(icon *Icon, bazaarIcons []*Icon) bool {
  358. if !strings.HasPrefix(icon.URL, "https://github.com/") {
  359. return false
  360. }
  361. repo := strings.TrimPrefix(icon.URL, "https://github.com/")
  362. parts := strings.Split(repo, "/")
  363. if 2 != len(parts) || "" == strings.TrimSpace(parts[1]) {
  364. return false
  365. }
  366. for _, pkg := range bazaarIcons {
  367. if icon.URL == pkg.URL && icon.Name == pkg.Name && icon.Author == pkg.Author && 0 > semver.Compare("v"+icon.Version, "v"+pkg.Version) {
  368. icon.RepoHash = pkg.RepoHash
  369. return true
  370. }
  371. }
  372. return false
  373. }
  374. func isOutdatedPlugin(plugin *Plugin, bazaarPlugins []*Plugin) bool {
  375. if !strings.HasPrefix(plugin.URL, "https://github.com/") {
  376. return false
  377. }
  378. repo := strings.TrimPrefix(plugin.URL, "https://github.com/")
  379. parts := strings.Split(repo, "/")
  380. if 2 != len(parts) || "" == strings.TrimSpace(parts[1]) {
  381. return false
  382. }
  383. for _, pkg := range bazaarPlugins {
  384. if plugin.URL == pkg.URL && plugin.Name == pkg.Name && plugin.Author == pkg.Author && 0 > semver.Compare("v"+plugin.Version, "v"+pkg.Version) {
  385. plugin.RepoHash = pkg.RepoHash
  386. return true
  387. }
  388. }
  389. return false
  390. }
  391. func isOutdatedWidget(widget *Widget, bazaarWidgets []*Widget) bool {
  392. if !strings.HasPrefix(widget.URL, "https://github.com/") {
  393. return false
  394. }
  395. repo := strings.TrimPrefix(widget.URL, "https://github.com/")
  396. parts := strings.Split(repo, "/")
  397. if 2 != len(parts) || "" == strings.TrimSpace(parts[1]) {
  398. return false
  399. }
  400. for _, pkg := range bazaarWidgets {
  401. if widget.URL == pkg.URL && widget.Name == pkg.Name && widget.Author == pkg.Author && 0 > semver.Compare("v"+widget.Version, "v"+pkg.Version) {
  402. widget.RepoHash = pkg.RepoHash
  403. return true
  404. }
  405. }
  406. return false
  407. }
  408. func isOutdatedTemplate(template *Template, bazaarTemplates []*Template) bool {
  409. if !strings.HasPrefix(template.URL, "https://github.com/") {
  410. return false
  411. }
  412. repo := strings.TrimPrefix(template.URL, "https://github.com/")
  413. parts := strings.Split(repo, "/")
  414. if 2 != len(parts) || "" == strings.TrimSpace(parts[1]) {
  415. return false
  416. }
  417. for _, pkg := range bazaarTemplates {
  418. if template.URL == pkg.URL && template.Name == pkg.Name && template.Author == pkg.Author && 0 > semver.Compare("v"+template.Version, "v"+pkg.Version) {
  419. template.RepoHash = pkg.RepoHash
  420. return true
  421. }
  422. }
  423. return false
  424. }
  425. func GetPackageREADME(repoURL, repoHash, packageType string) (ret string) {
  426. repoURLHash := repoURL + "@" + repoHash
  427. stageIndex := cachedStageIndex[packageType]
  428. if nil == stageIndex {
  429. return
  430. }
  431. url := strings.TrimPrefix(repoURLHash, "https://github.com/")
  432. var repo *StageRepo
  433. for _, r := range stageIndex.Repos {
  434. if r.URL == url {
  435. repo = r
  436. break
  437. }
  438. }
  439. if nil == repo {
  440. return
  441. }
  442. readme := getPreferredReadme(repo.Package.Readme)
  443. data, err := downloadPackage(repoURLHash+"/"+readme, false, "")
  444. if nil != err {
  445. ret = "Load bazaar package's README.md failed: " + err.Error()
  446. return
  447. }
  448. if 2 < len(data) {
  449. if 255 == data[0] && 254 == data[1] {
  450. data, _, err = transform.Bytes(textUnicode.UTF16(textUnicode.LittleEndian, textUnicode.ExpectBOM).NewDecoder(), data)
  451. } else if 254 == data[1] && 255 == data[0] {
  452. data, _, err = transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.ExpectBOM).NewDecoder(), data)
  453. }
  454. }
  455. ret, err = renderREADME(repoURL, data)
  456. return
  457. }
  458. func renderREADME(repoURL string, mdData []byte) (ret string, err error) {
  459. luteEngine := lute.New()
  460. luteEngine.SetSoftBreak2HardBreak(false)
  461. luteEngine.SetCodeSyntaxHighlight(false)
  462. linkBase := "https://cdn.jsdelivr.net/gh/" + strings.TrimPrefix(repoURL, "https://github.com/")
  463. luteEngine.SetLinkBase(linkBase)
  464. ret = luteEngine.Md2HTML(string(mdData))
  465. ret = util.LinkTarget(ret, linkBase)
  466. return
  467. }
  468. var (
  469. packageLocks = map[string]*sync.Mutex{}
  470. packageLocksLock = sync.Mutex{}
  471. )
  472. func downloadPackage(repoURLHash string, pushProgress bool, systemID string) (data []byte, err error) {
  473. packageLocksLock.Lock()
  474. defer packageLocksLock.Unlock()
  475. // repoURLHash: https://github.com/88250/Comfortably-Numb@6286912c381ef3f83e455d06ba4d369c498238dc
  476. repoURL := repoURLHash[:strings.LastIndex(repoURLHash, "@")]
  477. lock, ok := packageLocks[repoURLHash]
  478. if !ok {
  479. lock = &sync.Mutex{}
  480. packageLocks[repoURLHash] = lock
  481. }
  482. lock.Lock()
  483. defer lock.Unlock()
  484. repoURLHash = strings.TrimPrefix(repoURLHash, "https://github.com/")
  485. u := util.BazaarOSSServer + "/package/" + repoURLHash
  486. buf := &bytes.Buffer{}
  487. resp, err := httpclient.NewBrowserRequest().SetOutput(buf).SetDownloadCallback(func(info req.DownloadInfo) {
  488. if pushProgress {
  489. progress := float32(info.DownloadedSize) / float32(info.Response.ContentLength)
  490. //logging.LogDebugf("downloading bazaar package [%f]", progress)
  491. util.PushDownloadProgress(repoURL, progress)
  492. }
  493. }).Get(u)
  494. if nil != err {
  495. logging.LogErrorf("get bazaar package [%s] failed: %s", u, err)
  496. return nil, errors.New("get bazaar package failed, please check your network")
  497. }
  498. if 200 != resp.StatusCode {
  499. logging.LogErrorf("get bazaar package [%s] failed: %d", u, resp.StatusCode)
  500. return nil, errors.New("get bazaar package failed: " + resp.Status)
  501. }
  502. data = buf.Bytes()
  503. go incPackageDownloads(repoURLHash, systemID)
  504. return
  505. }
  506. func incPackageDownloads(repoURLHash, systemID string) {
  507. if strings.Contains(repoURLHash, ".md") || "" == systemID {
  508. return
  509. }
  510. repo := strings.Split(repoURLHash, "@")[0]
  511. u := util.GetCloudServer() + "/apis/siyuan/bazaar/addBazaarPackageDownloadCount"
  512. httpclient.NewCloudRequest30s().SetBody(
  513. map[string]interface{}{
  514. "systemID": systemID,
  515. "repo": repo,
  516. }).Post(u)
  517. }
  518. func installPackage(data []byte, installPath string) (err error) {
  519. tmpPackage := filepath.Join(util.TempDir, "bazaar", "package")
  520. if err = os.MkdirAll(tmpPackage, 0755); nil != err {
  521. return
  522. }
  523. name := gulu.Rand.String(7)
  524. tmp := filepath.Join(tmpPackage, name+".zip")
  525. if err = os.WriteFile(tmp, data, 0644); nil != err {
  526. return
  527. }
  528. unzipPath := filepath.Join(tmpPackage, name)
  529. if err = gulu.Zip.Unzip(tmp, unzipPath); nil != err {
  530. logging.LogErrorf("write file [%s] failed: %s", installPath, err)
  531. return
  532. }
  533. dirs, err := os.ReadDir(unzipPath)
  534. if nil != err {
  535. return
  536. }
  537. srcPath := unzipPath
  538. if 1 == len(dirs) && dirs[0].IsDir() {
  539. srcPath = filepath.Join(unzipPath, dirs[0].Name())
  540. }
  541. if err = filelock.Copy(srcPath, installPath); nil != err {
  542. return
  543. }
  544. return
  545. }
  546. func formatUpdated(updated string) (ret string) {
  547. t, e := dateparse.ParseIn(updated, time.Now().Location())
  548. if nil == e {
  549. ret = t.Format("2006-01-02")
  550. } else {
  551. if strings.Contains(updated, "T") {
  552. ret = updated[:strings.Index(updated, "T")]
  553. } else {
  554. ret = strings.ReplaceAll(strings.ReplaceAll(updated, "T", ""), "Z", "")
  555. }
  556. }
  557. return
  558. }
  559. type bazaarPackage struct {
  560. Name string `json:"name"`
  561. Downloads int `json:"downloads"`
  562. }
  563. var cachedBazaarIndex = map[string]*bazaarPackage{}
  564. var bazaarIndexCacheTime int64
  565. var bazaarIndexLock = sync.Mutex{}
  566. func getBazaarIndex() map[string]*bazaarPackage {
  567. bazaarIndexLock.Lock()
  568. defer bazaarIndexLock.Unlock()
  569. now := time.Now().Unix()
  570. if 3600 >= now-bazaarIndexCacheTime {
  571. return cachedBazaarIndex
  572. }
  573. request := httpclient.NewBrowserRequest()
  574. u := util.BazaarStatServer + "/bazaar/index.json"
  575. resp, reqErr := request.SetSuccessResult(&cachedBazaarIndex).Get(u)
  576. if nil != reqErr {
  577. logging.LogErrorf("get bazaar index [%s] failed: %s", u, reqErr)
  578. return cachedBazaarIndex
  579. }
  580. if 200 != resp.StatusCode {
  581. logging.LogErrorf("get bazaar index [%s] failed: %d", u, resp.StatusCode)
  582. return cachedBazaarIndex
  583. }
  584. bazaarIndexCacheTime = now
  585. return cachedBazaarIndex
  586. }
  587. // defaultMinAppVersion 如果集市包中缺失 minAppVersion 项,则使用该值作为最低支持的版本号,小于该版本号时不显示集市包
  588. // Add marketplace package config item `minAppVersion` https://github.com/siyuan-note/siyuan/issues/8330
  589. const defaultMinAppVersion = "2.9.0"
  590. func disallowDisplayBazaarPackage(pkg *Package) bool {
  591. if "" == pkg.MinAppVersion { // TODO: 目前暂时放过所有不带 minAppVersion 的集市包,后续版本会使用 defaultMinAppVersion
  592. return false
  593. }
  594. if 0 < semver.Compare("v"+pkg.MinAppVersion, "v"+util.Ver) {
  595. return true
  596. }
  597. return false
  598. }