Reviewed by Kevin.
[WebKit-https.git] / WebKit / Misc.subproj / WebNSFileManagerExtras.m
1 /*
2     WebNSFileManagerExtras.m
3     Private (SPI) header
4     Copyright 2003, Apple, Inc. All rights reserved.
5  */
6
7 #import <WebKit/WebNSFileManagerExtras.h>
8
9 #import <WebKit/WebAssertions.h>
10 #import <WebKit/WebKitNSStringExtras.h>
11
12 #import <sys/mount.h>
13
14 @implementation NSFileManager (WebNSFileManagerExtras)
15
16 - (BOOL)_webkit_fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory traverseLink:(BOOL)flag
17 {
18     BOOL result;
19     NSDictionary *attributes;
20
21     result = NO;
22     if (isDirectory) {
23         *isDirectory = NO;
24     }
25
26     attributes = [self fileAttributesAtPath:path traverseLink:flag];
27
28     if (attributes) {
29         result = YES;
30         if ([[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]) {
31             if (isDirectory) {
32                 *isDirectory = YES;
33             }
34         }
35     }
36
37     return result;
38 }
39
40 - (BOOL)_webkit_createIntermediateDirectoriesForPath:(NSString *)path attributes:(NSDictionary *)attributes
41 {
42     BOOL result;
43     NSArray *pathComponents;
44     BOOL isDir;
45     unsigned count;
46     unsigned i;
47     NSString *checkPath;
48     NSMutableString *subpath;
49             
50     if (!path || [path length] == 0 || ![path isAbsolutePath]) {
51         return NO;
52     }
53
54     result = NO;  
55
56     // check to see if the path to the file already exists        
57     if ([self _webkit_fileExistsAtPath:[path stringByDeletingLastPathComponent] isDirectory:&isDir traverseLink:YES]) {
58         if (isDir) {
59             result = YES;
60         }
61         else {
62             result = NO;
63         }
64     }
65     else {
66         // create the path to the file
67         result = YES;  
68
69         // assume that most of the path exists, look backwards until we find an existing subpath
70         checkPath = path;
71         while (![checkPath isEqualToString:@"/"]) {
72             checkPath = [checkPath stringByDeletingLastPathComponent];
73             if ([self _webkit_fileExistsAtPath:checkPath isDirectory:&isDir traverseLink:YES]) {
74                 if (isDir) {
75                     break;
76                 }
77                 else {
78                     // found a leaf node, can't continue
79                     result = NO;
80                     break;
81                 }
82             }
83         }
84
85         if (result) {
86             // now build up the path to the point where we found existing paths
87             subpath = [[NSMutableString alloc] initWithCapacity:[path length]];
88             pathComponents = [path componentsSeparatedByString:@"/"];    
89             count = [pathComponents count];
90             i = 0;
91             while (i < count - 1 && ![subpath isEqualToString:checkPath]) {
92                 if (i > 0) {
93                     [subpath appendString:@"/"];
94                 }
95                 [subpath appendString:[pathComponents objectAtIndex:i]];  
96                 i++;  
97             }
98             
99             // now create the parts of the path that did not yet exist
100             while (i < count - 1) {
101                 if ([(NSString *)[pathComponents objectAtIndex:i] length] == 0) {
102                     continue;
103                 }
104                 if (i > 0) {
105                     [subpath appendString:@"/"];
106                 }
107                 [subpath appendString:[pathComponents objectAtIndex:i]];
108                 
109                 // does this directory exist?
110                 if ([self _webkit_fileExistsAtPath:subpath isDirectory:&isDir traverseLink:YES]) {
111                     if (!isDir) {
112                         // ran into a leaf node of some sort
113                         result = NO;
114                         break;
115                     }
116                 }
117                 else {
118                     // subpath does not exist - create it
119                     if (![self createDirectoryAtPath:subpath attributes:attributes]) {
120                         // failed to create subpath
121                         result = NO;
122                         break;
123                     }
124                 }
125                 i++; 
126             }
127             
128             [subpath release];
129         }
130         
131     }    
132                             
133     return result;
134 }
135
136 - (BOOL)_webkit_createDirectoryAtPathWithIntermediateDirectories:(NSString *)path attributes:(NSDictionary *)attributes
137 {
138     // Be really optimistic - assume that in the common case, the directory exists.
139     BOOL isDirectory;
140     if ([self fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory) {
141         return YES;
142     }
143
144     // Assume the next most common case is that the parent directory already exists
145     if ([self createDirectoryAtPath:path attributes:attributes]) {
146         return YES;
147     }
148
149     // Do it the hard way 
150     return [self _webkit_createIntermediateDirectoriesForPath:path attributes:attributes] && [self createDirectoryAtPath:path attributes:attributes];
151 }
152
153 - (BOOL)_webkit_createFileAtPathWithIntermediateDirectories:(NSString *)path contents:(NSData *)contents attributes:(NSDictionary *)attributes directoryAttributes:(NSDictionary *)directoryAttributes
154 {
155     // Be optimistic - try just creating the file first, assuming intermediate directories exist. 
156     if ([self createFileAtPath:path contents:contents attributes:attributes]) {
157         return YES;
158     }
159
160     return ([self _webkit_createIntermediateDirectoriesForPath:path attributes:directoryAttributes] && [self createFileAtPath:path contents:contents attributes:attributes]);
161 }
162
163 - (BOOL)_webkit_removeFileOnlyAtPath:(NSString *)path
164 {
165     struct statfs buf;
166     BOOL result = unlink([path fileSystemRepresentation]) == 0;
167
168     // For mysterious reasons, MNT_DOVOLFS is the flag for "supports resource fork"
169     if ((statfs([path fileSystemRepresentation], &buf) == 0) && !(buf.f_flags & MNT_DOVOLFS)) {
170         NSString *lastPathComponent = [path lastPathComponent];
171         if ([lastPathComponent length] != 0 && ![lastPathComponent isEqualToString:@"/"]) {
172             NSString *resourcePath = [[path stringByDeletingLastPathComponent] stringByAppendingString:[@"._" stringByAppendingString:lastPathComponent]];
173             if (unlink([resourcePath fileSystemRepresentation]) != 0) {
174                 result = NO;
175             }
176         }
177     }
178
179     return result;
180 }
181
182 - (void)_webkit_backgroundRemoveFileAtPath:(NSString *)path
183 {
184     NSFileManager *manager;
185     NSString *moveToSubpath;
186     NSString *moveToPath;
187     int i;
188     
189     manager = [NSFileManager defaultManager];
190     
191     i = 0;
192     moveToSubpath = [path stringByDeletingLastPathComponent];
193     do {
194         moveToPath = [NSString stringWithFormat:@"%@/.tmp%d", moveToSubpath, i];
195         i++;
196     } while ([manager fileExistsAtPath:moveToPath]);
197
198     if ([manager movePath:path toPath:moveToPath handler:nil]) {
199         [NSThread detachNewThreadSelector:@selector(_performRemoveFileAtPath:) toTarget:self withObject:moveToPath];
200     }
201
202 }
203
204 - (void)_webkit_backgroundRemoveLeftoverFiles:(NSString *)path
205 {
206     NSFileManager *manager;
207     NSString *leftoverSubpath;
208     NSString *leftoverPath;
209     int i;
210     
211     manager = [NSFileManager defaultManager];
212     leftoverSubpath = [path stringByDeletingLastPathComponent];
213     
214     i = 0;
215     while (1) {
216         leftoverPath = [NSString stringWithFormat:@"%@/.tmp%d", leftoverSubpath, i];
217         if (![manager fileExistsAtPath:leftoverPath]) {
218             break;
219         }
220         [NSThread detachNewThreadSelector:@selector(_performRemoveFileAtPath:) toTarget:self withObject:leftoverPath];
221         i++;
222     }
223 }
224
225 - (NSString *)_webkit_carbonPathForPath:(NSString *)posixPath
226 {
227     OSStatus error;
228     FSRef ref, rootRef, parentRef;
229     FSCatalogInfo info;
230     NSMutableArray *carbonPathPieces;
231     HFSUniStr255 nameString;
232
233     // Make an FSRef.
234     error = FSPathMakeRef((const UInt8 *)[posixPath fileSystemRepresentation], &ref, NULL);
235     if (error != noErr) {
236         return nil;
237     }
238
239     // Get volume refNum.
240     error = FSGetCatalogInfo(&ref, kFSCatInfoVolume, &info, NULL, NULL, NULL);
241     if (error != noErr) {
242         return nil;
243     }
244
245     // Get root directory FSRef.
246     error = FSGetVolumeInfo(info.volume, 0, NULL, kFSVolInfoNone, NULL, NULL, &rootRef);
247     if (error != noErr) {
248         return nil;
249     }
250
251     // Get the pieces of the path.
252     carbonPathPieces = [NSMutableArray array];
253     for (;;) {
254         error = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, &nameString, NULL, &parentRef);
255         if (error != noErr) {
256             return nil;
257         }
258         [carbonPathPieces insertObject:[NSString stringWithCharacters:nameString.unicode length:nameString.length] atIndex:0];
259         if (FSCompareFSRefs(&ref, &rootRef) == noErr) {
260             break;
261         }
262         ref = parentRef;
263     }
264
265     // Volume names need trailing : character.
266     if ([carbonPathPieces count] == 1) {
267         [carbonPathPieces addObject:@""];
268     }
269
270     return [carbonPathPieces componentsJoinedByString:@":"];
271 }
272
273 - (NSString *)_webkit_startupVolumeName
274 {
275     NSString *path = [self _webkit_carbonPathForPath:@"/"];
276     return [path substringToIndex:[path length]-1];
277 }
278
279 - (NSString *)_webkit_pathWithUniqueFilenameForPath:(NSString *)path
280 {
281     // "Fix" the filename of the path.
282     NSString *filename = [[path lastPathComponent] _webkit_filenameByFixingIllegalCharacters];
283     path = [[path stringByDeletingLastPathComponent] stringByAppendingPathComponent:filename];
284
285     NSFileManager *fileManager = [NSFileManager defaultManager];
286     if ([fileManager fileExistsAtPath:path]) {
287         // Don't overwrite existing file by appending "-n", "-n.ext" or "-n.ext.ext" to the filename.
288         NSString *extensions = nil;
289         NSString *pathWithoutExtensions;
290         NSString *lastPathComponent = [path lastPathComponent];
291         NSRange periodRange = [lastPathComponent rangeOfString:@"."];
292         
293         if (periodRange.location == NSNotFound) {
294             pathWithoutExtensions = path;
295         } else {
296             extensions = [lastPathComponent substringFromIndex:periodRange.location + 1];
297             lastPathComponent = [lastPathComponent substringToIndex:periodRange.location];
298             pathWithoutExtensions = [[path stringByDeletingLastPathComponent] stringByAppendingPathComponent:lastPathComponent];
299         }
300
301         NSString *pathWithAppendedNumber;
302         unsigned i;
303
304         for (i = 1; 1; i++) {
305             pathWithAppendedNumber = [NSString stringWithFormat:@"%@-%d", pathWithoutExtensions, i];
306             path = [extensions length] ? [pathWithAppendedNumber stringByAppendingPathExtension:extensions] : pathWithAppendedNumber;
307             if (![fileManager fileExistsAtPath:path]) {
308                 break;
309             }
310         }
311     }
312
313     return path;
314 }
315
316 @end