Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbf7344b09 | ||
|
|
a7e75d95c4 |
No files matched your search
@@ -44,8 +44,23 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
cat > /tmp/notes.md <<'NOTES'
|
||||
Single-file AppImage for Linux x86_64. No root needed, everything stays in
|
||||
`~/.local/` and `~/.config/lazyrun/`.
|
||||
|
||||
```bash
|
||||
chmod +x lazyrun-*-x86_64.AppImage
|
||||
./lazyrun-*-x86_64.AppImage
|
||||
```
|
||||
|
||||
Already using LazyRun? It checks this release page on startup and offers to
|
||||
update itself (delta updates with `AppImageUpdate` when installed).
|
||||
|
||||
---
|
||||
NOTES
|
||||
gh release create "$GITHUB_REF_NAME" \
|
||||
dist/lazyrun-*.AppImage \
|
||||
dist/lazyrun-*.AppImage.zsync \
|
||||
--title "LazyRun ${GITHUB_REF_NAME}" \
|
||||
--notes-file /tmp/notes.md \
|
||||
--generate-notes
|
||||
@@ -24,14 +24,15 @@ import (
|
||||
)
|
||||
|
||||
type app struct {
|
||||
win fyne.Window
|
||||
store *store.Store
|
||||
browsers []browser.Browser
|
||||
rows *fyne.Container
|
||||
cards map[string]*hoverCard
|
||||
icons *iconCache
|
||||
count *canvas.Text // angka jumlah shortcut di header daftar
|
||||
version string
|
||||
win fyne.Window
|
||||
store *store.Store
|
||||
browsers []browser.Browser
|
||||
rows *fyne.Container
|
||||
cards map[string]*hoverCard
|
||||
icons *iconCache
|
||||
count *canvas.Text // angka jumlah shortcut di header daftar
|
||||
versionText *canvas.Text // versi di footer; berubah kalau ada update
|
||||
version string
|
||||
}
|
||||
|
||||
// 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.win = a.NewWindow(T("app_title"))
|
||||
ui.win.Resize(fyne.NewSize(840, 600))
|
||||
ui.versionText = caption("LazyRun v" + version + " · oxpoe")
|
||||
|
||||
st, err := store.Load(xdg.MetadataPath())
|
||||
ui.store = st
|
||||
@@ -82,7 +84,7 @@ func build(a fyne.App, version string) (*app, error) {
|
||||
footer := container.NewBorder(
|
||||
widget.NewSeparator(), nil,
|
||||
container.NewHBox(update, github, donate),
|
||||
caption("LazyRun v"+version+" · oxpoe"),
|
||||
ui.versionText,
|
||||
)
|
||||
|
||||
accent := accentBar()
|
||||
|
||||
+55
-7
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
@@ -15,15 +16,45 @@ import (
|
||||
"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.
|
||||
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() {
|
||||
if os.Getenv("APPIMAGE") == "" {
|
||||
return // dijalankan dari source: jangan ganggu dengan cek update
|
||||
if appImagePath() == "" {
|
||||
return // dijalankan dari source / hasil ekstrak: tidak bisa memasang update
|
||||
}
|
||||
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
|
||||
@@ -45,6 +76,7 @@ func (ui *app) checkForUpdate(manual bool) {
|
||||
dialog.ShowError(err, ui.win)
|
||||
}
|
||||
case update.Newer(ui.version, rel):
|
||||
ui.markUpdatePending(rel)
|
||||
ui.showUpdateDialog(rel)
|
||||
case manual:
|
||||
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.
|
||||
func (ui *app) showUpdateDialog(rel update.Release) {
|
||||
notes := widget.NewLabel(rel.Notes)
|
||||
@@ -91,7 +133,7 @@ func (ui *app) showUpdateDialog(rel update.Release) {
|
||||
|
||||
// updaterBinary mengembalikan path AppImageUpdate kalau terpasang.
|
||||
func (ui *app) updaterBinary() string {
|
||||
if os.Getenv("APPIMAGE") == "" {
|
||||
if appImagePath() == "" {
|
||||
return ""
|
||||
}
|
||||
path, err := exec.LookPath("AppImageUpdate")
|
||||
@@ -103,7 +145,7 @@ func (ui *app) updaterBinary() string {
|
||||
|
||||
// downloadUpdate mengunduh AppImage terbaru dan menukarnya dengan yang sedang dipakai.
|
||||
func (ui *app) downloadUpdate(rel update.Release) {
|
||||
appImage := os.Getenv("APPIMAGE")
|
||||
appImage := appImagePath()
|
||||
if appImage == "" {
|
||||
ui.openURL(rel.PageURL)
|
||||
return
|
||||
@@ -126,10 +168,16 @@ func (ui *app) downloadUpdate(rel update.Release) {
|
||||
|
||||
// runUpdater memakai AppImageUpdate untuk memasang update (pakai zsync, unduhannya kecil).
|
||||
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.Show()
|
||||
go func() {
|
||||
err := exec.Command(updater, os.Getenv("APPIMAGE")).Run()
|
||||
err := exec.Command(updater, appImage).Run()
|
||||
fyne.Do(func() {
|
||||
progress.Hide()
|
||||
if err != nil {
|
||||
@@ -157,7 +205,7 @@ func (ui *app) offerRestart() {
|
||||
|
||||
// restart menjalankan AppImage yang baru lalu menutup proses lama.
|
||||
func (ui *app) restart() {
|
||||
appImage := os.Getenv("APPIMAGE")
|
||||
appImage := appImagePath()
|
||||
if appImage == "" {
|
||||
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