-
Notifications
You must be signed in to change notification settings - Fork 2
Refactor: Improve Edge process management and JSON handling in patch … #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,37 +17,27 @@ Patch Edge Copilot - Go版本 | |
| */ | ||
| package main | ||
|
|
||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "runtime" | ||
| "strings" | ||
|
|
||
| "github.com/shirou/gopsutil/v3/process" | ||
| ) | ||
|
|
||
| type LocalState struct { | ||
| VariationsCountry string `json:"variations_country"` | ||
| } | ||
|
|
||
| type Preferences struct { | ||
| Browser Browser `json:"browser"` | ||
| } | ||
|
|
||
| type Browser struct { | ||
| ChatIPEligibilityStatus *bool `json:"chat_ip_eligibility_status"` | ||
| } | ||
|
|
||
| type versionPath struct { | ||
| stable string | ||
| canary string | ||
| dev string | ||
| beta string | ||
| } | ||
|
|
||
| // getVersionAndUserDataPath 根据当前平台返回实际存在的 Edge 用户数据目录。 | ||
| func getVersionAndUserDataPath() (map[string]string, error) { | ||
| var paths versionPath | ||
|
|
||
|
|
@@ -99,6 +89,7 @@ func getVersionAndUserDataPath() (map[string]string, error) { | |
| return versionDataPaths, nil | ||
| } | ||
|
|
||
| // shutdownEdge 关闭当前正在运行的 Edge 进程,并返回被关闭进程的可执行文件路径。 | ||
| func shutdownEdge() ([]string, error) { | ||
| var terminatedEdges []string | ||
| procs, err := process.Processes() | ||
|
|
@@ -112,23 +103,16 @@ func shutdownEdge() ([]string, error) { | |
| continue | ||
| } | ||
|
|
||
| var isEdge bool | ||
| if runtime.GOOS == "darwin" { | ||
| isEdge = strings.HasPrefix(name, "Microsoft Edge") | ||
| } else { | ||
| isEdge = name == "msedge" | ||
| } | ||
|
|
||
| if !isEdge { | ||
| if !isEdgeProcessName(runtime.GOOS, name) { | ||
| continue | ||
| } | ||
|
|
||
| // Check if process is still running | ||
| // 仅处理仍在运行的主进程,避免误杀同名辅助进程。 | ||
| if running, _ := p.IsRunning(); !running { | ||
| continue | ||
| } | ||
|
|
||
| // Skip if parent has same name (avoid killing helper processes) | ||
| // 如果父进程同名,通常说明当前是子进程或 helper,直接跳过。 | ||
| if ppid, _ := p.Ppid(); ppid > 0 { | ||
| if parent, err := process.NewProcess(ppid); err == nil { | ||
| if parentName, err := parent.Name(); err == nil && parentName == name { | ||
|
|
@@ -150,6 +134,7 @@ func shutdownEdge() ([]string, error) { | |
| return terminatedEdges, nil | ||
| } | ||
|
|
||
| // getLastVersion 读取 Edge 用户目录中的 Last Version 文件。 | ||
| func getLastVersion(userDataPath string) (string, error) { | ||
| lastVersionFile := filepath.Join(userDataPath, "Last Version") | ||
| if _, err := os.Stat(lastVersionFile); os.IsNotExist(err) { | ||
|
|
@@ -164,30 +149,21 @@ func getLastVersion(userDataPath string) (string, error) { | |
| return strings.TrimSpace(string(data)), nil | ||
| } | ||
|
|
||
| // patchLocalState 仅修改 variations_country,同时保留 Local State 的其他字段。 | ||
| func patchLocalState(userDataPath string) error { | ||
| localStateFile := filepath.Join(userDataPath, "Local State") | ||
| if _, err := os.Stat(localStateFile); os.IsNotExist(err) { | ||
| return fmt.Errorf("failed to patch Local State. File not found: %s", localStateFile) | ||
| } | ||
|
|
||
| data, err := os.ReadFile(localStateFile) | ||
| localState, err := readJSONObject(localStateFile) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| var localState LocalState | ||
| if err := json.Unmarshal(data, &localState); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if localState.VariationsCountry != "US" { | ||
| localState.VariationsCountry = "US" | ||
| updatedData, err := json.MarshalIndent(localState, "", " ") | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if err := os.WriteFile(localStateFile, updatedData, 0644); err != nil { | ||
| if localState["variations_country"] != "US" { | ||
| localState["variations_country"] = "US" | ||
| if err := writeJSONObject(localStateFile, localState); err != nil { | ||
| return err | ||
| } | ||
| fmt.Println("Succeeded in patching Local State") | ||
|
|
@@ -198,14 +174,15 @@ func patchLocalState(userDataPath string) error { | |
| return nil | ||
| } | ||
|
|
||
| // patchPreferences 只处理 Default 和 Profile * 目录下的 Preferences 文件。 | ||
| func patchPreferences(userDataPath string) error { | ||
| entries, err := os.ReadDir(userDataPath) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| for _, entry := range entries { | ||
| if !entry.IsDir() && entry.Name() != "Default" && !strings.HasPrefix(entry.Name(), "Profile ") { | ||
| if !entry.IsDir() || !isTargetProfileDir(entry.Name()) { | ||
| continue | ||
| } | ||
|
|
||
|
|
@@ -214,29 +191,17 @@ func patchPreferences(userDataPath string) error { | |
| continue | ||
| } | ||
|
|
||
| data, err := os.ReadFile(preferencesFile) | ||
| preferences, err := readJSONObject(preferencesFile) | ||
| if err != nil { | ||
| continue | ||
| } | ||
|
|
||
| var preferences Preferences | ||
| if err := json.Unmarshal(data, &preferences); err != nil { | ||
| continue | ||
| } | ||
|
|
||
| // Check if we need to patch | ||
| if preferences.Browser.ChatIPEligibilityStatus == nil || !*preferences.Browser.ChatIPEligibilityStatus { | ||
| // Set to true | ||
| trueVal := true | ||
| preferences.Browser.ChatIPEligibilityStatus = &trueVal | ||
| // browser 可能不存在,这里按需补齐,避免覆盖其他顶层字段。 | ||
| browser := ensureObject(preferences, "browser") | ||
|
|
||
| updatedData, err := json.MarshalIndent(preferences, "", " ") | ||
| if err != nil { | ||
| fmt.Printf("Failed to marshal preferences for %s: %v\n", entry.Name(), err) | ||
| continue | ||
| } | ||
|
|
||
| if err := os.WriteFile(preferencesFile, updatedData, 0644); err != nil { | ||
| if browser["chat_ip_eligibility_status"] == nil || browser["chat_ip_eligibility_status"] == false { | ||
| browser["chat_ip_eligibility_status"] = true | ||
| if err := writeJSONObject(preferencesFile, preferences); err != nil { | ||
| fmt.Printf("Failed to write preferences for %s: %v\n", entry.Name(), err) | ||
| continue | ||
| } | ||
|
|
@@ -249,27 +214,109 @@ func patchPreferences(userDataPath string) error { | |
| return nil | ||
| } | ||
|
|
||
| // restartEdge 负责用已记录的可执行路径重新拉起 Edge。 | ||
| func restartEdge(terminatedEdges []string) { | ||
| for _, edge := range terminatedEdges { | ||
| var cmd *string | ||
| switch runtime.GOOS { | ||
| case "windows": | ||
| cmdStr := edge + " --start-maximized" | ||
| cmd = &cmdStr | ||
| case "darwin": | ||
| cmdStr := "open -a 'Microsoft Edge' --args --start-maximized" | ||
| cmd = &cmdStr | ||
| case "linux": | ||
| cmdStr := edge + " --start-maximized" | ||
| cmd = &cmdStr | ||
| if err := restartEdges(terminatedEdges, runtime.GOOS, func(name string, args ...string) error { | ||
| cmd := exec.Command(name, args...) | ||
| cmd.Stderr = io.Discard | ||
| return cmd.Start() | ||
| }); err != nil { | ||
| fmt.Fprintf(os.Stderr, "Warning: Failed to restart Edge: %v\n", err) | ||
| } | ||
| } | ||
|
|
||
| // isEdgeProcessName 兼容 msedge 与 msedge.exe,并保留 macOS 的前缀匹配行为。 | ||
| func isEdgeProcessName(goos string, name string) bool { | ||
| if goos == "darwin" { | ||
| return strings.HasPrefix(name, "Microsoft Edge") | ||
| } | ||
|
|
||
| base := strings.TrimSuffix(strings.ToLower(name), strings.ToLower(filepath.Ext(name))) | ||
| return base == "msedge" | ||
| } | ||
|
|
||
| // isTargetProfileDir 与 Python 版保持一致,只处理 Default 和 Profile *。 | ||
| func isTargetProfileDir(name string) bool { | ||
| return name == "Default" || strings.HasPrefix(name, "Profile ") | ||
| } | ||
|
|
||
| // readJSONObject 读取 JSON 对象文件,便于按字段做最小修改。 | ||
| func readJSONObject(path string) (map[string]any, error) { | ||
| data, err := os.ReadFile(path) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("read JSON file %s: %w", path, err) | ||
| } | ||
|
|
||
| var value map[string]any | ||
| if err := json.Unmarshal(data, &value); err != nil { | ||
| return nil, fmt.Errorf("unmarshal JSON file %s: %w", path, err) | ||
| } | ||
|
|
||
| return value, nil | ||
| } | ||
|
|
||
| // writeJSONObject 将完整对象写回文件,避免像结构体精简反序列化那样丢字段。 | ||
| func writeJSONObject(path string, value map[string]any) error { | ||
| data, err := json.Marshal(value) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal JSON file %s: %w", path, err) | ||
| } | ||
| if err := os.WriteFile(path, data, 0644); err != nil { | ||
| return fmt.Errorf("write JSON file %s: %w", path, err) | ||
| } | ||
| return nil | ||
| } | ||
|
Comment on lines
+259
to
+268
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function has two issues:
func writeJSONObject(path string, value map[string]any) error {
data, err := json.MarshalIndent(value, "", " ")
if err != nil {
return fmt.Errorf("marshal JSON file %s: %w", path, err)
}
tmpPath := path + ".tmp"
if err := os.WriteFile(tmpPath, data, 0644); err != nil {
return fmt.Errorf("write temp JSON file %s: %w", tmpPath, err)
}
if err := os.Rename(tmpPath, path); err != nil {
_ = os.Remove(tmpPath)
return fmt.Errorf("rename JSON file %s: %w", path, err)
}
return nil
} |
||
|
|
||
| // ensureObject 保证指定键是对象;若不存在或类型不对,则创建一个空对象。 | ||
| func ensureObject(parent map[string]any, key string) map[string]any { | ||
| if value, ok := parent[key].(map[string]any); ok { | ||
| return value | ||
| } | ||
|
|
||
| child := make(map[string]any) | ||
| parent[key] = child | ||
| return child | ||
| } | ||
|
|
||
| // restartEdges 会先去重,再按平台构造命令逐个重启 Edge。 | ||
| func restartEdges(terminatedEdges []string, goos string, runner func(name string, args ...string) error) error { | ||
| for _, edge := range uniqueStrings(terminatedEdges) { | ||
| name, args, ok := buildRestartCommand(edge, goos) | ||
| if !ok { | ||
| continue | ||
| } | ||
|
|
||
| if cmd != nil { | ||
| fmt.Printf("Starting: %s\n", edge) | ||
| // Note: In a real implementation, you'd use exec.Command here | ||
| // This is simplified for the example | ||
| fmt.Printf("Starting: %s\n", edge) | ||
| if err := runner(name, args...); err != nil { | ||
| return fmt.Errorf("restart edge %s: %w", edge, err) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // buildRestartCommand 将“可执行文件路径 + 参数”拆开,方便测试和注入 runner。 | ||
| func buildRestartCommand(edgePath, goos string) (string, []string, bool) { | ||
| switch goos { | ||
| case "windows", "linux", "darwin": | ||
| return edgePath, []string{"--start-maximized"}, true | ||
| default: | ||
| return "", nil, false | ||
| } | ||
| } | ||
|
|
||
| // uniqueStrings 保留原顺序去重,避免同一个 Edge 可执行文件被重复启动。 | ||
| func uniqueStrings(values []string) []string { | ||
| seen := make(map[string]struct{}, len(values)) | ||
| result := make([]string, 0, len(values)) | ||
| for _, value := range values { | ||
| if _, ok := seen[value]; ok { | ||
| continue | ||
| } | ||
| seen[value] = struct{}{} | ||
| result = append(result, value) | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| func main() { | ||
|
|
@@ -320,6 +367,9 @@ func main() { | |
| restartEdge(terminatedEdges) | ||
| } | ||
|
|
||
| // 保持与原脚本一致:执行结束后等待用户确认,便于查看输出。 | ||
| fmt.Print("\nPress Enter to continue...") | ||
| fmt.Scanln() | ||
| if _, err := fmt.Scanln(); err != nil { | ||
| fmt.Fprintf(os.Stderr, "Warning: failed to read input: %v\n", err) | ||
| } | ||
|
Comment on lines
+372
to
+374
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
fmt.Scanln() |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current heuristic
parentName == nameto skip helper processes works on Windows and Linux where helpers share the same executable name (msedge). However, on macOS, the main process is typically namedMicrosoft Edgewhile helpers are namedMicrosoft Edge Helper. This check will fail to identify helpers on macOS, causing them to be killed individually. UsingisEdgeProcessNamefor the parent name is a more robust approach across all platforms.