VLCMediaFileDiscoverer.m 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. //
  2. // VLCMediaFileDiscoverer.m
  3. // VLC for iOS
  4. //
  5. // Created by Gleb on 7/27/13.
  6. // Copyright (c) 2013 VideoLAN. All rights reserved.
  7. //
  8. // Refer to the COPYING file of the official project for license.
  9. //
  10. #import "VLCMediaFileDiscoverer.h"
  11. #import "NSString+SupportedMedia.h"
  12. const float MediaTimerInterval = 2.f;
  13. @interface VLCMediaFileDiscoverer () {
  14. NSMutableArray *_observers;
  15. dispatch_source_t _directorySource;
  16. NSString *_directoryPath;
  17. NSArray *_directoryFiles;
  18. NSMutableDictionary *_addedFilesMapping;
  19. NSTimer *_addMediaTimer;
  20. }
  21. @end
  22. @implementation VLCMediaFileDiscoverer
  23. - (id)init
  24. {
  25. self = [super init];
  26. if (self) {
  27. _observers = [NSMutableArray array];
  28. _addedFilesMapping = [NSMutableDictionary dictionary];
  29. }
  30. return self;
  31. }
  32. + (instancetype)sharedInstance
  33. {
  34. static dispatch_once_t onceToken;
  35. static VLCMediaFileDiscoverer *instance;
  36. dispatch_once(&onceToken, ^{
  37. instance = [VLCMediaFileDiscoverer new];
  38. });
  39. return instance;
  40. }
  41. #pragma mark - observation
  42. - (void)addObserver:(id<VLCMediaFileDiscovererDelegate>)delegate
  43. {
  44. [_observers addObject:delegate];
  45. }
  46. - (void)removeObserver:(id<VLCMediaFileDiscovererDelegate>)delegate
  47. {
  48. [_observers removeObject:delegate];
  49. }
  50. - (void)notifyFileDeleted:(NSString *)fileName
  51. {
  52. if (![fileName isSupportedMediaFormat] && ![fileName isSupportedAudioMediaFormat])
  53. return;
  54. for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
  55. if ([delegate respondsToSelector:@selector(mediaFileDeleted:)]) {
  56. [delegate mediaFileDeleted:[self filePath:fileName]];
  57. }
  58. }
  59. }
  60. - (void)notifyFileAdded:(NSString *)fileName loading:(BOOL)isLoading
  61. {
  62. for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
  63. if ([delegate respondsToSelector:@selector(mediaFileAdded:loading:)]) {
  64. [delegate mediaFileAdded:[self filePath:fileName] loading:isLoading];
  65. }
  66. }
  67. }
  68. - (void)notifySizeChanged:(NSString *)fileName size:(unsigned long long)size
  69. {
  70. for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
  71. if ([delegate respondsToSelector:@selector(mediaFileChanged:size:)]) {
  72. [delegate mediaFileChanged:[self filePath:fileName] size:size];
  73. }
  74. }
  75. }
  76. #pragma mark - discovering
  77. - (void)startDiscovering:(NSString *)directoryPath
  78. {
  79. _directoryPath = directoryPath;
  80. _directoryFiles = [self directoryFiles];
  81. int const folderDescriptor = open([directoryPath fileSystemRepresentation], O_EVTONLY);
  82. _directorySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, folderDescriptor,
  83. DISPATCH_VNODE_WRITE, DISPATCH_TARGET_QUEUE_DEFAULT);
  84. dispatch_source_set_event_handler(_directorySource, ^(){
  85. unsigned long const data = dispatch_source_get_data(_directorySource);
  86. if (data & DISPATCH_VNODE_WRITE) {
  87. // Do all the work on the main thread,
  88. // including timer scheduling, notifications delivering
  89. dispatch_async(dispatch_get_main_queue(), ^{
  90. [self directoryDidChange];
  91. });
  92. }
  93. });
  94. dispatch_source_set_cancel_handler(_directorySource, ^(){
  95. close(folderDescriptor);
  96. });
  97. dispatch_resume(_directorySource);
  98. }
  99. - (void)stopDiscovering
  100. {
  101. dispatch_source_cancel(_directorySource);
  102. [self invalidateTimer];
  103. }
  104. #pragma mark -
  105. - (NSArray *)directoryFiles
  106. {
  107. NSArray *foundFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:_directoryPath
  108. error:nil];
  109. return foundFiles;
  110. }
  111. - (NSString *)filePath:(NSString *)fileName
  112. {
  113. return [_directoryPath stringByAppendingPathComponent:fileName];
  114. }
  115. #pragma mark - directory watcher delegate
  116. - (void)directoryDidChange
  117. {
  118. NSArray *foundFiles = [self directoryFiles];
  119. if (_directoryFiles.count > foundFiles.count) { // File was deleted
  120. NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"not (self in %@)", foundFiles];
  121. NSArray *deletedFiles = [_directoryFiles filteredArrayUsingPredicate:filterPredicate];
  122. for (NSString *fileName in deletedFiles) {
  123. [self notifyFileDeleted:fileName];
  124. }
  125. } else if (_directoryFiles.count < foundFiles.count) { // File was added
  126. NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"not (self in %@)", _directoryFiles];
  127. NSArray *addedFiles = [foundFiles filteredArrayUsingPredicate:filterPredicate];
  128. for (NSString *fileName in addedFiles) {
  129. if ([fileName isSupportedMediaFormat] || [fileName isSupportedAudioMediaFormat]) {
  130. [_addedFilesMapping setObject:@(0) forKey:fileName];
  131. [self notifyFileAdded:fileName loading:YES];
  132. }
  133. }
  134. if (![_addMediaTimer isValid]) {
  135. _addMediaTimer = [NSTimer scheduledTimerWithTimeInterval:MediaTimerInterval
  136. target:self selector:@selector(addFileTimerFired)
  137. userInfo:nil repeats:YES];
  138. }
  139. }
  140. _directoryFiles = foundFiles;
  141. }
  142. #pragma mark - media timer
  143. - (void)addFileTimerFired
  144. {
  145. NSArray *allKeys = [_addedFilesMapping allKeys];
  146. NSFileManager *fileManager = [NSFileManager defaultManager];
  147. for (NSString *fileName in allKeys) {
  148. NSString *filePath = [self filePath:fileName];
  149. if (![fileManager fileExistsAtPath:filePath]) {
  150. [_addedFilesMapping removeObjectForKey:fileName];
  151. continue;
  152. }
  153. NSNumber *prevFetchedSize = [_addedFilesMapping objectForKey:fileName];
  154. NSDictionary *attribs = [fileManager attributesOfItemAtPath:filePath error:nil];
  155. NSNumber *updatedSize = [attribs objectForKey:NSFileSize];
  156. if (!updatedSize)
  157. continue;
  158. [self notifySizeChanged:fileName size:[updatedSize unsignedLongLongValue]];
  159. if ([prevFetchedSize compare:updatedSize] == NSOrderedSame) {
  160. [_addedFilesMapping removeObjectForKey:fileName];
  161. [self notifyFileAdded:fileName loading:NO];
  162. } else
  163. [_addedFilesMapping setObject:updatedSize forKey:fileName];
  164. }
  165. if (_addedFilesMapping.count == 0)
  166. [self invalidateTimer];
  167. }
  168. - (void)invalidateTimer
  169. {
  170. [_addMediaTimer invalidate];
  171. _addMediaTimer = nil;
  172. }
  173. @end