updater.go 8.5 KB

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