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 bazaar
18
19import (
20 "os"
21 "path/filepath"
22 "sort"
23 "strings"
24
25 "github.com/88250/go-humanize"
26 "github.com/siyuan-note/logging"
27 "github.com/siyuan-note/siyuan/kernel/util"
28)
29
30type Theme struct {
31 *Package
32
33 Modes []string `json:"modes"`
34}
35
36// Themes 返回集市主题列表
37func Themes() (ret []*Theme) {
38 ret = []*Theme{}
39 result := getStageAndBazaar("themes")
40
41 if !result.Online {
42 return
43 }
44 if result.StageErr != nil {
45 return
46 }
47 if 1 > len(result.BazaarIndex) {
48 return
49 }
50
51 for _, repo := range result.StageIndex.Repos {
52 if nil == repo.Package {
53 continue
54 }
55 theme := buildThemeFromStageRepo(repo, result.BazaarIndex)
56 if nil != theme {
57 ret = append(ret, theme)
58 }
59 }
60
61 sort.Slice(ret, func(i, j int) bool { return ret[i].Updated > ret[j].Updated })
62 return
63}
64
65// buildThemeFromStageRepo 使用 stage 内嵌的 package 构建 *Theme,不发起 HTTP 请求。
66func buildThemeFromStageRepo(repo *StageRepo, bazaarIndex map[string]*bazaarPackage) *Theme {
67 pkg := *repo.Package
68 pkg.URL = strings.TrimSuffix(pkg.URL, "/")
69 repoURLHash := strings.Split(repo.URL, "@")
70 if 2 != len(repoURLHash) {
71 return nil
72 }
73 pkg.RepoURL = "https://github.com/" + repoURLHash[0]
74 pkg.RepoHash = repoURLHash[1]
75 pkg.PreviewURL = util.BazaarOSSServer + "/package/" + repo.URL + "/preview.png?imageslim"
76 pkg.PreviewURLThumb = util.BazaarOSSServer + "/package/" + repo.URL + "/preview.png?imageView2/2/w/436/h/232"
77 pkg.IconURL = util.BazaarOSSServer + "/package/" + repo.URL + "/icon.png"
78 pkg.Updated = repo.Updated
79 pkg.Stars = repo.Stars
80 pkg.OpenIssues = repo.OpenIssues
81 pkg.Size = repo.Size
82 pkg.HSize = humanize.BytesCustomCeil(uint64(pkg.Size), 2)
83 pkg.InstallSize = repo.InstallSize
84 pkg.HInstallSize = humanize.BytesCustomCeil(uint64(pkg.InstallSize), 2)
85 pkg.HUpdated = formatUpdated(pkg.Updated)
86 pkg.PreferredFunding = getPreferredFunding(pkg.Funding)
87 pkg.PreferredName = GetPreferredName(&pkg)
88 pkg.PreferredDesc = getPreferredDesc(pkg.Description)
89 pkg.DisallowInstall = disallowInstallBazaarPackage(&pkg)
90 pkg.DisallowUpdate = disallowInstallBazaarPackage(&pkg)
91 pkg.UpdateRequiredMinAppVer = pkg.MinAppVersion
92 if bp := bazaarIndex[repoURLHash[0]]; nil != bp {
93 pkg.Downloads = bp.Downloads
94 }
95 packageInstallSizeCache.SetDefault(pkg.RepoURL, pkg.InstallSize)
96 theme := &Theme{Package: &pkg, Modes: []string{}}
97 return theme
98}
99
100func InstalledThemes() (ret []*Theme) {
101 ret = []*Theme{}
102
103 if !util.IsPathRegularDirOrSymlinkDir(util.ThemesPath) {
104 return
105 }
106
107 themeDirs, err := os.ReadDir(util.ThemesPath)
108 if err != nil {
109 logging.LogWarnf("read appearance themes folder failed: %s", err)
110 return
111 }
112
113 bazaarThemes := Themes()
114
115 for _, themeDir := range themeDirs {
116 if !util.IsDirRegularOrSymlink(themeDir) {
117 continue
118 }
119 dirName := themeDir.Name()
120 if isBuiltInTheme(dirName) {
121 continue
122 }
123
124 theme, parseErr := ThemeJSON(dirName)
125 if nil != parseErr || nil == theme {
126 continue
127 }
128
129 theme.RepoURL = theme.URL
130 theme.DisallowInstall = disallowInstallBazaarPackage(theme.Package)
131 if bazaarPkg := getBazaarTheme(theme.Name, bazaarThemes); nil != bazaarPkg {
132 theme.DisallowUpdate = disallowInstallBazaarPackage(bazaarPkg.Package)
133 theme.UpdateRequiredMinAppVer = bazaarPkg.MinAppVersion
134 theme.RepoURL = bazaarPkg.RepoURL
135 }
136
137 installPath := filepath.Join(util.ThemesPath, dirName)
138 theme.Installed = true
139 theme.PreviewURL = "/appearance/themes/" + dirName + "/preview.png"
140 theme.PreviewURLThumb = "/appearance/themes/" + dirName + "/preview.png"
141 theme.IconURL = "/appearance/themes/" + dirName + "/icon.png"
142 theme.PreferredFunding = getPreferredFunding(theme.Funding)
143 theme.PreferredName = GetPreferredName(theme.Package)
144 theme.PreferredDesc = getPreferredDesc(theme.Description)
145 info, statErr := os.Stat(filepath.Join(installPath, "theme.json"))
146 if nil != statErr {
147 logging.LogWarnf("stat install theme.json failed: %s", statErr)
148 continue
149 }
150 theme.HInstallDate = info.ModTime().Format("2006-01-02")
151 if installSize, ok := packageInstallSizeCache.Get(theme.RepoURL); ok {
152 theme.InstallSize = installSize.(int64)
153 } else {
154 is, _ := util.SizeOfDirectory(installPath)
155 theme.InstallSize = is
156 packageInstallSizeCache.SetDefault(theme.RepoURL, is)
157 }
158 theme.HInstallSize = humanize.BytesCustomCeil(uint64(theme.InstallSize), 2)
159 theme.PreferredReadme = loadInstalledReadme(installPath, "/appearance/themes/"+dirName+"/", theme.Readme)
160 theme.Outdated = isOutdatedTheme(theme, bazaarThemes)
161 ret = append(ret, theme)
162 }
163 return
164}
165
166func isBuiltInTheme(dirName string) bool {
167 return "daylight" == dirName || "midnight" == dirName
168}
169
170func getBazaarTheme(name string, themes []*Theme) *Theme {
171 for _, p := range themes {
172 if p.Name == name {
173 return p
174 }
175 }
176 return nil
177}
178
179func InstallTheme(repoURL, repoHash, installPath string, systemID string) error {
180 repoURLHash := repoURL + "@" + repoHash
181 data, err := downloadPackage(repoURLHash, true, systemID)
182 if err != nil {
183 return err
184 }
185 return installPackage(data, installPath, repoURLHash)
186}
187
188func UninstallTheme(installPath string) error {
189 return uninstallPackage(installPath)
190}