返回 DeepSeek-Reasonix
icon_repair_darwin.m
根目录 / desktop / icon_repair_darwin.m
1 //go:build darwin && cgo
2
3 #import <Foundation/Foundation.h>
4
5 #include <stdlib.h>
6 #include <string.h>
7
8 static void reasonix_set_error(char **output, NSError *error, NSString *fallback) {
9 if (output == NULL) {
10 return;
11 }
12 NSString *message = error.localizedDescription ?: fallback;
13 *output = strdup(message.UTF8String ?: "unknown macOS alias error");
14 }
15
16 static void reasonix_set_exception(char **output, NSException *exception, NSString *fallback) {
17 if (output == NULL) {
18 return;
19 }
20 NSString *message = exception.reason ?: exception.name ?: fallback;
21 *output = strdup(message.UTF8String ?: "unknown macOS alias exception");
22 }
23
24 static BOOL reasonix_is_alias(NSURL *url, NSError **error) {
25 NSNumber *value = nil;
26 if (![url getResourceValue:&value forKey:NSURLIsAliasFileKey error:error]) {
27 return NO;
28 }
29 return value.boolValue;
30 }
31
32 static int reasonix_create_alias(NSURL *targetURL, NSURL *aliasURL, char **error_message) {
33 NSError *error = nil;
34 NSData *bookmark = [targetURL bookmarkDataWithOptions:NSURLBookmarkCreationSuitableForBookmarkFile
35 includingResourceValuesForKeys:nil
36 relativeToURL:nil
37 error:&error];
38 if (bookmark == nil) {
39 reasonix_set_error(error_message, error, @"create Finder alias bookmark");
40 return -1;
41 }
42 if (![NSURL writeBookmarkData:bookmark toURL:aliasURL options:0 error:&error]) {
43 reasonix_set_error(error_message, error, @"write Finder alias bookmark");
44 return -1;
45 }
46 return 0;
47 }
48
49 // NSBundle requires a file URL and raises NSInvalidArgumentException for some
50 // Finder aliases that resolve to other schemes. Objective-C exceptions cannot
51 // cross the cgo boundary safely, so keep this classifier fail-closed and convert
52 // any unexpected Foundation exception into a normal native error.
53 static int reasonix_is_reasonix_bundle_url(NSURL *url, char **error_message) {
54 if (url == nil || !url.isFileURL) {
55 return 0;
56 }
57 @try {
58 NSBundle *bundle = [NSBundle bundleWithURL:url];
59 return [bundle.bundleIdentifier isEqualToString:@"com.wails.reasonix-desktop"] ? 1 : 0;
60 } @catch (NSException *exception) {
61 reasonix_set_exception(error_message, exception, @"inspect Finder alias target");
62 return -1;
63 }
64 }
65
66 // Kept as a narrow C entry point so Go regression tests exercise the exact
67 // native URL classifier used by startup alias repair.
68 int reasonix_is_reasonix_bundle_url_string(const char *url_string,
69 char **error_message) {
70 @autoreleasepool {
71 @try {
72 if (url_string == NULL) {
73 return 0;
74 }
75 NSString *value = [NSString stringWithUTF8String:url_string];
76 if (value == nil) {
77 return 0;
78 }
79 return reasonix_is_reasonix_bundle_url([NSURL URLWithString:value], error_message);
80 } @catch (NSException *exception) {
81 reasonix_set_exception(error_message, exception, @"parse Finder alias target");
82 return -1;
83 }
84 }
85 }
86
87 static int reasonix_write_alias_impl(const char *target_path, const char *alias_path,
88 char **error_message) {
89 NSURL *targetURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:target_path]];
90 NSURL *aliasURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:alias_path]];
91 return reasonix_create_alias(targetURL, aliasURL, error_message);
92 }
93
94 int reasonix_write_alias(const char *target_path, const char *alias_path,
95 char **error_message) {
96 @autoreleasepool {
97 @try {
98 return reasonix_write_alias_impl(target_path, alias_path, error_message);
99 } @catch (NSException *exception) {
100 reasonix_set_exception(error_message, exception, @"write Finder alias");
101 return -1;
102 }
103 }
104 }
105
106 static int reasonix_resolve_alias_impl(const char *alias_path, char **target_path,
107 char **error_message) {
108 NSError *error = nil;
109 NSURL *aliasURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:alias_path]];
110 NSURL *resolved = [NSURL URLByResolvingAliasFileAtURL:aliasURL
111 options:NSURLBookmarkResolutionWithoutUI |
112 NSURLBookmarkResolutionWithoutMounting
113 error:&error];
114 if (resolved == nil || !resolved.isFileURL) {
115 reasonix_set_error(error_message, error, @"resolve Finder alias to file URL");
116 return -1;
117 }
118 if (target_path != NULL) {
119 *target_path = strdup(resolved.path.UTF8String);
120 }
121 return 0;
122 }
123
124 int reasonix_resolve_alias(const char *alias_path, char **target_path,
125 char **error_message) {
126 @autoreleasepool {
127 @try {
128 return reasonix_resolve_alias_impl(alias_path, target_path, error_message);
129 } @catch (NSException *exception) {
130 reasonix_set_exception(error_message, exception, @"resolve Finder alias");
131 return -1;
132 }
133 }
134 }
135
136 static int reasonix_repair_alias_impl(const char *alias_path, const char *current_app_path,
137 int allow_broken, char **error_message) {
138 NSError *error = nil;
139 NSURL *aliasURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:alias_path]];
140 if (!reasonix_is_alias(aliasURL, &error)) {
141 // Missing metadata means this is an ordinary desktop file, not an
142 // owned integration failure. Never replace it merely by name.
143 return 0;
144 }
145
146 NSURL *currentURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:current_app_path]];
147 NSURL *resolved = [NSURL URLByResolvingAliasFileAtURL:aliasURL
148 options:NSURLBookmarkResolutionWithoutUI |
149 NSURLBookmarkResolutionWithoutMounting
150 error:&error];
151 BOOL owned = NO;
152 if (resolved != nil) {
153 if (!resolved.isFileURL) {
154 // URL-backed and network aliases are unrelated desktop
155 // integrations. Skipping them also keeps NSBundle from
156 // raising for a non-file URL during application startup.
157 return 0;
158 }
159 NSString *resolvedPath = resolved.URLByResolvingSymlinksInPath.standardizedURL.path;
160 NSString *currentPath = currentURL.URLByResolvingSymlinksInPath.standardizedURL.path;
161 if ([resolvedPath isEqualToString:currentPath]) {
162 // The alias is already healthy. Avoid rewriting it on every
163 // launch so Finder metadata and any user customization remain.
164 return 0;
165 }
166 int ownership = reasonix_is_reasonix_bundle_url(resolved, error_message);
167 if (ownership < 0) {
168 return -1;
169 }
170 owned = ownership == 1;
171 } else if (allow_broken != 0) {
172 // A broken alias has no resolvable target to prove ownership. Only
173 // the exact historical installer names are admitted by Go.
174 owned = YES;
175 }
176 if (!owned) {
177 return 0;
178 }
179
180 NSURL *directory = [aliasURL URLByDeletingLastPathComponent];
181 NSString *temporaryName = [NSString stringWithFormat:@".reasonix-alias-%@", NSUUID.UUID.UUIDString];
182 NSURL *temporaryURL = [directory URLByAppendingPathComponent:temporaryName];
183 if (reasonix_create_alias(currentURL, temporaryURL, error_message) != 0) {
184 return -1;
185 }
186
187 NSURL *resultingURL = nil;
188 if (![[NSFileManager defaultManager] replaceItemAtURL:aliasURL
189 withItemAtURL:temporaryURL
190 backupItemName:nil
191 options:NSFileManagerItemReplacementUsingNewMetadataOnly
192 resultingItemURL:&resultingURL
193 error:&error]) {
194 [[NSFileManager defaultManager] removeItemAtURL:temporaryURL error:nil];
195 reasonix_set_error(error_message, error, @"replace Finder alias");
196 return -1;
197 }
198 return 1;
199 }
200
201 int reasonix_repair_alias(const char *alias_path, const char *current_app_path,
202 int allow_broken, char **error_message) {
203 @autoreleasepool {
204 @try {
205 return reasonix_repair_alias_impl(alias_path, current_app_path, allow_broken, error_message);
206 } @catch (NSException *exception) {
207 reasonix_set_exception(error_message, exception, @"repair Finder alias");
208 return -1;
209 }
210 }
211 }
212
212 lines Plain Text