Perbaiki backup/restore dan script yang tidak jalan dari shortcut
Backup/restore: - berkas .lazy (format versi 2) kini menyimpan ISI berkas: binary/script dan ikon (maks 512 MB per berkas); format versi 1 tetap bisa diimpor - impor memakai shortcut.Restore: binary ditulis ke ~/.local/bin lokal, ikon dari cadangan (atau diunduh ulang dari icon_source), .desktop ditulis dengan path lokal, salinan Desktop dibuat sesuai target - path dari komputer lama tidak dipakai mentah -> tidak ada lagi 'banyak file missing'; entri yang tidak bisa dipulihkan ditandai dan bisa dibetulkan via edit - tes end-to-end: pasang 3 shortcut -> ekspor -> pulihkan di HOME lain -> semua berkas lokal, script hasil restore benar-benar jalan Script: - script dipasang sebagai wrapper + <slug>.real: wrapper menambahkan ~/.local/bin ke PATH dan cd ke folder asli script (desktop environment menjalankan dengan cwd = home, PATH sesi) - .desktop dapat Path= sesuai folder asli script - opsi 'Run in a terminal window (scripts)' di dialog install & edit - uninstall membuang wrapper + script aslinya
This commit is contained in:
1 parent
fdb503e152
commit
581cd07dba
16 files changed
+779
-59
No files matched your search
@@ -72,7 +72,7 @@ func (ui *app) importShortcuts() {
|
||||
return
|
||||
}
|
||||
|
||||
added, skipped := mergeImported(ui.store, items)
|
||||
added, skipped := ui.restoreImported(items)
|
||||
if err := ui.store.Save(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
@@ -98,6 +98,11 @@ func (ui *app) showEditDialog(sc store.Shortcut) {
|
||||
menu.SetChecked(sc.Targets.Menu)
|
||||
desktopChk := widget.NewCheck(T("check_desktop"), nil)
|
||||
desktopChk.SetChecked(sc.Targets.Desktop)
|
||||
terminal := widget.NewCheck(T("check_terminal"), nil)
|
||||
terminal.SetChecked(sc.Terminal)
|
||||
if sc.Kind != shortcut.KindScript {
|
||||
terminal.Hide()
|
||||
}
|
||||
|
||||
browseIcon := newButton(T("browse"), theme.FolderOpenIcon(), T("tip_browse"), widget.MediumImportance, func() {
|
||||
pickFile(ui.win, imageFilter, func(path string) {
|
||||
@@ -118,6 +123,12 @@ func (ui *app) showEditDialog(sc store.Shortcut) {
|
||||
pickFile(ui.win, nil, func(path string) {
|
||||
binPath = path
|
||||
binLabel.SetText(filepath.Base(path) + " · " + detectedKind(path))
|
||||
if shortcut.DetectKind(path) == shortcut.KindScript {
|
||||
terminal.Show()
|
||||
} else {
|
||||
terminal.SetChecked(false)
|
||||
terminal.Hide()
|
||||
}
|
||||
})
|
||||
})
|
||||
category := widget.NewSelect(categories, nil)
|
||||
@@ -185,6 +196,7 @@ func (ui *app) showEditDialog(sc store.Shortcut) {
|
||||
IconData: iconData,
|
||||
Name: nameEntry.Text,
|
||||
Category: category.Selected,
|
||||
Terminal: terminal.Checked,
|
||||
Menu: menu.Checked,
|
||||
Desktop: desktopChk.Checked,
|
||||
})
|
||||
@@ -215,6 +227,7 @@ func (ui *app) showEditDialog(sc store.Shortcut) {
|
||||
sectionTitle(T("section_location")),
|
||||
menu,
|
||||
desktopChk,
|
||||
terminal,
|
||||
hint,
|
||||
container.NewHBox(logoBox(newLogo(), 20), spacer(),
|
||||
newButton(T("cancel"), nil, T("tip_cancel"), widget.MediumImportance, func() { d.Hide() }),
|
||||
@@ -226,19 +239,28 @@ func (ui *app) showEditDialog(sc store.Shortcut) {
|
||||
showDialog(d, ui.win, 560, 520)
|
||||
}
|
||||
|
||||
// mergeImported menambahkan shortcut hasil impor tanpa menimpa yang sudah ada.
|
||||
func mergeImported(target *store.Store, items []store.Shortcut) (added, skipped int) {
|
||||
for _, item := range items {
|
||||
// restoreImported memasang ulang setiap entri cadangan ke mesin ini
|
||||
// (binary, ikon, .desktop) lalu menambahkannya ke daftar, tanpa menimpa
|
||||
// shortcut yang sudah ada.
|
||||
func (ui *app) restoreImported(items []db.Backup) (added, skipped int) {
|
||||
for _, backup := range items {
|
||||
item := backup.Item
|
||||
item.Name = strings.TrimSpace(item.Name)
|
||||
if item.Name == "" || item.Slug == "" {
|
||||
if item.Name == "" {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
if _, exists := target.Get(item.Slug); exists {
|
||||
if _, exists := ui.store.Get(shortcut.Slug(item.Name)); exists {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
target.Add(item)
|
||||
|
||||
restored, err := shortcut.Restore(item, backup.Binary, backup.Icon)
|
||||
if err != nil {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
ui.store.Add(restored)
|
||||
added++
|
||||
}
|
||||
return added, skipped
|
||||
|
||||
@@ -40,11 +40,20 @@ func (ui *app) showExecutableDialog() {
|
||||
menu.SetChecked(true)
|
||||
desktop := widget.NewCheck(T("check_desktop"), nil)
|
||||
desktop.SetChecked(true)
|
||||
terminal := widget.NewCheck(T("check_terminal"), nil)
|
||||
terminal.Hide() // hanya relevan untuk script
|
||||
|
||||
browseBin := newButton(T("browse"), theme.FolderOpenIcon(), T("tip_browse"), widget.MediumImportance, func() {
|
||||
pickFile(ui.win, nil, func(path string) {
|
||||
binPath = path
|
||||
binLabel.SetText(filepath.Base(path) + " · " + detectedKind(path))
|
||||
// terminal hanya perlu untuk script; tampilkan kalau terdeteksi
|
||||
if shortcut.DetectKind(path) == shortcut.KindScript {
|
||||
terminal.Show()
|
||||
} else {
|
||||
terminal.SetChecked(false)
|
||||
terminal.Hide()
|
||||
}
|
||||
if strings.TrimSpace(nameEntry.Text) == "" {
|
||||
nameEntry.SetText(appNameFromFile(path))
|
||||
}
|
||||
@@ -83,6 +92,7 @@ func (ui *app) showExecutableDialog() {
|
||||
IconData: iconData,
|
||||
Name: nameEntry.Text,
|
||||
Category: category.Selected,
|
||||
Terminal: terminal.Checked,
|
||||
Menu: menu.Checked,
|
||||
Desktop: desktop.Checked,
|
||||
}
|
||||
@@ -106,6 +116,7 @@ func (ui *app) showExecutableDialog() {
|
||||
sectionTitle(T("section_location")),
|
||||
menu,
|
||||
desktop,
|
||||
terminal,
|
||||
hint,
|
||||
container.NewHBox(logoBox(newLogo(), 20), spacer(),
|
||||
newButton(T("cancel"), nil, T("tip_cancel"), widget.MediumImportance, func() { d.Hide() }),
|
||||
|
||||
+47
-17
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"fyne.io/fyne/v2/test"
|
||||
|
||||
"lazyrun/internal/db"
|
||||
"lazyrun/internal/icon"
|
||||
"lazyrun/internal/store"
|
||||
)
|
||||
@@ -121,26 +122,55 @@ func TestStringsCoverUsedKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeImported(t *testing.T) {
|
||||
target := &store.Store{}
|
||||
target.Add(store.Shortcut{Slug: "winbox", Name: "Winbox"})
|
||||
// TestRestoreImported memasang ulang entri cadangan: berkas dibuat di HOME saat ini
|
||||
// dan entri yang sudah ada tidak ditimpa.
|
||||
func TestRestoreImported(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("XDG_DATA_HOME", "")
|
||||
t.Setenv("PATH", "")
|
||||
|
||||
added, skipped := mergeImported(target, []store.Shortcut{
|
||||
{Slug: "winbox", Name: "Winbox lama"}, // sudah ada -> dilewati
|
||||
{Slug: "figma", Name: "Figma"}, // baru
|
||||
{Slug: "", Name: "Tanpa slug"}, // rusak -> dilewati
|
||||
{Slug: "kosong", Name: " "}, // tanpa nama -> dilewati
|
||||
{Slug: "deploy", Name: " deploy.sh "}, // nama dirapikan
|
||||
})
|
||||
script := []byte("#!/bin/sh\necho halo\n")
|
||||
items := []db.Backup{
|
||||
{Item: store.Shortcut{Slug: "lama", Name: "Deploy", Type: store.TypeExecutable, Kind: "script",
|
||||
BinaryPath: "/komputer-lama/.local/bin/deploy", Targets: store.Targets{Menu: true}}, Binary: script},
|
||||
{Item: store.Shortcut{Name: "Figma", Type: store.TypeWebApp, Kind: "pwa",
|
||||
URL: "https://figma.com", Browser: "/usr/bin/vivaldi", Targets: store.Targets{Menu: true}}},
|
||||
}
|
||||
|
||||
if added != 2 || skipped != 3 {
|
||||
t.Errorf("added=%d skipped=%d, mau 2 dan 3", added, skipped)
|
||||
a := test.NewApp()
|
||||
defer a.Quit()
|
||||
ui, err := build(a, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(target.Items) != 3 {
|
||||
t.Errorf("jumlah item = %d", len(target.Items))
|
||||
ui.store.Items = nil
|
||||
ui.store.Add(store.Shortcut{Slug: "figma", Name: "Figma"}) // sudah ada -> dilewati
|
||||
|
||||
added, skipped := ui.restoreImported(items)
|
||||
if added != 1 || skipped != 1 {
|
||||
t.Fatalf("added=%d skipped=%d, mau 1 dan 1", added, skipped)
|
||||
}
|
||||
dep, ok := target.Get("deploy")
|
||||
if !ok || target.Items[dep].Name != "deploy.sh" {
|
||||
t.Errorf("nama item tidak dirapikan: %+v", target.Items)
|
||||
|
||||
deploy, ok := ui.store.Get("deploy")
|
||||
if !ok {
|
||||
t.Fatal("shortcut hasil restore tidak masuk daftar")
|
||||
}
|
||||
sc := ui.store.Items[deploy]
|
||||
want := filepath.Join(home, ".local", "bin", "deploy")
|
||||
if sc.BinaryPath != want {
|
||||
t.Errorf("binary = %s, mau %s", sc.BinaryPath, want)
|
||||
}
|
||||
if _, err := os.Stat(want + ".real"); err != nil {
|
||||
t.Errorf("script asli (.real) tidak ditulis: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(sc.DesktopPath); err != nil {
|
||||
t.Errorf(".desktop tidak dibuat: %v", err)
|
||||
}
|
||||
if sc.IconPath == "" {
|
||||
t.Error("ikon tidak dipasang")
|
||||
}
|
||||
if sc.Kind != "script" {
|
||||
t.Errorf("kind hasil restore = %q", sc.Kind)
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ var tr = map[string][2]string{
|
||||
"section_location": {"Shortcut location", "Lokasi shortcut"},
|
||||
"check_menu": {"Application menu", "Menu aplikasi"},
|
||||
"check_desktop": {"Desktop", "Desktop"},
|
||||
"check_terminal": {"Run in a terminal window (scripts)", "Jalankan di jendela terminal (script)"},
|
||||
"exec_hint": {"The binary is copied to ~/.local/bin and can be uninstalled from the list.", "Binary disalin ke ~/.local/bin dan bisa dihapus lagi dari daftar."},
|
||||
"web_hint": {"The icon comes from the site's favicon, or is looked up online if there is none.", "Ikon diambil dari favicon situs, atau dicari otomatis di internet kalau tidak ada."},
|
||||
|
||||
|
||||
Reference in new issue
Block a user