Files
lazyrun/internal/shortcut/restore.go
T
oxpoe 581cd07dba 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
2026-10-13 09:25:49 +07:00

175 lines
4.7 KiB
Go

package shortcut
import (
"errors"
"os"
"os/exec"
"path/filepath"
"strings"
"lazyrun/internal/icon"
"lazyrun/internal/store"
"lazyrun/internal/xdg"
)
// Restore memasang ulang shortcut dari data cadangan ke mesin ini:
// binary dan ikon ditulis dari isi cadangan (kalau ada), lalu .desktop dibuat
// dengan path lokal. Metadata yang menunjuk ke komputer lain tidak dipakai
// mentah-mentah, jadi hasil restore langsung bisa dipakai.
func Restore(item store.Shortcut, binary, iconData []byte) (store.Shortcut, error) {
name := strings.TrimSpace(item.Name)
if name == "" {
return item, errors.New("nama shortcut kosong")
}
sl := slugify(name)
if sl == "" {
return item, errors.New("nama shortcut tidak valid")
}
restored := item
restored.Slug = sl
restored.Name = name
restored.DesktopCopyPath = ""
restored.IconPath = ""
if restored.Type == store.TypeExecutable {
if err := restoreBinary(&restored, sl, binary); err != nil {
return restored, err
}
}
if restored.Type == store.TypeWebApp && len(restored.ExecArgs) == 0 && restored.Browser != "" {
restored.ExecArgs = webAppArgs(restored.Browser, restored.URL)
}
iconPath, err := restoreIcon(restored, sl, iconData)
if err != nil {
return restored, err
}
restored.IconPath = iconPath
entry := Entry{
Name: name,
ExecArgs: restored.ExecArgs,
Icon: iconPath,
Categories: restored.Categories,
Terminal: restored.Terminal && restored.Kind != KindWindows,
NoDisplay: !restored.Targets.Menu,
}
if entry.Categories == "" {
entry.Categories = "Utility;"
}
if restored.Kind == KindScript {
if dir := scriptOriginDir(restored); dir != "" {
entry.WorkingDir = dir
}
}
deskDst := filepath.Join(xdg.ApplicationsDir(), sl+".desktop")
if err := WriteDesktop(deskDst, entry); err != nil {
return restored, err
}
restored.DesktopPath = deskDst
if restored.Targets.Desktop {
copyPath := filepath.Join(xdg.DesktopDir(), sl+".desktop")
if err := copyFile(deskDst, copyPath, 0o755); err != nil {
return restored, err
}
restored.DesktopCopyPath = copyPath
}
go RefreshCache()
return restored, nil
}
// restoreBinary menulis binary/script dari cadangan ke ~/.local/bin.
// Kalau cadangan tidak memuat isinya, berkas lokal yang masih ada dipakai.
func restoreBinary(restored *store.Shortcut, slug string, binary []byte) error {
binDst := filepath.Join(xdg.BinDir(), slug)
originDir := scriptOriginDir(*restored)
if len(binary) == 0 {
// tidak ada isi cadangan: pakai berkas lama kalau masih ada di mesin ini
if _, err := os.Stat(restored.BinaryPath); err != nil {
restored.BinaryPath = ""
restored.ExecArgs = nil
return nil
}
binary, _ = os.ReadFile(restored.BinaryPath)
}
if err := writeFileMode(binDst, binary, 0o755); err != nil {
return err
}
kind := DetectKind(binDst)
if kind == KindScript {
// script dijalankan lewat wrapper: script aslinya disimpan sebagai .real
if err := os.Rename(binDst, binDst+scriptSuffix); err != nil {
return err
}
if err := writeScriptWrapper(binDst, binDst+scriptSuffix, originDir); err != nil {
return err
}
}
restored.Kind = kind
restored.BinaryPath = binDst
restored.ExecArgs = []string{binDst}
if kind == KindWindows {
if wine, err := exec.LookPath("wine"); err == nil {
restored.ExecArgs = []string{wine, binDst}
}
}
return nil
}
// restoreIcon memakai ikon dari cadangan; kalau tidak ada, coba unduh ulang
// dari sumber yang tercatat, lalu jatuh ke ikon default.
func restoreIcon(item store.Shortcut, slug string, iconData []byte) (string, error) {
if len(iconData) > 0 {
if path, err := icon.Save(iconData, slug); err == nil {
return path, nil
}
}
source := item.IconSource
if source != "" {
options := iconOptions{Slug: slug}
switch {
case strings.HasPrefix(source, iconSourceFavicon):
options.PageURL = strings.TrimPrefix(source, iconSourceFavicon)
case strings.HasPrefix(source, iconSourceSearch):
options.Query = strings.TrimPrefix(source, iconSourceSearch)
case strings.HasPrefix(source, "http"):
options.IconURL = source
}
if path, _, err := installIconFor(options); err == nil && path != "" {
return path, nil
}
}
return icon.Default(slug)
}
// scriptOriginDir menebak folder asal script dari metadata (dipakai sebagai
// Path= dan folder kerja wrapper).
func scriptOriginDir(item store.Shortcut) string {
dir := filepath.Dir(item.BinaryPath)
if dir == "" || dir == "." || dir == "/" {
return ""
}
return dir
}
// webAppArgs menyusun argv PWA dari perintah browser yang tersimpan.
func webAppArgs(browser, pageURL string) []string {
args := strings.Fields(browser)
if len(args) == 0 {
return nil
}
if strings.Contains(strings.ToLower(args[0]), "firefox") {
return append(args, "--new-window", pageURL)
}
return append(args, "--app="+pageURL)
}