updater.go 8.9 KB

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