Update: deteksi AppImage tanpa $APPIMAGE, cek berkala, indikator di footer
- appImagePath(): pakai $APPIMAGE kalau ada, kalau tidak periksa berkas executable sendiri apakah AppImage (dulu cek update dilewati begitu saja) - cek ulang tiap 4 jam selama aplikasi terbuka, bukan hanya saat start - versi di footer berubah jadi 'v1.1.0 -> v1.1.1' saat ada update, jadi tetap terlihat walau dialognya ditutup dengan Nanti - tes: appImagePath (env, fallback, bukan AppImage), indikator footer
This commit is contained in:
1 parent
a7e75d95c4
commit
fbf7344b09
3 files changed
+122
-16
No files matched your search
@@ -24,14 +24,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type app struct {
|
type app struct {
|
||||||
win fyne.Window
|
win fyne.Window
|
||||||
store *store.Store
|
store *store.Store
|
||||||
browsers []browser.Browser
|
browsers []browser.Browser
|
||||||
rows *fyne.Container
|
rows *fyne.Container
|
||||||
cards map[string]*hoverCard
|
cards map[string]*hoverCard
|
||||||
icons *iconCache
|
icons *iconCache
|
||||||
count *canvas.Text // angka jumlah shortcut di header daftar
|
count *canvas.Text // angka jumlah shortcut di header daftar
|
||||||
version string
|
versionText *canvas.Text // versi di footer; berubah kalau ada update
|
||||||
|
version string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run menampilkan window utama LazyRun sampai user menutupnya.
|
// Run menampilkan window utama LazyRun sampai user menutupnya.
|
||||||
@@ -60,6 +61,7 @@ func build(a fyne.App, version string) (*app, error) {
|
|||||||
ui.count = count
|
ui.count = count
|
||||||
ui.win = a.NewWindow(T("app_title"))
|
ui.win = a.NewWindow(T("app_title"))
|
||||||
ui.win.Resize(fyne.NewSize(840, 600))
|
ui.win.Resize(fyne.NewSize(840, 600))
|
||||||
|
ui.versionText = caption("LazyRun v" + version + " · oxpoe")
|
||||||
|
|
||||||
st, err := store.Load(xdg.MetadataPath())
|
st, err := store.Load(xdg.MetadataPath())
|
||||||
ui.store = st
|
ui.store = st
|
||||||
@@ -82,7 +84,7 @@ func build(a fyne.App, version string) (*app, error) {
|
|||||||
footer := container.NewBorder(
|
footer := container.NewBorder(
|
||||||
widget.NewSeparator(), nil,
|
widget.NewSeparator(), nil,
|
||||||
container.NewHBox(update, github, donate),
|
container.NewHBox(update, github, donate),
|
||||||
caption("LazyRun v"+version+" · oxpoe"),
|
ui.versionText,
|
||||||
)
|
)
|
||||||
|
|
||||||
accent := accentBar()
|
accent := accentBar()
|
||||||
|
|||||||
+55
-7
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
@@ -15,15 +16,45 @@ import (
|
|||||||
"lazyrun/internal/update"
|
"lazyrun/internal/update"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// recheckInterval adalah jeda pemeriksaan update berkala selama aplikasi terbuka.
|
||||||
|
const recheckInterval = 4 * time.Hour
|
||||||
|
|
||||||
|
// appImagePath mengembalikan lokasi AppImage yang sedang dipakai.
|
||||||
|
// $APPIMAGE dipakai kalau ada; kalau tidak (mis. dijalankan lewat wrapper),
|
||||||
|
// berkas executable-nya sendiri diperiksa apakah benar AppImage.
|
||||||
|
func appImagePath() string {
|
||||||
|
if path := os.Getenv("APPIMAGE"); path != "" {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
|
||||||
|
exe = resolved
|
||||||
|
}
|
||||||
|
if update.IsAppImage(exe) {
|
||||||
|
return exe
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// checkUpdates dipanggil tombol footer: hasilnya selalu diberitahukan.
|
// checkUpdates dipanggil tombol footer: hasilnya selalu diberitahukan.
|
||||||
func (ui *app) checkUpdates() { ui.checkForUpdate(true) }
|
func (ui *app) checkUpdates() { ui.checkForUpdate(true) }
|
||||||
|
|
||||||
// scheduleUpdateCheck memeriksa rilis terbaru beberapa saat setelah aplikasi tampil.
|
// scheduleUpdateCheck memeriksa rilis terbaru setelah aplikasi tampil,
|
||||||
|
// lalu mengulanginya berkala selama aplikasi terbuka.
|
||||||
func (ui *app) scheduleUpdateCheck() {
|
func (ui *app) scheduleUpdateCheck() {
|
||||||
if os.Getenv("APPIMAGE") == "" {
|
if appImagePath() == "" {
|
||||||
return // dijalankan dari source: jangan ganggu dengan cek update
|
return // dijalankan dari source / hasil ekstrak: tidak bisa memasang update
|
||||||
}
|
}
|
||||||
time.AfterFunc(2*time.Second, func() { ui.checkForUpdate(false) })
|
time.AfterFunc(2*time.Second, func() { ui.checkForUpdate(false) })
|
||||||
|
ticker := time.NewTicker(recheckInterval)
|
||||||
|
go func() {
|
||||||
|
for range ticker.C {
|
||||||
|
ui.checkForUpdate(false)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkForUpdate mengambil rilis terbaru dari GitHub. manual=false hanya
|
// checkForUpdate mengambil rilis terbaru dari GitHub. manual=false hanya
|
||||||
@@ -45,6 +76,7 @@ func (ui *app) checkForUpdate(manual bool) {
|
|||||||
dialog.ShowError(err, ui.win)
|
dialog.ShowError(err, ui.win)
|
||||||
}
|
}
|
||||||
case update.Newer(ui.version, rel):
|
case update.Newer(ui.version, rel):
|
||||||
|
ui.markUpdatePending(rel)
|
||||||
ui.showUpdateDialog(rel)
|
ui.showUpdateDialog(rel)
|
||||||
case manual:
|
case manual:
|
||||||
dialog.ShowInformation(T("check_updates"), fmt.Sprintf(T("up_to_date"), ui.version), ui.win)
|
dialog.ShowInformation(T("check_updates"), fmt.Sprintf(T("up_to_date"), ui.version), ui.win)
|
||||||
@@ -53,6 +85,16 @@ func (ui *app) checkForUpdate(manual bool) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// markUpdatePending menampilkan versi baru di footer, jadi tetap terlihat
|
||||||
|
// walau dialognya ditutup dengan "Nanti".
|
||||||
|
func (ui *app) markUpdatePending(rel update.Release) {
|
||||||
|
if ui.versionText == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ui.versionText.Text = fmt.Sprintf("v%s → v%s", ui.version, rel.Version)
|
||||||
|
ui.versionText.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
// showUpdateDialog menawarkan update: unduh sekarang, buka halaman rilis, atau nanti.
|
// showUpdateDialog menawarkan update: unduh sekarang, buka halaman rilis, atau nanti.
|
||||||
func (ui *app) showUpdateDialog(rel update.Release) {
|
func (ui *app) showUpdateDialog(rel update.Release) {
|
||||||
notes := widget.NewLabel(rel.Notes)
|
notes := widget.NewLabel(rel.Notes)
|
||||||
@@ -91,7 +133,7 @@ func (ui *app) showUpdateDialog(rel update.Release) {
|
|||||||
|
|
||||||
// updaterBinary mengembalikan path AppImageUpdate kalau terpasang.
|
// updaterBinary mengembalikan path AppImageUpdate kalau terpasang.
|
||||||
func (ui *app) updaterBinary() string {
|
func (ui *app) updaterBinary() string {
|
||||||
if os.Getenv("APPIMAGE") == "" {
|
if appImagePath() == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
path, err := exec.LookPath("AppImageUpdate")
|
path, err := exec.LookPath("AppImageUpdate")
|
||||||
@@ -103,7 +145,7 @@ func (ui *app) updaterBinary() string {
|
|||||||
|
|
||||||
// downloadUpdate mengunduh AppImage terbaru dan menukarnya dengan yang sedang dipakai.
|
// downloadUpdate mengunduh AppImage terbaru dan menukarnya dengan yang sedang dipakai.
|
||||||
func (ui *app) downloadUpdate(rel update.Release) {
|
func (ui *app) downloadUpdate(rel update.Release) {
|
||||||
appImage := os.Getenv("APPIMAGE")
|
appImage := appImagePath()
|
||||||
if appImage == "" {
|
if appImage == "" {
|
||||||
ui.openURL(rel.PageURL)
|
ui.openURL(rel.PageURL)
|
||||||
return
|
return
|
||||||
@@ -126,10 +168,16 @@ func (ui *app) downloadUpdate(rel update.Release) {
|
|||||||
|
|
||||||
// runUpdater memakai AppImageUpdate untuk memasang update (pakai zsync, unduhannya kecil).
|
// runUpdater memakai AppImageUpdate untuk memasang update (pakai zsync, unduhannya kecil).
|
||||||
func (ui *app) runUpdater(updater string, rel update.Release) {
|
func (ui *app) runUpdater(updater string, rel update.Release) {
|
||||||
|
appImage := appImagePath()
|
||||||
|
if appImage == "" {
|
||||||
|
ui.openURL(rel.PageURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
progress := dialog.NewProgressInfinite(T("check_updates"), T("update_downloading"), ui.win)
|
progress := dialog.NewProgressInfinite(T("check_updates"), T("update_downloading"), ui.win)
|
||||||
progress.Show()
|
progress.Show()
|
||||||
go func() {
|
go func() {
|
||||||
err := exec.Command(updater, os.Getenv("APPIMAGE")).Run()
|
err := exec.Command(updater, appImage).Run()
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progress.Hide()
|
progress.Hide()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -157,7 +205,7 @@ func (ui *app) offerRestart() {
|
|||||||
|
|
||||||
// restart menjalankan AppImage yang baru lalu menutup proses lama.
|
// restart menjalankan AppImage yang baru lalu menutup proses lama.
|
||||||
func (ui *app) restart() {
|
func (ui *app) restart() {
|
||||||
appImage := os.Getenv("APPIMAGE")
|
appImage := appImagePath()
|
||||||
if appImage == "" {
|
if appImage == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2/test"
|
||||||
|
|
||||||
|
"lazyrun/internal/update"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppImagePathFromEnv(t *testing.T) {
|
||||||
|
t.Setenv("APPIMAGE", "/home/u/Apps/lazyrun-1.0.0-x86_64.AppImage")
|
||||||
|
if got := appImagePath(); got != "/home/u/Apps/lazyrun-1.0.0-x86_64.AppImage" {
|
||||||
|
t.Errorf("appImagePath() = %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppImagePathWithoutEnvAndNotAppImage(t *testing.T) {
|
||||||
|
// test binary bukan AppImage, jadi tidak ada yang bisa diupdate
|
||||||
|
t.Setenv("APPIMAGE", "")
|
||||||
|
if got := appImagePath(); got != "" {
|
||||||
|
t.Errorf("appImagePath() = %q, mau kosong untuk binary biasa", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppImagePathFallsBackToExecutable(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
// executable tiruan: ELF + magic AI (AppImage)
|
||||||
|
fake := filepath.Join(dir, "lazyrun.AppImage")
|
||||||
|
data := make([]byte, 64)
|
||||||
|
copy(data, []byte{0x7f, 'E', 'L', 'F', 0x02, 0x01, 0x01, 0x00})
|
||||||
|
copy(data[8:], []byte{'A', 'I', 0x02})
|
||||||
|
if err := os.WriteFile(fake, data, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// jalur env kosong -> deteksi dari IsAppImage (di sini diuji langsung)
|
||||||
|
if !update.IsAppImage(fake) {
|
||||||
|
t.Fatal("berkas uji harus dikenali sebagai AppImage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarkUpdatePendingSetsFooter(t *testing.T) {
|
||||||
|
a := test.NewApp()
|
||||||
|
defer a.Quit()
|
||||||
|
|
||||||
|
ui, err := build(a, "1.1.0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ui.markUpdatePending(update.Release{Version: "9.9.9"})
|
||||||
|
if got := ui.versionText.Text; got != "v1.1.0 → v9.9.9" {
|
||||||
|
t.Errorf("footer tidak menampilkan update: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user