Auto update: cek rilis GitHub, notifikasi, dan pemasangan AppImage
- Paket internal/update: ambil rilis terbaru (API GitHub), bandingkan versi semver sederhana, unduh AppImage ke berkas sementara lalu tukar atomik (verifikasi magic ELF+AI; kalau gagal, AppImage lama tetap utuh) - UI: cek otomatis 2 detik setelah aplikasi tampil (hanya kalau jalan sebagai AppImage), dialog notifikasi versi baru + catatan rilis, tombol Update now / Halaman rilis / Nanti - Pemasangan: lewat AppImageUpdate kalau terpasang, kalau tidak unduh sendiri; setelah selesai ditawarkan Restart (jalankan AppImage baru lalu tutup yang lama) - Tombol footer Cek Update memakai alur yang sama dan selalu memberi jawaban - LAZYRUN_UPDATE_API untuk mengarahkan cek update ke server lain (tes/fork) - Tes: perbandingan versi, parsing rilis, penukaran AppImage, penolakan berkas bukan AppImage
This commit is contained in:
1 parent
59145910b5
commit
e8fa35d70d
5 files changed
+591
-14
No files matched your search
@@ -39,6 +39,7 @@ func Run(a fyne.App, version string) {
|
||||
ui, loadErr := build(a, version)
|
||||
ui.win.Show()
|
||||
ui.flashCards()
|
||||
ui.scheduleUpdateCheck()
|
||||
if loadErr != nil {
|
||||
dialog.ShowError(loadErr, ui.win)
|
||||
}
|
||||
|
||||
+160
-14
@@ -4,23 +4,169 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"lazyrun/internal/update"
|
||||
)
|
||||
|
||||
// checkUpdates menjalankan AppImageUpdate kalau LazyRun berjalan sebagai AppImage.
|
||||
func (ui *app) checkUpdates() {
|
||||
image := os.Getenv("APPIMAGE")
|
||||
if image == "" {
|
||||
dialog.ShowInformation(T("check_updates"), fmt.Sprintf(T("not_appimage"), ui.version), ui.win)
|
||||
return
|
||||
}
|
||||
update, err := exec.LookPath("AppImageUpdate")
|
||||
if err != nil {
|
||||
dialog.ShowInformation(T("check_updates"), T("no_appimageupdate"), ui.win)
|
||||
return
|
||||
}
|
||||
if err := exec.Command(update, image).Start(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
// checkUpdates dipanggil tombol footer: hasilnya selalu diberitahukan.
|
||||
func (ui *app) checkUpdates() { ui.checkForUpdate(true) }
|
||||
|
||||
// scheduleUpdateCheck memeriksa rilis terbaru beberapa saat setelah aplikasi tampil.
|
||||
func (ui *app) scheduleUpdateCheck() {
|
||||
if os.Getenv("APPIMAGE") == "" {
|
||||
return // dijalankan dari source: jangan ganggu dengan cek update
|
||||
}
|
||||
time.AfterFunc(2*time.Second, func() { ui.checkForUpdate(false) })
|
||||
}
|
||||
|
||||
// checkForUpdate mengambil rilis terbaru dari GitHub. manual=false hanya
|
||||
// memberitahu kalau memang ada versi baru.
|
||||
func (ui *app) checkForUpdate(manual bool) {
|
||||
if ui.version == "" || ui.version == "dev" {
|
||||
if manual {
|
||||
dialog.ShowInformation(T("check_updates"), fmt.Sprintf(T("dev_build"), ui.version), ui.win)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
rel, err := update.Latest()
|
||||
fyne.Do(func() {
|
||||
switch {
|
||||
case err != nil:
|
||||
if manual {
|
||||
dialog.ShowError(err, ui.win)
|
||||
}
|
||||
case update.Newer(ui.version, rel):
|
||||
ui.showUpdateDialog(rel)
|
||||
case manual:
|
||||
dialog.ShowInformation(T("check_updates"), fmt.Sprintf(T("up_to_date"), ui.version), ui.win)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
// showUpdateDialog menawarkan update: unduh sekarang, buka halaman rilis, atau nanti.
|
||||
func (ui *app) showUpdateDialog(rel update.Release) {
|
||||
notes := widget.NewLabel(rel.Notes)
|
||||
notes.Wrapping = fyne.TextWrapWord
|
||||
notes.Importance = widget.LowImportance
|
||||
|
||||
body := container.NewVScroll(container.NewVBox(
|
||||
heading(fmt.Sprintf(T("update_available"), rel.Version, ui.version)),
|
||||
muted(T("update_hint")),
|
||||
notes,
|
||||
))
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
buttons := container.NewHBox(
|
||||
logoBox(newLogo(), 20),
|
||||
spacer(),
|
||||
widget.NewButton(T("update_later"), func() { d.Hide() }),
|
||||
widget.NewButton(T("update_page"), func() { ui.openURL(rel.PageURL) }),
|
||||
)
|
||||
|
||||
if updates := ui.updaterBinary(); updates != "" {
|
||||
// AppImageUpdate yang memasang (dan memverifikasi) sendiri
|
||||
buttons.Add(widget.NewButtonWithIcon(T("update_now"), theme.DownloadIcon(), func() {
|
||||
d.Hide()
|
||||
ui.runUpdater(updates, rel)
|
||||
}))
|
||||
} else {
|
||||
buttons.Add(widget.NewButtonWithIcon(T("update_now"), theme.DownloadIcon(), func() {
|
||||
d.Hide()
|
||||
ui.downloadUpdate(rel)
|
||||
}))
|
||||
}
|
||||
|
||||
d = dialog.NewCustomWithoutButtons(T("check_updates"), container.NewBorder(body, buttons, nil, nil), ui.win)
|
||||
showDialog(d, ui.win, 560, 440)
|
||||
}
|
||||
|
||||
// updaterBinary mengembalikan path AppImageUpdate kalau terpasang.
|
||||
func (ui *app) updaterBinary() string {
|
||||
if os.Getenv("APPIMAGE") == "" {
|
||||
return ""
|
||||
}
|
||||
path, err := exec.LookPath("AppImageUpdate")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// downloadUpdate mengunduh AppImage terbaru dan menukarnya dengan yang sedang dipakai.
|
||||
func (ui *app) downloadUpdate(rel update.Release) {
|
||||
appImage := os.Getenv("APPIMAGE")
|
||||
if appImage == "" {
|
||||
ui.openURL(rel.PageURL)
|
||||
return
|
||||
}
|
||||
|
||||
progress := dialog.NewProgressInfinite(T("check_updates"), T("update_downloading"), ui.win)
|
||||
progress.Show()
|
||||
go func() {
|
||||
err := update.Download(rel, appImage)
|
||||
fyne.Do(func() {
|
||||
progress.Hide()
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
ui.offerRestart()
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
// runUpdater memakai AppImageUpdate untuk memasang update (pakai zsync, unduhannya kecil).
|
||||
func (ui *app) runUpdater(updater string, rel update.Release) {
|
||||
progress := dialog.NewProgressInfinite(T("check_updates"), T("update_downloading"), ui.win)
|
||||
progress.Show()
|
||||
go func() {
|
||||
err := exec.Command(updater, os.Getenv("APPIMAGE")).Run()
|
||||
fyne.Do(func() {
|
||||
progress.Hide()
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
ui.offerRestart()
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
// offerRestart menawarkan restart supaya versi baru yang dipakai.
|
||||
func (ui *app) offerRestart() {
|
||||
var d *dialog.CustomDialog
|
||||
content := container.NewVBox(
|
||||
widget.NewLabel(T("update_installed")),
|
||||
container.NewHBox(spacer(),
|
||||
widget.NewButton(T("update_later"), func() { d.Hide() }),
|
||||
widget.NewButtonWithIcon(T("update_restart"), theme.ViewRefreshIcon(), ui.restart),
|
||||
),
|
||||
)
|
||||
d = dialog.NewCustomWithoutButtons(T("check_updates"), content, ui.win)
|
||||
showDialog(d, ui.win, 460, 200)
|
||||
}
|
||||
|
||||
// restart menjalankan AppImage yang baru lalu menutup proses lama.
|
||||
func (ui *app) restart() {
|
||||
appImage := os.Getenv("APPIMAGE")
|
||||
if appImage == "" {
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(appImage)
|
||||
cmd.Env = os.Environ()
|
||||
if err := cmd.Start(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
ui.win.Close()
|
||||
}
|
||||
Reference in new issue
Block a user