/ CFURLEnumerator.c
CFURLEnumerator.c
1 // 2 // CFURLEnumerator.c 3 // CoreFoundation 4 // 5 // Copyright (c) 2014 Apportable. All rights reserved. 6 // 7 8 #include "CFBase.h" 9 #include "CFRuntime.h" 10 #include "CFURLEnumerator.h" 11 #include "CFNumber.h" 12 #include <dirent.h> 13 #include <errno.h> 14 #include <sys/syslimits.h> 15 16 extern const CFStringRef NSURLErrorKey; 17 extern const CFStringRef NSFilePathErrorKey; 18 19 static CFIndex CFURLEnumeratorPushURL(CFURLEnumeratorRef enumerator, CFURLRef url, CFErrorRef *error); 20 static CFIndex CFURLEnumeratorPopURL(CFURLEnumeratorRef enumerator); 21 22 static CFStringRef fileInfoNameKey = CFSTR("name"); 23 static CFStringRef fileInfoIsDirKey = CFSTR("isDir"); 24 25 struct __CFURLEnumerator { 26 CFRuntimeBase _base; 27 CFURLRef directoryURL; 28 CFURLEnumeratorOptions options; 29 CFArrayRef propertyKeys; 30 CFMutableArrayRef urlStack; 31 CFMutableArrayRef dirFileInfos; 32 }; 33 34 CFComparisonResult _compareFileInfo(const void *fileInfo1, const void *fileInfo2, void *context) { 35 CFStringRef name1 = (CFStringRef)CFDictionaryGetValue(((CFMutableDictionaryRef) fileInfo1), fileInfoNameKey); 36 CFStringRef name2 = (CFStringRef)CFDictionaryGetValue(((CFMutableDictionaryRef) fileInfo2), fileInfoNameKey); 37 return CFStringCompare(name1, name2, kCFCompareCaseInsensitive); 38 } 39 40 static void __CFURLEnumeratorDeallocate(CFTypeRef cf) { 41 struct __CFURLEnumerator *enumerator = (struct __CFURLEnumerator *)cf; 42 CFRelease(enumerator->directoryURL); 43 44 if (enumerator->propertyKeys != NULL) { 45 CFRelease(enumerator->propertyKeys); 46 } 47 48 if (enumerator->urlStack) { 49 CFRelease(enumerator->urlStack); 50 } 51 52 if (enumerator->dirFileInfos) { 53 CFRelease(enumerator->dirFileInfos); 54 } 55 } 56 57 static CFTypeID __kCFURLEnumeratorTypeID = _kCFRuntimeNotATypeID; 58 59 static const CFRuntimeClass __CFURLEnumeratorClass = { 60 _kCFRuntimeScannedObject, 61 "CFURLEnumerator", 62 NULL, // init 63 NULL, // copy 64 __CFURLEnumeratorDeallocate, 65 NULL, 66 NULL, 67 NULL, 68 NULL 69 }; 70 71 static void __CFURLEnumeratorInitialize(void) { 72 __kCFURLEnumeratorTypeID = _CFRuntimeRegisterClass(&__CFURLEnumeratorClass); 73 } 74 75 CFTypeID CFURLEnumeratorGetTypeID(void) { 76 if (__kCFURLEnumeratorTypeID == _kCFRuntimeNotATypeID) { 77 __CFURLEnumeratorInitialize(); 78 } 79 return __kCFURLEnumeratorTypeID; 80 } 81 82 static struct __CFURLEnumerator *_CFURLEnumeratorCreate(CFAllocatorRef allocator) { 83 CFIndex size = sizeof(struct __CFURLEnumerator) - sizeof(CFRuntimeBase); 84 return (struct __CFURLEnumerator *)_CFRuntimeCreateInstance(allocator, CFURLEnumeratorGetTypeID(), size, NULL); 85 } 86 87 static void cocoaError(CFErrorRef *error, CFIndex code, CFURLRef url, CFStringRef path) { 88 if (error) { 89 const CFStringRef keys[2] = { 90 NSURLErrorKey, 91 NSFilePathErrorKey 92 }; 93 CFTypeRef values[2] = { 94 url, 95 path, 96 }; 97 *error = CFErrorCreateWithUserInfoKeysAndValues(kCFAllocatorDefault, kCFErrorDomainCocoa, code, (const void *const *)keys, (const void *const *)values, 2); 98 } 99 } 100 101 static void posixError(CFErrorRef *error, CFURLRef url, CFStringRef path) { 102 if (error) { 103 const CFStringRef keys[3] = { 104 kCFErrorUnderlyingErrorKey, 105 NSURLErrorKey, 106 NSFilePathErrorKey 107 }; 108 CFStringRef err = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s"), strerror(errno)); 109 CFTypeRef values[3] = { 110 err, 111 url, 112 path, 113 }; 114 *error = CFErrorCreateWithUserInfoKeysAndValues(kCFAllocatorDefault, kCFErrorDomainPOSIX, errno, (const void *const *)keys, (const void *const *)values, 3); 115 CFRelease(err); 116 } 117 } 118 119 static CFIndex CFURLEnumeratorPushURL(CFURLEnumeratorRef enumerator, CFURLRef url, CFErrorRef *error) { 120 char path[PATH_MAX] = { 0 }; 121 CFStringRef urlPath = CFURLCopyPath(url); 122 Boolean success = CFStringGetFileSystemRepresentation(urlPath, path, PATH_MAX); 123 124 if (!success) { 125 cocoaError(error, -1, url, urlPath); 126 CFRelease(urlPath); 127 return kCFNotFound; 128 } 129 130 DIR *dir = opendir(path); 131 if (dir == NULL) { 132 posixError(error, url, urlPath); 133 CFRelease(urlPath); 134 return kCFNotFound; 135 } 136 137 CFMutableArrayRef fileInfos = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); 138 139 struct dirent *current = NULL; 140 while ((current = readdir(dir)) != NULL) { 141 if (strcmp(current->d_name, ".") == 0 || strcmp(current->d_name, "..") == 0) { 142 continue; 143 } 144 CFMutableDictionaryRef fileInfo = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 145 CFStringRef fileName = CFStringCreateWithBytes(kCFAllocatorDefault, current->d_name, strlen(current->d_name), kCFStringEncodingUTF8, false); 146 CFDictionarySetValue(fileInfo, fileInfoNameKey, fileName); 147 CFDictionarySetValue(fileInfo, fileInfoIsDirKey, current->d_type == DT_DIR ? kCFBooleanTrue : kCFBooleanFalse); 148 CFArrayAppendValue(fileInfos, fileInfo); 149 CFRelease(fileName); 150 CFRelease(fileInfo); 151 } 152 153 CFArraySortValues(fileInfos, CFRangeMake(0, CFArrayGetCount(fileInfos)), _compareFileInfo, nil); 154 CFArrayAppendValue(enumerator->urlStack, url); 155 CFArrayAppendValue(enumerator->dirFileInfos, fileInfos); 156 CFRelease(urlPath); 157 CFRelease(fileInfos); 158 closedir(dir); 159 return CFArrayGetCount(enumerator->urlStack); 160 } 161 162 static CFDictionaryRef CFURLEnumeratorDequeueFileInfo(CFURLEnumeratorRef enumerator) { 163 CFIndex count = CFArrayGetCount(enumerator->dirFileInfos); 164 if (count > 0) { 165 CFMutableArrayRef fileInfos = (CFMutableArrayRef)CFArrayGetValueAtIndex(enumerator->dirFileInfos, count - 1); 166 count = CFArrayGetCount(fileInfos); 167 if (count > 0) { 168 CFDictionaryRef fileInfo = (CFDictionaryRef)CFArrayGetValueAtIndex(fileInfos, 0); 169 CFRetain(fileInfo); 170 CFArrayRemoveValueAtIndex(fileInfos, 0); 171 return fileInfo; 172 } else { 173 return NULL; 174 } 175 } else { 176 return NULL; 177 } 178 } 179 180 static CFIndex CFURLEnumeratorPopURL(CFURLEnumeratorRef enumerator) { 181 CFIndex count = CFArrayGetCount(enumerator->urlStack); 182 if (count > 0) { 183 CFArrayRemoveValueAtIndex(enumerator->urlStack, count - 1); 184 CFArrayRemoveValueAtIndex(enumerator->dirFileInfos, count - 1); 185 return count - 1; 186 } else { 187 return 0; 188 } 189 } 190 191 static CFIndex CFURLEnumeratorPeek(CFURLEnumeratorRef enumerator, CFURLRef *url) { 192 CFIndex count = CFArrayGetCount(enumerator->urlStack); 193 194 if (url != NULL) { 195 *url = NULL; 196 } 197 198 if (count > 0) { 199 if (url != NULL) { 200 *url = (CFURLRef)CFArrayGetValueAtIndex(enumerator->urlStack, count - 1); 201 } 202 } 203 204 return count; 205 } 206 207 CFURLEnumeratorRef CFURLEnumeratorCreateForDirectoryURL(CFAllocatorRef alloc, CFURLRef directoryURL, CFURLEnumeratorOptions option, CFArrayRef propertyKeys) { 208 struct __CFURLEnumerator *enumerator = _CFURLEnumeratorCreate(alloc); 209 enumerator->directoryURL = (CFURLRef)CFRetain(directoryURL); 210 enumerator->options = option; 211 if (propertyKeys != NULL) { 212 enumerator->propertyKeys = CFArrayCreateCopy(alloc, propertyKeys); 213 } 214 enumerator->urlStack = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); 215 enumerator->dirFileInfos = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); 216 217 if (CFURLEnumeratorPushURL(enumerator, directoryURL, NULL) <= 0) { 218 CFRelease(enumerator); 219 return NULL; 220 } 221 222 return (CFURLEnumeratorRef)enumerator; 223 } 224 225 CFURLEnumeratorResult CFURLEnumeratorGetNextURL(CFURLEnumeratorRef enumer, CFURLRef *url, CFErrorRef *error) { 226 struct __CFURLEnumerator *enumerator = (struct __CFURLEnumerator *)enumer; 227 228 if (url != NULL) { 229 *url = NULL; 230 } 231 232 if (error != NULL) { 233 *error = NULL; 234 } 235 236 CFIndex count = 0; 237 CFDictionaryRef fileInfo = NULL; 238 CFURLRef parent = NULL; 239 do { 240 CFURLEnumeratorPeek(enumerator, &parent); 241 if (parent != NULL) { 242 fileInfo = CFURLEnumeratorDequeueFileInfo(enumerator); 243 } 244 245 if (fileInfo == NULL) { 246 count = CFURLEnumeratorPopURL(enumerator); 247 } 248 else { 249 count = 0; 250 } 251 } while (count > 0); 252 253 if (fileInfo == NULL || parent == NULL) { // the parent being null might be an error if it happens... it doesnt seem possible however 254 return kCFURLEnumeratorEnd; 255 } 256 257 Boolean isDir = CFBooleanGetValue(CFDictionaryGetValue(fileInfo, fileInfoIsDirKey)); 258 CFURLRef item = NULL; 259 260 if (url != NULL) { 261 CFStringRef name = (CFStringRef)CFDictionaryGetValue(fileInfo, fileInfoNameKey); 262 item = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, parent, name, isDir); 263 *url = item; 264 } 265 266 if (fileInfo) { 267 CFRelease(fileInfo); 268 } 269 270 if (isDir && (enumerator->options & kCFURLEnumeratorDescendRecursively) == 0) { 271 if (CFURLEnumeratorPushURL(enumerator, item, error) <= 0) { 272 return kCFURLEnumeratorError; // error populated by push 273 } 274 } 275 276 if (enumerator->propertyKeys) { 277 CFDictionaryRef properties = CFURLCopyResourcePropertiesForKeys(item, enumerator->propertyKeys, error); 278 if (properties != NULL) { 279 CFRelease(properties); 280 return kCFURLEnumeratorSuccess; 281 } else { 282 return kCFURLEnumeratorError; 283 } 284 } else { 285 return kCFURLEnumeratorSuccess; 286 } 287 } 288 289 void CFURLEnumeratorSkipDescendents(CFURLEnumeratorRef enumer) { 290 struct __CFURLEnumerator *enumerator = (struct __CFURLEnumerator *)enumer; 291 enumerator->options &= ~(kCFURLEnumeratorDescendRecursively); 292 } 293 294 CFIndex CFURLEnumeratorGetDescendentLevel(CFURLEnumeratorRef enumerator) { 295 if (enumerator->urlStack == NULL) { 296 return 0; 297 } 298 return CFArrayGetCount(enumerator->urlStack) + 1; 299 } 300 301 /* 302 Boolean CFURLEnumeratorGetSourceDidChange(CFURLEnumeratorRef enumerator) { 303 return false; 304 } 305 */