VLCMediaFileDiscoverer.m 11.7 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCMediaFileDiscoverer.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
6 7 8
 * $Id$
 *
 * Authors: Gleb Pinigin <gpinigin # gmail.com>
9
 *          Felix Paul Kühne <fkuehne # videolan.org>
10 11 12
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
13 14 15 16 17 18

#import "VLCMediaFileDiscoverer.h"
#import "NSString+SupportedMedia.h"

const float MediaTimerInterval = 2.f;

19
@interface VLCMediaFileDiscoverer () {
20
    NSMutableArray *_observers;
21
    dispatch_source_t _directorySource;
22 23 24 25

    NSArray *_directoryFiles;
    NSMutableDictionary *_addedFilesMapping;
    NSTimer *_addMediaTimer;
26
    NSArray *_discoveredFilePath;
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
}

@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];
50
        instance.filterResultsForPlayability = YES;
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    });

    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

97
- (void)startDiscovering
98
{
99 100 101 102 103 104
    if (!_directoryPath) {
        APLog(@"file discovery failed, no path was set");
        return;
    } else
        APLog(@"will discover files in path: '%@'", _directoryPath);

105
    _directoryFiles = [self directoryFiles];
106

107
    int const folderDescriptor = open([_directoryPath fileSystemRepresentation], O_EVTONLY);
108 109 110 111
    _directorySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, folderDescriptor,
                                              DISPATCH_VNODE_WRITE, DISPATCH_TARGET_QUEUE_DEFAULT);

    dispatch_source_set_event_handler(_directorySource, ^(){
112
        unsigned long const data = dispatch_source_get_data(self->_directorySource);
113 114 115 116 117 118 119 120 121 122 123 124 125 126
        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);
127 128 129 130
}

- (void)stopDiscovering
{
131
    dispatch_source_cancel(_directorySource);
132 133 134 135 136 137 138 139

    [self invalidateTimer];
}

#pragma mark -

- (NSArray *)directoryFiles
{
140 141
    NSArray *foundFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:_directoryPath
                                                                              error:nil];
142 143 144 145 146 147 148 149 150 151
    return foundFiles;
}

- (NSString *)filePath:(NSString *)fileName
{
    return [_directoryPath stringByAppendingPathComponent:fileName];
}

#pragma mark - directory watcher delegate

152
- (void)directoryDidChange
153 154 155 156 157 158 159
{
    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];

160
        for (NSString *fileName in deletedFiles)
161 162 163
            [self notifyFileDeleted:fileName];
    } else if (_directoryFiles.count < foundFiles.count) { // File was added
        NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"not (self in %@)", _directoryFiles];
164
        NSMutableArray *addedFiles = [NSMutableArray arrayWithArray:[foundFiles filteredArrayUsingPredicate:filterPredicate]];
165 166

        for (NSString *fileName in addedFiles) {
167 168 169 170
            BOOL isDirectory = NO;
            NSString *directoryPath = [self directoryPath];
            NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
            BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
171

172 173
            if (exists && !isDirectory) {
                if (self.filterResultsForPlayability) {
174
                    if ([fileName isSupportedMediaFormat] || [fileName isSupportedAudioMediaFormat] || [fileName isSupportedPlaylistFormat]) {
175 176 177 178 179 180 181 182
                        [_addedFilesMapping setObject:@(0) forKey:fileName];
                        [self notifyFileAdded:fileName loading:YES];
                    }
                } else {
                    [_addedFilesMapping setObject:@(0) forKey:fileName];
                    [self notifyFileAdded:fileName loading:YES];
                }
            } else if (exists && isDirectory) {
183
                // add folders
184 185 186 187 188 189 190 191 192 193
                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:@"/"];
194
                        }
195 196 197
                        NSString *path = [folderpath stringByAppendingString:file];
                        [_addedFilesMapping setObject:@(0) forKey:path];
                        [self notifyFileAdded:path loading:YES];
198 199
                    }
                }
200 201 202 203 204
            }
        }

        if (![_addMediaTimer isValid]) {
            _addMediaTimer = [NSTimer scheduledTimerWithTimeInterval:MediaTimerInterval
205
                                                              target:self selector:@selector(addFileTimerFired)
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
                                                            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;
}

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
#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];

272 273
        BOOL isDirectory = NO;
        BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
274

275 276 277 278
        if (exists && !isDirectory) {
            if (self.filterResultsForPlayability) {
                if ([fileName isSupportedMediaFormat] || [fileName isSupportedAudioMediaFormat]) {
                    [filePaths addObject:filePath];
279

280 281 282 283 284 285 286 287 288 289 290 291
                    /* exclude media files from backup (QA1719) */
                    fileURL = [NSURL fileURLWithPath:filePath];
                    [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
                }
            } else {
                [filePaths addObject:filePath];

                /* exclude media files from backup (QA1719) */
                fileURL = [NSURL fileURLWithPath:filePath];
                [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
            }
        } else if (exists && isDirectory) {
292
            // add folders
293 294 295 296 297 298 299 300 301 302
            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:@"/"];
303
                    }
304 305
                    NSString *path = [folderpath stringByAppendingString:file];
                    [foundFiles addObject:path];
306 307 308 309
                }
            }
        }
    }
310 311 312 313 314 315
    if (![_discoveredFilePath isEqualToArray:filePaths]) {
        _discoveredFilePath = filePaths;
        for (id<VLCMediaFileDiscovererDelegate> delegate in _observers) {
            if ([delegate respondsToSelector:@selector(mediaFilesFoundRequiringAdditionToStorageBackend:)]) {
                [delegate mediaFilesFoundRequiringAdditionToStorageBackend:[filePaths copy]];
            }
316 317
        }
    }
318 319
}

320
@end