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 model
18
19import (
20 "bufio"
21 "crypto/sha256"
22 "fmt"
23 "io"
24 "os"
25 "os/exec"
26 "path"
27 "path/filepath"
28 "runtime"
29 "strconv"
30 "strings"
31 "sync"
32 "time"
33
34 "github.com/88250/gulu"
35 "github.com/imroc/req/v3"
36 "github.com/siyuan-note/logging"
37 "github.com/siyuan-note/siyuan/kernel/util"
38)
39
40func execNewVerInstallPkg(newVerInstallPkgPath string) {
41 logging.LogInfof("installing the new version [%s]", newVerInstallPkgPath)
42 var cmd *exec.Cmd
43 if gulu.OS.IsWindows() {
44 cmd = exec.Command(newVerInstallPkgPath)
45 } else if gulu.OS.IsDarwin() {
46 exec.Command("chmod", "+x", newVerInstallPkgPath).CombinedOutput()
47 cmd = exec.Command("open", newVerInstallPkgPath)
48 }
49 gulu.CmdAttr(cmd)
50 cmdErr := cmd.Run()
51 if nil != cmdErr {
52 logging.LogErrorf("exec install new version failed: %s", cmdErr)
53 return
54 }
55}
56
57var newVerInstallPkgPath string
58
59func getNewVerInstallPkgPath() string {
60 if skipNewVerInstallPkg() {
61 newVerInstallPkgPath = ""
62 return ""
63 }
64
65 downloadPkgURLs, checksum, err := getUpdatePkg()
66 if err != nil || 1 > len(downloadPkgURLs) || "" == checksum {
67 newVerInstallPkgPath = ""
68 return ""
69 }
70
71 pkg := path.Base(downloadPkgURLs[0])
72 newVerInstallPkgPath = filepath.Join(util.TempDir, "install", pkg)
73 localChecksum, _ := sha256Hash(newVerInstallPkgPath)
74 if checksum != localChecksum {
75 newVerInstallPkgPath = ""
76 return ""
77 }
78 return newVerInstallPkgPath
79}
80
81var checkDownloadInstallPkgLock = sync.Mutex{}
82
83func checkDownloadInstallPkg() {
84 defer logging.Recover()
85
86 if skipNewVerInstallPkg() {
87 return
88 }
89
90 if !checkDownloadInstallPkgLock.TryLock() {
91 return
92 }
93 defer checkDownloadInstallPkgLock.Unlock()
94
95 downloadPkgURLs, checksum, err := getUpdatePkg()
96 if err != nil || 1 > len(downloadPkgURLs) || "" == checksum {
97 return
98 }
99
100 msgId := util.PushMsg(Conf.Language(103), 1000*7)
101 succ := false
102 for _, downloadPkgURL := range downloadPkgURLs {
103 err = downloadInstallPkg(downloadPkgURL, checksum)
104 if err == nil {
105 succ = true
106 break
107
108 }
109 }
110 if !succ {
111 util.PushUpdateMsg(msgId, Conf.Language(104), 7000)
112 }
113}
114
115func getUpdatePkg() (downloadPkgURLs []string, checksum string, err error) {
116 defer logging.Recover()
117 result, err := util.GetRhyResult(false)
118 if err != nil {
119 return
120 }
121
122 ver := result["ver"].(string)
123 if isVersionUpToDate(ver) {
124 return
125 }
126
127 var suffix string
128 if gulu.OS.IsWindows() {
129 if "arm64" == runtime.GOARCH {
130 suffix = "win-arm64.exe"
131 } else {
132 suffix = "win.exe"
133 }
134 } else if gulu.OS.IsDarwin() {
135 if "arm64" == runtime.GOARCH {
136 suffix = "mac-arm64.dmg"
137 } else {
138 suffix = "mac.dmg"
139 }
140 }
141 pkg := "siyuan-" + ver + "-" + suffix
142
143 b3logURL := "https://release.b3log.org/siyuan/" + pkg
144 liuyunURL := "https://release.liuyun.io/siyuan/" + pkg
145 githubURL := "https://github.com/siyuan-note/siyuan/releases/download/v" + ver + "/" + pkg
146 ghproxyURL := "https://ghfast.top/" + githubURL
147 if util.IsChinaCloud() {
148 downloadPkgURLs = append(downloadPkgURLs, b3logURL)
149 downloadPkgURLs = append(downloadPkgURLs, liuyunURL)
150 downloadPkgURLs = append(downloadPkgURLs, ghproxyURL)
151 downloadPkgURLs = append(downloadPkgURLs, githubURL)
152 } else {
153 downloadPkgURLs = append(downloadPkgURLs, b3logURL)
154 downloadPkgURLs = append(downloadPkgURLs, liuyunURL)
155 downloadPkgURLs = append(downloadPkgURLs, githubURL)
156 downloadPkgURLs = append(downloadPkgURLs, ghproxyURL)
157 }
158
159 checksums := result["checksums"].(map[string]interface{})
160 checksum = checksums[pkg].(string)
161 return
162}
163
164func downloadInstallPkg(pkgURL, checksum string) (err error) {
165 if "" == pkgURL || "" == checksum {
166 return
167 }
168
169 pkg := path.Base(pkgURL)
170 savePath := filepath.Join(util.TempDir, "install", pkg)
171 if gulu.File.IsExist(savePath) {
172 localChecksum, _ := sha256Hash(savePath)
173 if localChecksum == checksum {
174 return
175 }
176 }
177
178 err = os.MkdirAll(filepath.Join(util.TempDir, "install"), 0755)
179 if err != nil {
180 logging.LogErrorf("create temp install dir failed: %s", err)
181 return
182 }
183
184 logging.LogInfof("downloading install package [%s]", pkgURL)
185 client := req.C().SetTLSHandshakeTimeout(7 * time.Second).SetTimeout(10 * time.Minute).DisableInsecureSkipVerify()
186 callback := func(info req.DownloadInfo) {
187 progress := fmt.Sprintf("%.2f%%", float64(info.DownloadedSize)/float64(info.Response.ContentLength)*100.0)
188 // logging.LogDebugf("downloading install package [%s %s]", pkgURL, progress)
189 util.PushStatusBar(fmt.Sprintf(Conf.Language(133), progress))
190 }
191 _, err = client.R().SetOutputFile(savePath).SetDownloadCallbackWithInterval(callback, 1*time.Second).Get(pkgURL)
192 if err != nil {
193 logging.LogErrorf("download install package [%s] failed: %s", pkgURL, err)
194 return
195 }
196
197 localChecksum, _ := sha256Hash(savePath)
198 if checksum != localChecksum {
199 logging.LogErrorf("verify checksum failed, download install package [%s] checksum [%s] not equal to downloaded [%s] checksum [%s]", pkgURL, checksum, savePath, localChecksum)
200 return
201 }
202 logging.LogInfof("downloaded install package [%s] to [%s]", pkgURL, savePath)
203 util.PushStatusBar(Conf.Language(62))
204 return
205}
206
207func sha256Hash(filename string) (ret string, err error) {
208 file, err := os.Open(filename)
209 if err != nil {
210 return
211 }
212 defer file.Close()
213
214 hash := sha256.New()
215 reader := bufio.NewReader(file)
216 buf := make([]byte, 1024*1024*4)
217 for {
218 switch n, readErr := reader.Read(buf); readErr {
219 case nil:
220 hash.Write(buf[:n])
221 case io.EOF:
222 return fmt.Sprintf("%x", hash.Sum(nil)), nil
223 default:
224 return "", err
225 }
226 }
227}
228
229type Announcement struct {
230 Id string `json:"id"`
231 Title string `json:"title"`
232 URL string `json:"url"`
233 Region int `json:"region"`
234}
235
236func getAnnouncements() (ret []*Announcement) {
237 result, err := util.GetRhyResult(false)
238 if err != nil {
239 logging.LogErrorf("get announcement failed: %s", err)
240 return
241 }
242
243 if nil == result["announcement"] {
244 return
245 }
246
247 announcements := result["announcement"].([]interface{})
248 for _, announcement := range announcements {
249 ann := announcement.(map[string]interface{})
250 ret = append(ret, &Announcement{
251 Id: ann["id"].(string),
252 Title: ann["title"].(string),
253 URL: ann["url"].(string),
254 Region: int(ann["region"].(float64)),
255 })
256 }
257 return
258}
259
260func CheckUpdate(showMsg bool) {
261 if !showMsg {
262 return
263 }
264
265 if Conf.System.IsMicrosoftStore {
266 return
267 }
268
269 result, err := util.GetRhyResult(showMsg)
270 if err != nil {
271 return
272 }
273
274 ver := result["ver"].(string)
275 releaseLang := result["release"].(string)
276 if releaseLangArg := result["release_"+Conf.Lang]; nil != releaseLangArg {
277 releaseLang = releaseLangArg.(string)
278 }
279
280 var msg string
281 var timeout int
282 if isVersionUpToDate(ver) {
283 msg = Conf.Language(10)
284 timeout = 3000
285 } else {
286 msg = fmt.Sprintf(Conf.Language(9), "<a href=\""+releaseLang+"\">"+releaseLang+"</a>")
287 showMsg = true
288 timeout = 15000
289 }
290 if showMsg {
291 util.PushMsg(msg, timeout)
292 go func() {
293 defer logging.Recover()
294 checkDownloadInstallPkg()
295 if "" != getNewVerInstallPkgPath() {
296 util.PushMsg(Conf.Language(62), 15*1000)
297 }
298 }()
299 }
300}
301
302func isVersionUpToDate(releaseVer string) bool {
303 return ver2num(releaseVer) <= ver2num(util.Ver)
304}
305
306func skipNewVerInstallPkg() bool {
307 if !gulu.OS.IsWindows() && !gulu.OS.IsDarwin() {
308 return true
309 }
310 if util.ISMicrosoftStore || util.ContainerStd != util.Container {
311 return true
312 }
313 if !Conf.System.DownloadInstallPkg {
314 return true
315 }
316 if gulu.OS.IsWindows() {
317 plat := strings.ToLower(Conf.System.OSPlatform)
318 // Windows 7, 8 and Server 2012 are no longer supported https://github.com/siyuan-note/siyuan/issues/7347
319 if strings.Contains(plat, " 7 ") || strings.Contains(plat, " 8 ") || strings.Contains(plat, "2012") {
320 return true
321 }
322 }
323 return false
324}
325
326func ver2num(a string) int {
327 var version string
328 var suffixpos int
329 var suffixStr string
330 var suffix string
331 a = strings.Trim(a, " ")
332 if strings.Contains(a, "alpha") {
333 suffixpos = strings.Index(a, "-alpha")
334 version = a[0:suffixpos]
335 suffixStr = a[suffixpos+6 : len(a)]
336 suffix = "0" + fmt.Sprintf("%03s", suffixStr)
337 } else if strings.Contains(a, "beta") {
338 suffixpos = strings.Index(a, "-beta")
339 version = a[0:suffixpos]
340 suffixStr = a[suffixpos+5 : len(a)]
341 suffix = "1" + fmt.Sprintf("%03s", suffixStr)
342 } else {
343 version = a
344 suffix = "5000"
345 }
346 split := strings.Split(version, ".")
347 var verArr []string
348
349 verArr = append(verArr, "1")
350 var tmp string
351 for i := 0; i < 3; i++ {
352 if i < len(split) {
353 tmp = split[i]
354 } else {
355 tmp = "0"
356 }
357 verArr = append(verArr, fmt.Sprintf("%04s", tmp))
358 }
359 verArr = append(verArr, suffix)
360
361 ver := strings.Join(verArr, "")
362 verNum, _ := strconv.Atoi(ver)
363 return verNum
364}