VLCMediaFileDiscoverer.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /*****************************************************************************
  2. * VLCMediaFileDiscoverer.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Gleb Pinigin <gpinigin # gmail.com>
  9. * Felix Paul Kühne <fkuehne # videolan.org>
  10. *
  11. * Refer to the COPYING file of the official project for license.
  12. *****************************************************************************/
  13. #import "VLCMediaFileDiscoverer.h"
  14. #import "NSString+SupportedMedia.h"
  15. const float MediaTimerInterval = 2.f;
  16. @interface VLCMediaFileDiscoverer () {
  17. NSMutableArray *_observers;
  18. dispatch_source_t _directorySource;
  19. NSString *_directoryPath;
  20. NSArray *_directoryFiles;
  21. NSMutableDictionary *_addedFilesMapping;
  22. NSTimer *_addMediaTimer;
  23. }
  24. @end
  25. @implementation VLCMediaFileDiscoverer
  26. - (id)init
  27. {
  28. self = [super init];
  29. if (self) {
  30. _observers = [NSMutableArray array];
  31. _addedFilesMapping = [NSMutableDictionary dictionary];
  32. }
  33. return self;
  34. }
  35. + (instancetype)sharedInstance
  36. {
  37. static dispatch_once_t onceToken;
  38. static VLCMediaFileDiscoverer *instance;
  39. dispatch_once(&onceToken, ^{
  40. instance = [VLCMediaFileDiscoverer new];
  41. });
  42. return instance;
  43. }
  44. #pragma mark - observation
  45. - (void)addObserver:(id<VLCMediaFileDiscovererDelegate>)delegate
  46. {
  47. [_observers addObject:delegate];
  48. }
  49. - (void)removeObserver:(id<VLCMediaFileDiscovererDelegate>)delegate
  50. {
  51. [_observers removeObject:delegate];
  52. }
  53. - (void)notifyFileDeleted:(NSString *)fileName
  54. {
  55. for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
  56. if ([delegate respondsToSelector:@selector(mediaFileDeleted:)]) {
  57. [delegate mediaFileDeleted:[self filePath:fileName]];
  58. }
  59. }
  60. }
  61. - (void)notifyFileAdded:(NSString *)fileName loading:(BOOL)isLoading
  62. {
  63. for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
  64. if ([delegate respondsToSelector:@selector(mediaFileAdded:loading:)]) {
  65. [delegate mediaFileAdded:[self filePath:fileName] loading:isLoading];
  66. }
  67. }
  68. }
  69. - (void)notifySizeChanged:(NSString *)fileName size:(unsigned long long)size
  70. {
  71. for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
  72. if ([delegate respondsToSelector:@selector(mediaFileChanged:size:)]) {
  73. [delegate mediaFileChanged:[self filePath:fileName] size:size];
  74. }
  75. }
  76. }
  77. #pragma mark - discovering
  78. - (void)startDiscovering
  79. {
  80. _directoryPath = [self directoryPath];
  81. _directoryFiles = [self directoryFiles];
  82. int const folderDescriptor = open([_directoryPath fileSystemRepresentation], O_EVTONLY);
  83. _directorySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, folderDescriptor,
  84. DISPATCH_VNODE_WRITE, DISPATCH_TARGET_QUEUE_DEFAULT);
  85. dispatch_source_set_event_handler(_directorySource, ^(){
  86. unsigned long const data = dispatch_source_get_data(_directorySource);
  87. if (data & DISPATCH_VNODE_WRITE) {
  88. // Do all the work on the main thread,
  89. // including timer scheduling, notifications delivering
  90. dispatch_async(dispatch_get_main_queue(), ^{
  91. [self directoryDidChange];
  92. });
  93. }
  94. });
  95. dispatch_source_set_cancel_handler(_directorySource, ^(){
  96. close(folderDescriptor);
  97. });
  98. dispatch_resume(_directorySource);
  99. }
  100. - (void)stopDiscovering
  101. {
  102. dispatch_source_cancel(_directorySource);
  103. [self invalidateTimer];
  104. }
  105. #pragma mark -
  106. - (NSArray *)directoryFiles
  107. {
  108. NSArray *foundFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:_directoryPath
  109. error:nil];
  110. return foundFiles;
  111. }
  112. - (NSString *)filePath:(NSString *)fileName
  113. {
  114. return [_directoryPath stringByAppendingPathComponent:fileName];
  115. }
  116. #pragma mark - directory watcher delegate
  117. - (NSString *)directoryPath
  118. {
  119. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  120. NSString *directoryPath = searchPaths[0];
  121. return directoryPath;
  122. }
  123. - (void)directoryDidChange
  124. {
  125. NSArray *foundFiles = [self directoryFiles];
  126. if (_directoryFiles.count > foundFiles.count) { // File was deleted
  127. NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"not (self in %@)", foundFiles];
  128. NSArray *deletedFiles = [_directoryFiles filteredArrayUsingPredicate:filterPredicate];
  129. for (NSString *fileName in deletedFiles)
  130. [self notifyFileDeleted:fileName];
  131. } else if (_directoryFiles.count < foundFiles.count) { // File was added
  132. NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"not (self in %@)", _directoryFiles];
  133. NSMutableArray *addedFiles = [NSMutableArray arrayWithArray:[foundFiles filteredArrayUsingPredicate:filterPredicate]];
  134. for (NSString *fileName in addedFiles) {
  135. if ([fileName isSupportedMediaFormat] || [fileName isSupportedAudioMediaFormat]) {
  136. [_addedFilesMapping setObject:@(0) forKey:fileName];
  137. [self notifyFileAdded:fileName loading:YES];
  138. } else {
  139. BOOL isDirectory = NO;
  140. NSString *directoryPath = [self directoryPath];
  141. NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
  142. BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
  143. // add folders
  144. if (exists && isDirectory) {
  145. NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:filePath error:nil];
  146. for (NSString* file in files) {
  147. NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:file];
  148. isDirectory = NO;
  149. exists = [[NSFileManager defaultManager] fileExistsAtPath:fullFilePath isDirectory:&isDirectory];
  150. //only add folders or files in folders
  151. if ((exists && isDirectory) || ![filePath.lastPathComponent isEqualToString:@"Documents"]) {
  152. NSString *folderpath = [filePath stringByReplacingOccurrencesOfString:directoryPath withString:@""];
  153. if (![folderpath isEqualToString:@""]) {
  154. folderpath = [folderpath stringByAppendingString:@"/"];
  155. }
  156. NSString *path = [folderpath stringByAppendingString:file];
  157. [_addedFilesMapping setObject:@(0) forKey:path];
  158. [self notifyFileAdded:path loading:YES];
  159. }
  160. }
  161. }
  162. }
  163. }
  164. if (![_addMediaTimer isValid]) {
  165. _addMediaTimer = [NSTimer scheduledTimerWithTimeInterval:MediaTimerInterval
  166. target:self selector:@selector(addFileTimerFired)
  167. userInfo:nil repeats:YES];
  168. }
  169. }
  170. _directoryFiles = foundFiles;
  171. }
  172. #pragma mark - media timer
  173. - (void)addFileTimerFired
  174. {
  175. NSArray *allKeys = [_addedFilesMapping allKeys];
  176. NSFileManager *fileManager = [NSFileManager defaultManager];
  177. for (NSString *fileName in allKeys) {
  178. NSString *filePath = [self filePath:fileName];
  179. if (![fileManager fileExistsAtPath:filePath]) {
  180. [_addedFilesMapping removeObjectForKey:fileName];
  181. continue;
  182. }
  183. NSNumber *prevFetchedSize = [_addedFilesMapping objectForKey:fileName];
  184. NSDictionary *attribs = [fileManager attributesOfItemAtPath:filePath error:nil];
  185. NSNumber *updatedSize = [attribs objectForKey:NSFileSize];
  186. if (!updatedSize)
  187. continue;
  188. [self notifySizeChanged:fileName size:[updatedSize unsignedLongLongValue]];
  189. if ([prevFetchedSize compare:updatedSize] == NSOrderedSame) {
  190. [_addedFilesMapping removeObjectForKey:fileName];
  191. [self notifyFileAdded:fileName loading:NO];
  192. } else
  193. [_addedFilesMapping setObject:updatedSize forKey:fileName];
  194. }
  195. if (_addedFilesMapping.count == 0)
  196. [self invalidateTimer];
  197. }
  198. - (void)invalidateTimer
  199. {
  200. [_addMediaTimer invalidate];
  201. _addMediaTimer = nil;
  202. }
  203. #pragma mark - media list management
  204. - (void)updateMediaList
  205. {
  206. if (![NSThread isMainThread]) {
  207. [self performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
  208. return;
  209. }
  210. NSString *directoryPath = [self directoryPath];
  211. NSMutableArray *foundFiles = [NSMutableArray arrayWithArray:[[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:nil]];
  212. NSMutableArray *filePaths = [NSMutableArray array];
  213. NSURL *fileURL;
  214. while (foundFiles.count) {
  215. NSString *fileName = foundFiles.firstObject;
  216. NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
  217. [foundFiles removeObject:fileName];
  218. if ([fileName isSupportedMediaFormat] || [fileName isSupportedAudioMediaFormat]) {
  219. [filePaths addObject:filePath];
  220. /* exclude media files from backup (QA1719) */
  221. fileURL = [NSURL fileURLWithPath:filePath];
  222. [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
  223. } else {
  224. BOOL isDirectory = NO;
  225. BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
  226. // add folders
  227. if (exists && isDirectory) {
  228. NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:filePath error:nil];
  229. for (NSString* file in files) {
  230. NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:file];
  231. isDirectory = NO;
  232. exists = [[NSFileManager defaultManager] fileExistsAtPath:fullFilePath isDirectory:&isDirectory];
  233. //only add folders or files in folders
  234. if ((exists && isDirectory) || ![filePath.lastPathComponent isEqualToString:@"Documents"]) {
  235. NSString *folderpath = [filePath stringByReplacingOccurrencesOfString:directoryPath withString:@""];
  236. if (![folderpath isEqualToString:@""]) {
  237. folderpath = [folderpath stringByAppendingString:@"/"];
  238. }
  239. NSString *path = [folderpath stringByAppendingString:file];
  240. [foundFiles addObject:path];
  241. }
  242. }
  243. }
  244. }
  245. }
  246. for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
  247. if ([delegate respondsToSelector:@selector(mediaFilesFoundRequiringAdditionToStorageBackend:)]) {
  248. [delegate mediaFilesFoundRequiringAdditionToStorageBackend:[filePaths copy]];
  249. }
  250. }
  251. }
  252. @end