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
@@ -0,0 +1,174 @@
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user