diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 12964e499..1aa97229b 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1,4 +1,6 @@ { + "autoDownloadUpdatePkg": "Automatically download update installation package", + "autoDownloadUpdatePkgTip": "After enabling, it will automatically check the version update every two hours. If there is an updated version, it will automatically download the installation package and prompt for installation", "downloaded": "Downloaded", "allOp": "All operations", "htmlBlockError": "The execution of the following script will affect the interface display, and the script has stopped running", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 65e74d6d8..0232993d9 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1,4 +1,6 @@ { + "autoDownloadUpdatePkg": "Descargar automáticamente el paquete de instalación de actualizaciones", + "autoDownloadUpdatePkgTip": "Después de abrir, verificará automáticamente la actualización de la versión cada dos horas. Si hay una versión actualizada, descargará automáticamente el paquete de instalación y solicitará la instalación", "downloaded": "Descargado", "allOp": "Todas las operaciones", "htmlBlockError": "La ejecución del siguiente script afectará la visualización de la interfaz y el script ha dejado de ejecutarse", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index 3c232774a..3e3e4b523 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1,4 +1,6 @@ { + "autoDownloadUpdatePkg": "Télécharger automatiquement le package d'installation de la mise à jour", + "autoDownloadUpdatePkgTip": "Après l'ouverture, il vérifiera automatiquement la mise à jour de la version toutes les deux heures. S'il existe une version mise à jour, il téléchargera automatiquement le package d'installation et demandera l'installation", "downloaded": "Téléchargé", "allOp": "Toutes les opérations", "htmlBlockError": "L'exécution du script suivant affectera l'affichage de l'interface et le script a cessé de s'exécuter", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 551ed2452..60d2185b1 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1,4 +1,6 @@ { + "autoDownloadUpdatePkg": "自動下載更新安裝包", + "autoDownloadUpdatePkgTip": "開啟後會每隔兩小時自動檢查版本更新,如果有更新版本則自動下載安裝包並提示安裝", "downloaded": "已下載", "allOp": "所有操作", "htmlBlockError": "以下 script 執行會影響界面顯示,已經停止運行該腳本", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 1df0d54b8..11ba4ceb4 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1,4 +1,6 @@ { + "autoDownloadUpdatePkg": "自动下载更新安装包", + "autoDownloadUpdatePkgTip": "开启后会每隔两小时自动检查版本更新,如果有更新版本则自动下载安装包并提示安装", "downloaded": "已下载", "allOp": "所有操作", "htmlBlockError": "以下 script 执行会影响界面显示,已经停止运行该脚本", diff --git a/app/src/config/about.ts b/app/src/config/about.ts index dc93c26cf..29757e68d 100644 --- a/app/src/config/about.ts +++ b/app/src/config/about.ts @@ -125,6 +125,14 @@ export const about = { ${window.siyuan.languages.checkUpdate} +
+
+ ${window.siyuan.languages.autoDownloadUpdatePkg} +
${window.siyuan.languages.autoDownloadUpdatePkgTip}
+
+
+ +
${window.siyuan.languages.systemLog} @@ -339,6 +347,12 @@ export const about = { }); }); }); + const downloadInstallPkgElement = about.element.querySelector("#downloadInstallPkg") as HTMLInputElement; + downloadInstallPkgElement.addEventListener("change", () => { + fetchPost("/api/system/setDownloadInstallPkg", {downloadInstallPkg: downloadInstallPkgElement.checked}, () => { + window.siyuan.config.system.downloadInstallPkg = downloadInstallPkgElement.checked + }); + }); about.element.querySelector("#aboutConfim").addEventListener("click", () => { fetchPost("/api/system/setNetworkProxy", { scheme: (about.element.querySelector("#aboutScheme") as HTMLInputElement).value, diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index 43f2a2373..46579cd3d 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -304,6 +304,7 @@ declare interface IConfig { xanadu: boolean udanax: boolean uploadErrLog: boolean + downloadInstallPkg: boolean networkServe: boolean useExistingDB: boolean } diff --git a/kernel/api/router.go b/kernel/api/router.go index a207d9100..d6e4f8b6c 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -40,6 +40,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/system/setAccessAuthCode", model.CheckAuth, setAccessAuthCode) ginServer.Handle("POST", "/api/system/setNetworkServe", model.CheckAuth, setNetworkServe) ginServer.Handle("POST", "/api/system/setUploadErrLog", model.CheckAuth, setUploadErrLog) + ginServer.Handle("POST", "/api/system/setDownloadInstallPkg", model.CheckAuth, setDownloadInstallPkg) ginServer.Handle("POST", "/api/system/setNetworkProxy", model.CheckAuth, setNetworkProxy) ginServer.Handle("POST", "/api/system/setWorkspaceDir", model.CheckAuth, setWorkspaceDir) ginServer.Handle("POST", "/api/system/listWorkspaceDirs", model.CheckAuth, listWorkspaceDirs) diff --git a/kernel/api/system.go b/kernel/api/system.go index aaf7d136b..a97c94403 100644 --- a/kernel/api/system.go +++ b/kernel/api/system.go @@ -291,6 +291,20 @@ func setUploadErrLog(c *gin.Context) { time.Sleep(time.Second * 3) } +func setDownloadInstallPkg(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, ok := util.JsonArg(c, ret) + if !ok { + return + } + + downloadInstallPkg := arg["downloadInstallPkg"].(bool) + model.Conf.System.DownloadInstallPkg = downloadInstallPkg + model.Conf.Save() +} + func setNetworkProxy(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/conf/system.go b/kernel/conf/system.go index 2b2ae2308..9e47bf5b4 100644 --- a/kernel/conf/system.go +++ b/kernel/conf/system.go @@ -36,14 +36,16 @@ type System struct { NetworkServe bool `json:"networkServe"` NetworkProxy *NetworkProxy `json:"networkProxy"` - UploadErrLog bool `json:"uploadErrLog"` + UploadErrLog bool `json:"uploadErrLog"` + DownloadInstallPkg bool `json:"downloadInstallPkg"` } func NewSystem() *System { return &System{ - ID: util.GetDeviceID(), - KernelVersion: util.Ver, - NetworkProxy: &NetworkProxy{}, + ID: util.GetDeviceID(), + KernelVersion: util.Ver, + NetworkProxy: &NetworkProxy{}, + DownloadInstallPkg: true, } } diff --git a/kernel/main.go b/kernel/main.go index aea697819..58f043df6 100644 --- a/kernel/main.go +++ b/kernel/main.go @@ -46,7 +46,7 @@ func main() { go model.HookResident() util.SetBooted() util.ClearPushProgress(100) - go model.AutoRefreshUser() + go model.AutoRefreshCheck() go model.AutoFlushTx() go sql.AutoFlushTreeQueue() go treenode.AutoFlushBlockTree() diff --git a/kernel/mobile/kernel.go b/kernel/mobile/kernel.go index 24866015d..f63b1148f 100644 --- a/kernel/mobile/kernel.go +++ b/kernel/mobile/kernel.go @@ -59,7 +59,7 @@ func StartKernel(container, appDir, workspaceDir, nativeLibDir, privateDataDir, go model.AutoStat() util.SetBooted() util.ClearPushProgress(100) - go model.AutoRefreshUser() + go model.AutoRefreshCheck() go model.AutoFlushTx() go sql.AutoFlushTreeQueue() go treenode.AutoFlushBlockTree() diff --git a/kernel/model/liandi.go b/kernel/model/liandi.go index e527774d5..4c9e26166 100644 --- a/kernel/model/liandi.go +++ b/kernel/model/liandi.go @@ -144,11 +144,11 @@ func LoadUploadToken() (err error) { } var ( - refreshUserTicker = time.NewTicker(2 * time.Hour) + refreshCheckTicker = time.NewTicker(2 * time.Hour) subscriptionExpirationReminded bool ) -func AutoRefreshUser() { +func AutoRefreshCheck() { for { if !subscriptionExpirationReminded { subscriptionExpirationReminded = true @@ -235,7 +235,12 @@ func AutoRefreshUser() { } }() - <-refreshUserTicker.C + go func() { + defer logging.Recover() + checkDownloadInstallPkg() + }() + + <-refreshCheckTicker.C } } diff --git a/kernel/model/updater.go b/kernel/model/updater.go index 3793d8d8c..03d833618 100644 --- a/kernel/model/updater.go +++ b/kernel/model/updater.go @@ -17,16 +17,111 @@ package model import ( + "bufio" + "crypto/sha256" "fmt" - "sync" + "io" + "os" + "path/filepath" + "runtime" + "github.com/88250/gulu" + "github.com/imroc/req/v3" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/util" ) -var ( - checkUpdateLock = &sync.Mutex{} -) +func checkDownloadInstallPkg() { + if !Conf.System.DownloadInstallPkg { + return + } + + result, err := util.GetRhyResult(false) + if nil != err { + return + } + + installPkgSite := result["installPkg"].(string) + ver := result["ver"].(string) + if ver == util.Ver { + return + } + + var suffix string + if gulu.OS.IsWindows() { + if "386" == runtime.GOARCH { + suffix = "win32.exe" + } else { + suffix = "win.exe" + } + } else if gulu.OS.IsDarwin() { + if "arm64" == runtime.GOARCH { + suffix = "mac-arm64.dmg" + } else { + suffix = "mac.dmg" + } + } else if gulu.OS.IsLinux() { + suffix = "linux.AppImage" + } + pkg := "siyuan-" + ver + "-" + suffix + installPkg := "siyuan/" + pkg + downloadPkgURL := installPkgSite + installPkg + localPkgPath := filepath.Join(util.TempDir, "install", pkg) + checksums := result["checksums"].(map[string]interface{}) + checksum := checksums[pkg].(string) + + downloadInstallPkg(downloadPkgURL, checksum, localPkgPath) +} + +func downloadInstallPkg(pkgURL, checksum, savePath string) { + if gulu.File.IsExist(savePath) { + localChecksum, _ := sha256Hash(savePath) + if localChecksum == checksum { + return + } + } + + client := req.C() + callback := func(info req.DownloadInfo) { + logging.LogInfof("downloading install package from [%s] %.2f%%", pkgURL, float64(info.DownloadedSize)/float64(info.Response.ContentLength)*100.0) + } + resp, err := client.R().SetOutputFile(savePath).SetDownloadCallback(callback).Get(pkgURL) + if nil != err { + logging.LogErrorf("download install package failed: %s", err) + return + } + if 200 != resp.StatusCode { + logging.LogErrorf("download install package [%s] failed [sc=%d]", pkgURL, resp.StatusCode) + return + } + logging.LogInfof("downloaded install package [%s] to [%s]", pkgURL, savePath) + localChecksum, _ := sha256Hash(savePath) + if checksum != localChecksum { + logging.LogErrorf("verify checksum failed, download install package [%s] checksum [%s] not equal to downloaded [%s] checksum [%s]", pkgURL, checksum, savePath, localChecksum) + } +} + +func sha256Hash(filename string) (ret string, err error) { + file, err := os.Open(filename) + if nil != err { + return + } + defer file.Close() + + hash := sha256.New() + reader := bufio.NewReader(file) + buf := make([]byte, 1024*1024*4) + for { + switch n, readErr := reader.Read(buf); readErr { + case nil: + hash.Write(buf[:n]) + case io.EOF: + return fmt.Sprintf("%x", hash.Sum(nil)), nil + default: + return "", err + } + } +} type Announcement struct { Id string `json:"id"` @@ -58,9 +153,6 @@ func CheckUpdate(showMsg bool) { return } - checkUpdateLock.Lock() - defer checkUpdateLock.Unlock() - result, err := util.GetRhyResult(showMsg) if nil != err { return