mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
Merge pull request #8073 from erphenheimer/fix/8072-windows-shortcut-root-icon
fix(windows): repair legacy root-level shortcut icon and target / 修复旧版根目录快捷方式
This commit is contained in:
2 files changed
+110
-13
No files matched your search
@@ -87,6 +87,9 @@ func repairExistingWindowsShortcuts(paths []string, launcher string, repair func
|
||||
}
|
||||
|
||||
func repairWindowsShortcut(shortcutPath, launcher string) (bool, error) {
|
||||
root := filepath.Dir(filepath.Clean(strings.TrimSpace(launcher)))
|
||||
versionedLayout := installlayout.HasCurrent(root)
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED); err != nil {
|
||||
@@ -130,7 +133,7 @@ func repairWindowsShortcut(shortcutPath, launcher string) (bool, error) {
|
||||
}
|
||||
iconLocation := iconValue.ToString()
|
||||
_ = iconValue.Clear()
|
||||
repointTarget, fixIcon := repairWindowsShortcutPlan(target, iconLocation, launcher)
|
||||
repointTarget, fixIcon := repairWindowsShortcutPlan(target, iconLocation, launcher, versionedLayout)
|
||||
if !repointTarget && !fixIcon {
|
||||
return false, nil
|
||||
}
|
||||
@@ -205,13 +208,32 @@ func reasonixWindowsVersionedTarget(target, launcher string) bool {
|
||||
|
||||
// repairWindowsShortcutPlan decides which owned-shortcut properties need
|
||||
// rewriting. repointTarget is true when TargetPath points into a versioned
|
||||
// directory the updater can delete; fixIcon is true when IconLocation points
|
||||
// at the versioned desktop binary instead of the stable launcher.
|
||||
func repairWindowsShortcutPlan(target, iconLocation, launcher string) (repointTarget, fixIcon bool) {
|
||||
return reasonixWindowsVersionedTarget(target, launcher), reasonixWindowsStaleIcon(iconLocation, launcher)
|
||||
// directory the updater can delete or at a legacy root-level desktop binary
|
||||
// after the versioned layout is active; fixIcon applies the same policy to
|
||||
// IconLocation. Flat installs keep their live root-level binary untouched.
|
||||
func repairWindowsShortcutPlan(target, iconLocation, launcher string, versionedLayout bool) (repointTarget, fixIcon bool) {
|
||||
repointTarget = reasonixWindowsVersionedTarget(target, launcher) ||
|
||||
reasonixWindowsFlatDesktopTarget(target, launcher, versionedLayout)
|
||||
return repointTarget, reasonixWindowsStaleIcon(iconLocation, launcher, versionedLayout)
|
||||
}
|
||||
|
||||
func reasonixWindowsStaleIcon(iconLocation, launcher string) bool {
|
||||
// reasonixWindowsFlatDesktopTarget reports whether target points at the legacy
|
||||
// root-level desktop binary after versioned layout activation or after the file
|
||||
// disappeared. A valid current.json is the commit point, so a leftover flat
|
||||
// binary is not a live flat install when best-effort cleanup could not remove it.
|
||||
func reasonixWindowsFlatDesktopTarget(target, launcher string, versionedLayout bool) bool {
|
||||
flat := filepath.Join(filepath.Dir(filepath.Clean(strings.TrimSpace(launcher))), "reasonix-desktop.exe")
|
||||
if !strings.EqualFold(filepath.Clean(strings.TrimSpace(target)), flat) {
|
||||
return false
|
||||
}
|
||||
if versionedLayout {
|
||||
return true
|
||||
}
|
||||
_, err := os.Lstat(flat)
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func reasonixWindowsStaleIcon(iconLocation, launcher string, versionedLayout bool) bool {
|
||||
iconPath := strings.TrimSpace(iconLocation)
|
||||
if comma := strings.LastIndex(iconPath, ","); comma >= 0 {
|
||||
if _, err := strconv.Atoi(strings.TrimSpace(iconPath[comma+1:])); err == nil {
|
||||
@@ -228,8 +250,21 @@ func reasonixWindowsStaleIcon(iconLocation, launcher string) bool {
|
||||
return false
|
||||
}
|
||||
parts := strings.Split(rel, string(filepath.Separator))
|
||||
return len(parts) == 3 && strings.EqualFold(parts[0], "versions") &&
|
||||
strings.EqualFold(parts[2], "reasonix-desktop.exe")
|
||||
if len(parts) == 3 && strings.EqualFold(parts[0], "versions") &&
|
||||
strings.EqualFold(parts[2], "reasonix-desktop.exe") {
|
||||
return true
|
||||
}
|
||||
// Legacy root-level icon: stale once the versioned layout is committed, even
|
||||
// if best-effort cleanup left the old binary behind. Without current.json,
|
||||
// require the file to be gone so a live flat install keeps its working icon.
|
||||
if len(parts) == 1 && strings.EqualFold(parts[0], "reasonix-desktop.exe") {
|
||||
if versionedLayout {
|
||||
return true
|
||||
}
|
||||
_, err := os.Lstat(filepath.Clean(iconPath))
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func notifyWindowsShortcutChanged(path string) {
|
||||
|
||||
@@ -73,9 +73,13 @@ func TestReasonixWindowsShortcutTargetRequiresCurrentInstall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReasonixWindowsStaleIconOnlyMatchesVersionedDesktop(t *testing.T) {
|
||||
root := filepath.Join(`C:\Program Files, Inc`, "Reasonix")
|
||||
func TestReasonixWindowsStaleIcon(t *testing.T) {
|
||||
root := filepath.Join(t.TempDir(), "Program Files, Inc", "Reasonix")
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
launcher := filepath.Join(root, "reasonix-launcher.exe")
|
||||
flat := filepath.Join(root, "reasonix-desktop.exe")
|
||||
tests := []struct {
|
||||
name string
|
||||
icon string
|
||||
@@ -83,6 +87,7 @@ func TestReasonixWindowsStaleIconOnlyMatchesVersionedDesktop(t *testing.T) {
|
||||
}{
|
||||
{name: "versioned", icon: filepath.Join(root, "versions", "v1.19.3", "reasonix-desktop.exe") + ",0", want: true},
|
||||
{name: "quoted versioned", icon: `"` + filepath.Join(root, "versions", "v1.19.3", "reasonix-desktop.exe") + `", 0`, want: true},
|
||||
{name: "legacy root-level (file gone)", icon: flat + ",0", want: true},
|
||||
{name: "stable launcher", icon: launcher + ",0", want: false},
|
||||
{name: "custom icon", icon: filepath.Join(root, "custom.ico") + ",0", want: false},
|
||||
{name: "other install", icon: filepath.Join(`D:\Apps`, "Reasonix", "versions", "v1.19.3", "reasonix-desktop.exe") + ",0", want: false},
|
||||
@@ -90,11 +95,55 @@ func TestReasonixWindowsStaleIconOnlyMatchesVersionedDesktop(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := reasonixWindowsStaleIcon(tt.icon, launcher); got != tt.want {
|
||||
if got := reasonixWindowsStaleIcon(tt.icon, launcher, false); got != tt.want {
|
||||
t.Fatalf("reasonixWindowsStaleIcon(%q, %q) = %v, want %v", tt.icon, launcher, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if err := os.WriteFile(flat, []byte("binary"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if reasonixWindowsStaleIcon(flat+",0", launcher, false) {
|
||||
t.Fatal("live flat icon = stale, want healthy")
|
||||
}
|
||||
if !reasonixWindowsStaleIcon(flat+",0", launcher, true) {
|
||||
t.Fatal("versioned layout with leftover flat icon = healthy, want stale")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReasonixWindowsFlatDesktopTargetNeedsMissingFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
launcher := filepath.Join(root, "reasonix-launcher.exe")
|
||||
flat := filepath.Join(root, "reasonix-desktop.exe")
|
||||
|
||||
// No file on disk: the legacy root-level target dangles after the
|
||||
// versioned-layout migration and must be repointed.
|
||||
if !reasonixWindowsFlatDesktopTarget(flat, launcher, false) {
|
||||
t.Fatalf("missing flat desktop target = false, want true")
|
||||
}
|
||||
|
||||
// A live flat install still has the binary; its shortcut must be left alone.
|
||||
if err := os.WriteFile(flat, []byte("binary"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if reasonixWindowsFlatDesktopTarget(flat, launcher, false) {
|
||||
t.Fatalf("existing flat desktop target = true, want false")
|
||||
}
|
||||
if !reasonixWindowsFlatDesktopTarget(flat, launcher, true) {
|
||||
t.Fatalf("versioned layout with leftover flat desktop target = false, want true")
|
||||
}
|
||||
|
||||
// Non-flat targets never match.
|
||||
for _, target := range []string{
|
||||
launcher,
|
||||
filepath.Join(root, "Reasonix.exe"),
|
||||
filepath.Join(root, "versions", "v1.19.3", "reasonix-desktop.exe"),
|
||||
} {
|
||||
if reasonixWindowsFlatDesktopTarget(target, launcher, true) {
|
||||
t.Fatalf("reasonixWindowsFlatDesktopTarget(%q) = true, want false", target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReasonixWindowsVersionedTarget(t *testing.T) {
|
||||
@@ -125,7 +174,7 @@ func TestReasonixWindowsVersionedTarget(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRepairWindowsShortcutPlan(t *testing.T) {
|
||||
root := filepath.Join(`C:\Program Files`, "Reasonix")
|
||||
root := t.TempDir()
|
||||
launcher := filepath.Join(root, "reasonix-launcher.exe")
|
||||
versioned := filepath.Join(root, "versions", "v1.19.3", "reasonix-desktop.exe")
|
||||
tests := []struct {
|
||||
@@ -139,14 +188,27 @@ func TestRepairWindowsShortcutPlan(t *testing.T) {
|
||||
{name: "versioned target + clean icon", target: versioned, icon: launcher + ",0", wantRepoint: true, wantFixIcon: false},
|
||||
{name: "stable target + versioned icon", target: launcher, icon: versioned + ",0", wantRepoint: false, wantFixIcon: true},
|
||||
{name: "stable target + clean icon", target: launcher, icon: launcher + ",0", wantRepoint: false, wantFixIcon: false},
|
||||
{name: "flat target + flat icon (both gone)", target: filepath.Join(root, "reasonix-desktop.exe"), icon: filepath.Join(root, "reasonix-desktop.exe") + ",0", wantRepoint: true, wantFixIcon: true},
|
||||
{name: "flat target + clean icon", target: filepath.Join(root, "reasonix-desktop.exe"), icon: launcher + ",0", wantRepoint: true, wantFixIcon: false},
|
||||
{name: "custom target + custom icon", target: filepath.Join(root, "custom.ico"), icon: filepath.Join(root, "custom.ico") + ",0", wantRepoint: false, wantFixIcon: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repoint, fixIcon := repairWindowsShortcutPlan(tt.target, tt.icon, launcher)
|
||||
repoint, fixIcon := repairWindowsShortcutPlan(tt.target, tt.icon, launcher, false)
|
||||
if repoint != tt.wantRepoint || fixIcon != tt.wantFixIcon {
|
||||
t.Fatalf("repairWindowsShortcutPlan(%q, %q) = (%v, %v), want (%v, %v)", tt.target, tt.icon, repoint, fixIcon, tt.wantRepoint, tt.wantFixIcon)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
flat := filepath.Join(root, "reasonix-desktop.exe")
|
||||
if err := os.WriteFile(flat, []byte("binary"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if repoint, fixIcon := repairWindowsShortcutPlan(flat, flat+",0", launcher, false); repoint || fixIcon {
|
||||
t.Fatalf("live flat plan = (%v, %v), want (false, false)", repoint, fixIcon)
|
||||
}
|
||||
if repoint, fixIcon := repairWindowsShortcutPlan(flat, flat+",0", launcher, true); !repoint || !fixIcon {
|
||||
t.Fatalf("versioned layout leftover plan = (%v, %v), want (true, true)", repoint, fixIcon)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user