forked from jerrykrinock/CategoriesObjC
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NSData+FileAlias.m
320 lines (255 loc) · 8.78 KB
/
NSData+FileAlias.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#import "NSData+FileAlias.h"
#import "NSError+SSYAdds.h"
#import "NSError+LowLevel.h"
#import "SSYShellTasker.h"
#import "NSBundle+HelperPaths.h"
#import "NSKeyedUnarchiver+CatchExceptions.h"
//#import "DebugGuy.h"
//extern id debugGuyObject ;
__attribute__((visibility("default"))) NSString* const NSDataFileAliasAliasRecord = @"aliasRecord" ;
__attribute__((visibility("default"))) NSString* const NSDataFileAliasPath = @"path" ;
__attribute__((visibility("default"))) NSString* const NSDataFileAliasError = @"error" ;
NSString* const NSDataFileAliasWorkerName = @"FileAliasWorker" ;
#if 0
@interface SSYAliasResolver : NSObject {
}
@end
@implementation SSYAliasResolver
- (id) init {
self = [super init];
if (self != nil) {
// NSMutableDictionary* info = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
[NSThread detachNewThreadSelector:@selector(resolveAlias:)
toTarget:self
withObject:info] ;
}
return self;
}
- (NSString*)resolveAlias:(AliasHandle)handle {
OSErr osErr = FSResolveAlias(NULL,
(AliasHandle)handle,
&resolvedFSRef,
&changed) ;
}
@end
#endif
@implementation NSData (FileAlias)
+ (NSData*)aliasRecordFromPath:(NSString*)path {
if ([path length] == 0) {
return nil ;
}
const char* pathC = [path fileSystemRepresentation] ;
OSErr osErr ;
AliasHandle aliasHandle = NULL ;
osErr = FSNewAliasFromPath (
NULL,
pathC,
0,
&aliasHandle,
NULL
) ;
NSData* data = nil ;
if (
(osErr == noErr)
// ... File exists and we have a full alias
||
((osErr == fnfErr) && (aliasHandle != NULL))
// ... File does not exist and we have a minimal alias
) {
Size size = GetAliasSize(aliasHandle) ;
data = [NSData dataWithBytes:*aliasHandle
length:size] ;
}
return data ;
}
- (AliasHandle)aliasHandle {
unsigned short nBytesAliasRecord ;
/*
In Aliases.h, note that the AliasRecord struct is opaque if
MAC_OS_X_MIN_VERSION_REQUIRED >= MAC_OS_X_VERSION_10_4. In other
words, if the "Mac OS X Deployment Target" setting for your project is
10.4 or later, the AliasRecord struct is opaque.
That's because AliasRecords, as you've noticed, get written to disk but
are also referenced in data, which means that they often have to be
big-endian even on little-endian systems. Rather than enumerate the
cases in which you'd want big- or little-endian AliasRecords, we made
the data type opaque and added new APIs which deal in native-endian
data. They're Get/SetAliasUserType and GetAliasSize, and there are
also FromPtr versions of each if you have an AliasRecord * instead of
an AliasHandle.
Eric Albert, Apple */
nBytesAliasRecord = GetAliasSizeFromPtr((AliasPtr)[self bytes]);
AliasHandle handle ;
// Move the now-decoded data into the Handle.
if (PtrToHand([self bytes], (Handle*)&handle, nBytesAliasRecord) != noErr) {
// I don't think PtrToHandle can fail with virtual memory.
// This branch is probably just left over from the old days.
NSLog(@"Internal Error 526-0917") ;
return NULL ;
}
return handle ;
}
- (NSString*)pathFromAliasRecordWithTimeout:(NSTimeInterval)timeout
error_p:(NSError**)error_p {
NSDictionary* requestInfo = [NSDictionary dictionaryWithObject:self
forKey:NSDataFileAliasAliasRecord] ;
// Note: It is important that requestInfo and all of its keys and all
// of its values be encodeable. The only objects we put in there were
// an NSString key and an NSData value.
// Thus, we should be OK to do the following:
NSData* requestData = [NSKeyedArchiver archivedDataWithRootObject:requestInfo] ;
NSString* workerPath = [[NSBundle mainBundle] pathForHelper:NSDataFileAliasWorkerName] ;
NSData* responseData = nil ;
NSData* stderrData = nil ;
NSError* taskError = nil ;
NSInteger taskResult = [SSYShellTasker doShellTaskCommand:workerPath
arguments:nil
inDirectory:nil
stdinData:requestData
stdoutData_p:&responseData
stderrData_p:&stderrData
timeout:timeout
error_p:&taskError] ;
NSError* error = nil ;
NSString* path = nil ;
if (!responseData) {
error = SSYMakeError(59751, @"No stdout from helper") ;
error = [error errorByAddingUserInfoObject:[NSNumber numberWithInt:taskResult]
forKey:@"task result"] ;
error = [error errorByAddingUserInfoObject:stderrData
forKey:@"stderr"] ;
goto end ;
}
NSDictionary* responseInfo = [NSKeyedUnarchiver unarchiveObjectSafelyWithData:responseData] ;
if (!responseInfo) {
error = SSYMakeError(29170, @"Could not decode response from helper") ;
goto end ;
}
path = [responseInfo objectForKey:NSDataFileAliasPath] ;
if (!path) {
NSError* helperError = [responseInfo objectForKey:NSDataFileAliasError] ;
NSInteger errorCode = [helperError code] ;
if (
(errorCode == fnfErr) // Local file not found
||
(errorCode == nsvErr) // Remote file's volume not mounted and server not available
) {
// File referenced by our alias record does not exist at this time
// We can still extract the expected path from the alias.
AliasHandle handle = [self aliasHandle] ;
OSErr osErr = FSCopyAliasInfo (
handle,
NULL,
NULL,
(CFStringRef*)&(path),
NULL,
NULL
) ;
if (osErr != noErr) {
//NSLog(@"Extracted from minimal alias: path: %@", path) ;
// There may be a bug in the above function. If the alias is to
// that of a nonexistent directory in the root, for example,
// /Yousers
// Then the path returned will begin with two slashes.
// To work around that,
if ([path hasPrefix:@"//"]) {
path = [path substringFromIndex:1] ;
}
}
else {
error = SSYMakeError(26108, @"Helper returned error") ;
error = [error errorByAddingUnderlyingError:helperError] ;
}
}
else {
// This is an error we cannot work around
error = SSYMakeError(26195, @"Helper returned error") ;
error = [error errorByAddingUnderlyingError:helperError] ;
}
}
end:
if (error_p) {
error = [error errorByAddingUnderlyingError:taskError] ;
*error_p = error ;
}
return path ;
}
- (NSData*)resolveAliasWithInfo:(NSData*)requestData {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init] ;
NSError* error = nil ;
NSDictionary* requestInfo = [NSKeyedUnarchiver unarchiveObjectSafelyWithData:requestData] ;
if(!requestInfo) {
error = SSYMakeError(62608, @"Could not unarchive request") ;
goto end ;
}
NSString* path = nil ;
NSData* aliasRecord = [requestInfo objectForKey:NSDataFileAliasAliasRecord] ;
if(!aliasRecord) {
error = SSYMakeError(65838, @"No aliasRecord in request") ;
goto end ;
}
AliasHandle handle = [aliasRecord aliasHandle] ;
if (!handle) {
error = SSYMakeError(26238, @"Could not create AliasHandle") ;
goto end ;
}
// Try and resolve the alias
Boolean changed ;
FSRef resolvedFSRef;
OSErr osErr = FSResolveAlias(NULL,
(AliasHandle)handle,
&resolvedFSRef,
&changed) ;
if (osErr != noErr) {
error = [NSError errorWithMacErrorCode:osErr] ;
error = [error errorByAddingUserInfoObject:@"FSResolveAlias"
forKey:@"Function"] ;
goto end ;
}
// Alias was resolved. Now get its path from resolvedFSRef
char pathC[4096] ;
OSStatus osStatus = FSRefMakePath(
&resolvedFSRef,
(UInt8*)pathC,
sizeof(pathC)
) ;
if (osStatus != noErr) {
error = [NSError errorWithMacErrorCode:osStatus] ;
error = [error errorByAddingUserInfoObject:@"FSRefMakePath"
forKey:@"Function"] ;
goto end ;
}
path = [NSString stringWithCString:pathC
encoding:NSUTF8StringEncoding] ;
// The full path returned by FSRefMakePath will NOT have a trailing slash UNLESS
// the path is the root, i.e. @"/". In that case it will. Thus, in order to return
// a standard result to which "/Filename.ext" should be appended, we remove that:
if ([path length] == 1) {
path = @"" ;
}
end:
if (handle) {
DisposeHandle((Handle)handle);
}
NSDictionary* returnInfo ;
if (path) {
returnInfo = [NSDictionary dictionaryWithObject:path
forKey:NSDataFileAliasPath] ;
}
else if (error) {
returnInfo = [NSDictionary dictionaryWithObject:error
forKey:NSDataFileAliasError] ;
}
else {
NSLog(@"Internal Error 267-1857") ;
}
// Note: It is important that returnInfo and all of its keys and all
// of its values be encodeable. The only objects we put in there were
// NSString, and NSError whose userInfo dictionary contains only NSString
// keys and values. Thus, we should be OK to do the following:
NSData* returnData = [NSKeyedArchiver archivedDataWithRootObject:returnInfo] ;
[returnData retain] ;
[pool drain] ;
return returnData ;
}
@end