/ 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  */