diff --git a/backend-golang/app.go b/backend-golang/app.go index c67d3d2..1b56daa 100644 --- a/backend-golang/app.go +++ b/backend-golang/app.go @@ -4,12 +4,14 @@ import ( "bufio" "context" "errors" + "io" "net/http" "os" "os/exec" "path/filepath" "runtime" "syscall" + "time" "github.com/fsnotify/fsnotify" "github.com/minio/selfupdate" @@ -118,13 +120,50 @@ func (a *App) monitorHardware() { monitor.Start() } +type ProgressReader struct { + reader io.Reader + total int64 + err error +} + +func (pr *ProgressReader) Read(p []byte) (n int, err error) { + n, err = pr.reader.Read(p) + pr.err = err + pr.total += int64(n) + return +} + func (a *App) UpdateApp(url string) (broken bool, err error) { resp, err := http.Get(url) if err != nil { return false, err } defer resp.Body.Close() - err = selfupdate.Apply(resp.Body, selfupdate.Options{}) + pr := &ProgressReader{reader: resp.Body} + + ticker := time.NewTicker(250 * time.Millisecond) + defer ticker.Stop() + + go func() { + for { + <-ticker.C + wruntime.EventsEmit(a.ctx, "updateApp", &DownloadStatus{ + Name: filepath.Base(url), + Path: "", + Url: url, + Transferred: pr.total, + Size: resp.ContentLength, + Speed: 0, + Progress: 100 * (float64(pr.total) / float64(resp.ContentLength)), + Downloading: pr.err == nil && pr.total < resp.ContentLength, + Done: pr.total == resp.ContentLength, + }) + if pr.err != nil || pr.total == resp.ContentLength { + break + } + } + }() + err = selfupdate.Apply(pr, selfupdate.Options{}) if err != nil { if rerr := selfupdate.RollbackError(err); rerr != nil { return true, rerr diff --git a/frontend/src/utils/index.tsx b/frontend/src/utils/index.tsx index a77b7c1..b5776a0 100644 --- a/frontend/src/utils/index.tsx +++ b/frontend/src/utils/index.tsx @@ -15,7 +15,7 @@ import { toast } from 'react-toastify'; import { t } from 'i18next'; import { ToastOptions } from 'react-toastify/dist/types'; import { Button } from '@fluentui/react-components'; -import { BrowserOpenURL, WindowShow } from '../../wailsjs/runtime'; +import { BrowserOpenURL, EventsOff, EventsOn, WindowShow } from '../../wailsjs/runtime'; import { NavigateFunction } from 'react-router'; import { ModelConfig, ModelParameters } from '../types/configs'; import { DownloadStatus } from '../types/downloads'; @@ -333,27 +333,47 @@ export async function checkUpdate(notifyEvenLatest: boolean = false) { `https://gitee.com/josc146/RWKV-Runner/releases/download/${versionTag}/${asset.name}`; toastWithButton(t('New Version Available') + ': ' + versionTag, t('Update'), () => { DeleteFile('cache.json'); - toast(t('Downloading update, please wait. If it is not completed, please manually download the program from GitHub and replace the original program.'), { - type: 'info', - position: 'bottom-left', - autoClose: false - }); - setTimeout(() => { - UpdateApp(updateUrl).then(() => { - toast(t('Update completed, please restart the program.'), { - type: 'success', - position: 'bottom-left', - autoClose: false - } - ); - }).catch((e) => { - toast(t('Update Error') + ' - ' + (e.message || e), { - type: 'error', + const progressId = 'update_app'; + const progressEvent = 'updateApp'; + const updateProgress = (ds: DownloadStatus | null) => { + const content = + t('Downloading update, please wait. If it is not completed, please manually download the program from GitHub and replace the original program.') + + (ds ? ` (${ds.progress.toFixed(2)}% ${bytesToReadable(ds.transferred)}/${bytesToReadable(ds.size)})` : ''); + const options: ToastOptions = { + type: 'info', + position: 'bottom-left', + autoClose: false, + toastId: progressId, + hideProgressBar: false, + progress: ds ? ds.progress / 100 : 0 + }; + if (toast.isActive(progressId)) + toast.update(progressId, { + render: content, + ...options + }); + else + toast(content, options); + }; + updateProgress(null); + EventsOn(progressEvent, updateProgress); + UpdateApp(updateUrl).then(() => { + toast(t('Update completed, please restart the program.'), { + type: 'success', position: 'bottom-left', autoClose: false - }); + } + ); + }).catch((e) => { + toast(t('Update Error') + ' - ' + (e.message || e), { + type: 'error', + position: 'bottom-left', + autoClose: false }); - }, 500); + }).finally(() => { + toast.dismiss(progressId); + EventsOff(progressEvent); + }); }, { autoClose: false, position: 'bottom-left'