VLCMediaFileDiscoverer.m 11 KB

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