123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- /*****************************************************************************
- * VLCMediaFileDiscoverer.m
- * VLC for iOS
- *****************************************************************************
- * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
- * $Id$
- *
- * Authors: Gleb Pinigin <gpinigin # gmail.com>
- * Felix Paul Kühne <fkuehne # videolan.org>
- *
- * Refer to the COPYING file of the official project for license.
- *****************************************************************************/
- #import "VLCMediaFileDiscoverer.h"
- #import "NSString+SupportedMedia.h"
- const float MediaTimerInterval = 2.f;
- @interface VLCMediaFileDiscoverer () {
- NSMutableArray *_observers;
- dispatch_source_t _directorySource;
- NSString *_directoryPath;
- NSArray *_directoryFiles;
- NSMutableDictionary *_addedFilesMapping;
- NSTimer *_addMediaTimer;
- }
- @end
- @implementation VLCMediaFileDiscoverer
- - (id)init
- {
- self = [super init];
- if (self) {
- _observers = [NSMutableArray array];
- _addedFilesMapping = [NSMutableDictionary dictionary];
- }
- return self;
- }
- + (instancetype)sharedInstance
- {
- static dispatch_once_t onceToken;
- static VLCMediaFileDiscoverer *instance;
- dispatch_once(&onceToken, ^{
- instance = [VLCMediaFileDiscoverer new];
- });
- return instance;
- }
- #pragma mark - observation
- - (void)addObserver:(id<VLCMediaFileDiscovererDelegate>)delegate
- {
- [_observers addObject:delegate];
- }
- - (void)removeObserver:(id<VLCMediaFileDiscovererDelegate>)delegate
- {
- [_observers removeObject:delegate];
- }
- - (void)notifyFileDeleted:(NSString *)fileName
- {
- for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
- if ([delegate respondsToSelector:@selector(mediaFileDeleted:)]) {
- [delegate mediaFileDeleted:[self filePath:fileName]];
- }
- }
- }
- - (void)notifyFileAdded:(NSString *)fileName loading:(BOOL)isLoading
- {
- for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
- if ([delegate respondsToSelector:@selector(mediaFileAdded:loading:)]) {
- [delegate mediaFileAdded:[self filePath:fileName] loading:isLoading];
- }
- }
- }
- - (void)notifySizeChanged:(NSString *)fileName size:(unsigned long long)size
- {
- for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
- if ([delegate respondsToSelector:@selector(mediaFileChanged:size:)]) {
- [delegate mediaFileChanged:[self filePath:fileName] size:size];
- }
- }
- }
- #pragma mark - discovering
- - (void)startDiscovering
- {
- _directoryPath = [self directoryPath];
- _directoryFiles = [self directoryFiles];
- int const folderDescriptor = open([_directoryPath fileSystemRepresentation], O_EVTONLY);
- _directorySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, folderDescriptor,
- DISPATCH_VNODE_WRITE, DISPATCH_TARGET_QUEUE_DEFAULT);
- dispatch_source_set_event_handler(_directorySource, ^(){
- unsigned long const data = dispatch_source_get_data(_directorySource);
- if (data & DISPATCH_VNODE_WRITE) {
- // Do all the work on the main thread,
- // including timer scheduling, notifications delivering
- dispatch_async(dispatch_get_main_queue(), ^{
- [self directoryDidChange];
- });
- }
- });
- dispatch_source_set_cancel_handler(_directorySource, ^(){
- close(folderDescriptor);
- });
- dispatch_resume(_directorySource);
- }
- - (void)stopDiscovering
- {
- dispatch_source_cancel(_directorySource);
- [self invalidateTimer];
- }
- #pragma mark -
- - (NSArray *)directoryFiles
- {
- NSArray *foundFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:_directoryPath
- error:nil];
- return foundFiles;
- }
- - (NSString *)filePath:(NSString *)fileName
- {
- return [_directoryPath stringByAppendingPathComponent:fileName];
- }
- #pragma mark - directory watcher delegate
- - (NSString *)directoryPath
- {
- NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
- NSString *directoryPath = searchPaths[0];
- return directoryPath;
- }
- - (void)directoryDidChange
- {
- NSArray *foundFiles = [self directoryFiles];
- if (_directoryFiles.count > foundFiles.count) { // File was deleted
- NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"not (self in %@)", foundFiles];
- NSArray *deletedFiles = [_directoryFiles filteredArrayUsingPredicate:filterPredicate];
- for (NSString *fileName in deletedFiles)
- [self notifyFileDeleted:fileName];
- } else if (_directoryFiles.count < foundFiles.count) { // File was added
- NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"not (self in %@)", _directoryFiles];
- NSMutableArray *addedFiles = [NSMutableArray arrayWithArray:[foundFiles filteredArrayUsingPredicate:filterPredicate]];
- for (NSString *fileName in addedFiles) {
- if ([fileName isSupportedMediaFormat] || [fileName isSupportedAudioMediaFormat]) {
- [_addedFilesMapping setObject:@(0) forKey:fileName];
- [self notifyFileAdded:fileName loading:YES];
- } else {
- BOOL isDirectory = NO;
- NSString *directoryPath = [self directoryPath];
- NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
- BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
- // add folders
- if (exists && isDirectory) {
- NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:filePath error:nil];
- for (NSString* file in files) {
- NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:file];
- isDirectory = NO;
- exists = [[NSFileManager defaultManager] fileExistsAtPath:fullFilePath isDirectory:&isDirectory];
- //only add folders or files in folders
- if ((exists && isDirectory) || ![filePath.lastPathComponent isEqualToString:@"Documents"]) {
- NSString *folderpath = [filePath stringByReplacingOccurrencesOfString:directoryPath withString:@""];
- if (![folderpath isEqualToString:@""]) {
- folderpath = [folderpath stringByAppendingString:@"/"];
- }
- NSString *path = [folderpath stringByAppendingString:file];
- [_addedFilesMapping setObject:@(0) forKey:path];
- [self notifyFileAdded:path loading:YES];
- }
- }
- }
- }
- }
- if (![_addMediaTimer isValid]) {
- _addMediaTimer = [NSTimer scheduledTimerWithTimeInterval:MediaTimerInterval
- target:self selector:@selector(addFileTimerFired)
- userInfo:nil repeats:YES];
- }
- }
- _directoryFiles = foundFiles;
- }
- #pragma mark - media timer
- - (void)addFileTimerFired
- {
- NSArray *allKeys = [_addedFilesMapping allKeys];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- for (NSString *fileName in allKeys) {
- NSString *filePath = [self filePath:fileName];
- if (![fileManager fileExistsAtPath:filePath]) {
- [_addedFilesMapping removeObjectForKey:fileName];
- continue;
- }
- NSNumber *prevFetchedSize = [_addedFilesMapping objectForKey:fileName];
- NSDictionary *attribs = [fileManager attributesOfItemAtPath:filePath error:nil];
- NSNumber *updatedSize = [attribs objectForKey:NSFileSize];
- if (!updatedSize)
- continue;
- [self notifySizeChanged:fileName size:[updatedSize unsignedLongLongValue]];
- if ([prevFetchedSize compare:updatedSize] == NSOrderedSame) {
- [_addedFilesMapping removeObjectForKey:fileName];
- [self notifyFileAdded:fileName loading:NO];
- } else
- [_addedFilesMapping setObject:updatedSize forKey:fileName];
- }
- if (_addedFilesMapping.count == 0)
- [self invalidateTimer];
- }
- - (void)invalidateTimer
- {
- [_addMediaTimer invalidate];
- _addMediaTimer = nil;
- }
- #pragma mark - media list management
- - (void)updateMediaList
- {
- if (![NSThread isMainThread]) {
- [self performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
- return;
- }
- NSString *directoryPath = [self directoryPath];
- NSMutableArray *foundFiles = [NSMutableArray arrayWithArray:[[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:nil]];
- NSMutableArray *filePaths = [NSMutableArray array];
- NSURL *fileURL;
- while (foundFiles.count) {
- NSString *fileName = foundFiles.firstObject;
- NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
- [foundFiles removeObject:fileName];
- if ([fileName isSupportedMediaFormat] || [fileName isSupportedAudioMediaFormat]) {
- [filePaths addObject:filePath];
- /* exclude media files from backup (QA1719) */
- fileURL = [NSURL fileURLWithPath:filePath];
- [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
- } else {
- BOOL isDirectory = NO;
- BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
- // add folders
- if (exists && isDirectory) {
- NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:filePath error:nil];
- for (NSString* file in files) {
- NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:file];
- isDirectory = NO;
- exists = [[NSFileManager defaultManager] fileExistsAtPath:fullFilePath isDirectory:&isDirectory];
- //only add folders or files in folders
- if ((exists && isDirectory) || ![filePath.lastPathComponent isEqualToString:@"Documents"]) {
- NSString *folderpath = [filePath stringByReplacingOccurrencesOfString:directoryPath withString:@""];
- if (![folderpath isEqualToString:@""]) {
- folderpath = [folderpath stringByAppendingString:@"/"];
- }
- NSString *path = [folderpath stringByAppendingString:file];
- [foundFiles addObject:path];
- }
- }
- }
- }
- }
- for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
- if ([delegate respondsToSelector:@selector(mediaFilesFoundRequiringAdditionToStorageBackend:)]) {
- [delegate mediaFilesFoundRequiringAdditionToStorageBackend:[filePaths copy]];
- }
- }
- }
- @end
|