diff --git a/TOFileSystemObserver/Categories/NSFileManager+TOFileSystemDirectoryEnumerator.m b/TOFileSystemObserver/Categories/NSFileManager+TOFileSystemDirectoryEnumerator.m index 1040677..a160d13 100644 --- a/TOFileSystemObserver/Categories/NSFileManager+TOFileSystemDirectoryEnumerator.m +++ b/TOFileSystemObserver/Categories/NSFileManager+TOFileSystemDirectoryEnumerator.m @@ -24,23 +24,22 @@ @implementation NSFileManager (TOFileSystemDirectoryEnumerator) -- (NSDirectoryEnumerator *)to_fileSystemEnumeratorForDirectoryAtURL:(NSURL *)url -{ +- (NSDirectoryEnumerator *)to_fileSystemEnumeratorForDirectoryAtURL:(NSURL *)url { // Set the keys for the properties we wish to capture - NSArray *keys = @[NSURLIsDirectoryKey, - NSURLFileSizeKey, - NSURLCreationDateKey, - NSURLContentModificationDateKey]; + NSArray * const keys = @[NSURLIsDirectoryKey, + NSURLFileSizeKey, + NSURLCreationDateKey, + NSURLContentModificationDateKey]; // Set the flags for the enumerator - NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsHiddenFiles | - NSDirectoryEnumerationSkipsSubdirectoryDescendants; + const NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsHiddenFiles | + NSDirectoryEnumerationSkipsSubdirectoryDescendants; // Create the enumerator - NSDirectoryEnumerator *urlEnumerator = [self enumeratorAtURL:url - includingPropertiesForKeys:keys - options:options - errorHandler:nil]; + NSDirectoryEnumerator * const urlEnumerator = [self enumeratorAtURL:url + includingPropertiesForKeys:keys + options:options + errorHandler:nil]; return urlEnumerator; } diff --git a/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m b/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m index d4ae8f1..daaa691 100644 --- a/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m +++ b/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m @@ -27,39 +27,34 @@ @implementation NSURL (TOFileSystemAttributes) -- (BOOL)to_isCopying -{ +- (BOOL)to_isCopying { // When files are still being copied, their // modification date is equal to the current device time. - NSDate *modificationDate = self.to_modificationDate; + NSDate * const modificationDate = self.to_modificationDate; if (modificationDate == nil) { return NO; } return [modificationDate timeIntervalSinceDate:[NSDate date]] > (-kTOFileSystemObserverCopyingTimeDelay - FLT_EPSILON); } -- (BOOL)to_isDirectory -{ +- (BOOL)to_isDirectory { NSNumber *isDirectory; [self getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; return isDirectory.boolValue; } -- (long long)to_size -{ +- (long long)to_size { NSNumber *fileSize; [self getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil]; return fileSize.longLongValue; } -- (NSDate *)to_creationDate -{ +- (NSDate *)to_creationDate { NSDate *creationDate; [self getResourceValue:&creationDate forKey:NSURLCreationDateKey error:nil]; return creationDate; } -- (NSDate *)to_modificationDate -{ +- (NSDate *)to_modificationDate { [self removeCachedResourceValueForKey:NSURLContentModificationDateKey]; NSDate *modificationDate; [self getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:nil]; @@ -72,14 +67,13 @@ - (NSDate *)to_modificationDate // that don't fill d_type, like NFS or FAT — falls back to lstat. Exposed (not // static) so the DT_UNKNOWN branch can be exercised by unit tests, since it's // not reachable on APFS where readdir always reports a concrete type. -BOOL TOFileSystemDirEntryIsCountable(const char *parentPath, const struct dirent *entry) -{ +BOOL TOFileSystemDirEntryIsCountable(const char *parentPath, const struct dirent *entry) { if (entry->d_name[0] == '.') { return NO; } if (entry->d_type == DT_REG || entry->d_type == DT_DIR) { return YES; } if (entry->d_type != DT_UNKNOWN) { return NO; } char fullPath[PATH_MAX]; - int written = snprintf(fullPath, sizeof(fullPath), "%s/%s", parentPath, entry->d_name); + const int written = snprintf(fullPath, sizeof(fullPath), "%s/%s", parentPath, entry->d_name); if (written <= 0 || written >= (int)sizeof(fullPath)) { return NO; } struct stat st; @@ -87,11 +81,10 @@ BOOL TOFileSystemDirEntryIsCountable(const char *parentPath, const struct dirent return S_ISREG(st.st_mode) || S_ISDIR(st.st_mode); } -- (NSInteger)to_numberOfSubItems -{ +- (NSInteger)to_numberOfSubItems { // Do it using POSIX APIs to avoid needing to load in all of the file names - const char *path = [self.path cStringUsingEncoding:NSUTF8StringEncoding]; - DIR *directory = opendir(path); + const char * const path = [self.path cStringUsingEncoding:NSUTF8StringEncoding]; + DIR * const directory = opendir(path); if (directory == NULL) { return 0; } NSInteger numberOfItems = 0; diff --git a/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m b/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m index cfafcdc..a760b43 100644 --- a/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m +++ b/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m @@ -27,37 +27,35 @@ @implementation NSURL (TOFileSystemUUID) -+ (void)to_setKeyNamePrefix:(NSString *)prefix -{ ++ (void)to_setKeyNamePrefix:(NSString *)prefix { kTOFileSystemAttributeKey = [NSString stringWithFormat:@"%@.fileSystemObserver.UUID", prefix]; } -- (NSString *)to_fileSystemUUID -{ - const char *filePath = [self.path fileSystemRepresentation]; - const char *keyName = kTOFileSystemAttributeKey.UTF8String; +- (NSString *)to_fileSystemUUID { + const char * const filePath = [self.path fileSystemRepresentation]; + const char * const keyName = kTOFileSystemAttributeKey.UTF8String; // Allocate a buffer for the value (UUID values are always 36 characters) char value[36]; // Fetch the value from disk. A short read means there's no valid UUID stored. - ssize_t bytesRead = getxattr(filePath, keyName, value, sizeof(value), 0, 0); + const ssize_t bytesRead = getxattr(filePath, keyName, value, sizeof(value), 0, 0); if (bytesRead != (ssize_t)sizeof(value)) { return nil; } // Convert to a string, and return if successful - NSString *uuid = [[NSString alloc] initWithBytes:value length:bytesRead encoding:NSUTF8StringEncoding]; + NSString * const uuid = [[NSString alloc] initWithBytes:value length:bytesRead encoding:NSUTF8StringEncoding]; if (uuid.length == 0) { return nil; } - + // Verify to make sure the provided value is a valid UUID string - NSString *uuidPattern = @"\\A[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}\\Z"; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:uuidPattern - options:NSRegularExpressionCaseInsensitive - error:nil]; - NSRange range = [regex rangeOfFirstMatchInString:uuid options:0 range:NSMakeRange(0, uuid.length)]; + NSString * const uuidPattern = @"\\A[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}\\Z"; + NSRegularExpression * const regex = [NSRegularExpression regularExpressionWithPattern:uuidPattern + options:NSRegularExpressionCaseInsensitive + error:nil]; + const NSRange range = [regex rangeOfFirstMatchInString:uuid options:0 range:NSMakeRange(0, uuid.length)]; // A valid regex was found. if (range.location == NSNotFound) { @@ -67,8 +65,7 @@ - (NSString *)to_fileSystemUUID return uuid; } -- (BOOL)to_setFileSystemUUID:(NSString *)uuid -{ +- (BOOL)to_setFileSystemUUID:(NSString *)uuid { if (uuid.length == 0) { return NO; } if (uuid.length != 36) { @throw [NSException exceptionWithName:NSInternalInconsistencyException @@ -77,19 +74,18 @@ - (BOOL)to_setFileSystemUUID:(NSString *)uuid } // Determine the file path and destination key - const char *filePath = [self.path fileSystemRepresentation]; - const char *keyName = kTOFileSystemAttributeKey.UTF8String; + const char * const filePath = [self.path fileSystemRepresentation]; + const char * const keyName = kTOFileSystemAttributeKey.UTF8String; // Convert the string to a C byte string - const char *uuidString = [uuid cStringUsingEncoding:NSUTF8StringEncoding]; + const char * const uuidString = [uuid cStringUsingEncoding:NSUTF8StringEncoding]; if (uuidString == NULL) { return NO; } // Save it to this file. UUID strings are always 36 ASCII bytes. return setxattr(filePath, keyName, uuidString, 36, 0, 0) == 0; } -- (BOOL)to_setFileSystemUUIDIfAbsent:(NSString *)uuid -{ +- (BOOL)to_setFileSystemUUIDIfAbsent:(NSString *)uuid { if (uuid.length == 0) { return NO; } if (uuid.length != 36) { @throw [NSException exceptionWithName:NSInternalInconsistencyException @@ -97,9 +93,9 @@ - (BOOL)to_setFileSystemUUIDIfAbsent:(NSString *)uuid userInfo:nil]; } - const char *filePath = [self.path fileSystemRepresentation]; - const char *keyName = kTOFileSystemAttributeKey.UTF8String; - const char *uuidString = [uuid cStringUsingEncoding:NSUTF8StringEncoding]; + const char * const filePath = [self.path fileSystemRepresentation]; + const char * const keyName = kTOFileSystemAttributeKey.UTF8String; + const char * const uuidString = [uuid cStringUsingEncoding:NSUTF8StringEncoding]; if (uuidString == NULL) { return NO; } // XATTR_CREATE makes setxattr fail with EEXIST if the attribute is already @@ -108,9 +104,8 @@ - (BOOL)to_setFileSystemUUIDIfAbsent:(NSString *)uuid return setxattr(filePath, keyName, uuidString, 36, 0, XATTR_CREATE) == 0; } -- (NSString *)to_generateFileSystemUUID -{ - NSString *uuid = [NSUUID UUID].UUIDString; +- (NSString *)to_generateFileSystemUUID { + NSString * const uuid = [NSUUID UUID].UUIDString; if (![self to_setFileSystemUUID:uuid]) { return nil; } return uuid; } diff --git a/TOFileSystemObserver/Entities/Changes/TOFileSystemChanges.m b/TOFileSystemObserver/Entities/Changes/TOFileSystemChanges.m index c00a3e0..c983111 100644 --- a/TOFileSystemObserver/Entities/Changes/TOFileSystemChanges.m +++ b/TOFileSystemObserver/Entities/Changes/TOFileSystemChanges.m @@ -34,32 +34,28 @@ @interface TOFileSystemChanges () @implementation TOFileSystemChanges -- (instancetype)initWithFileSystemObserver:(TOFileSystemObserver *)fileSystemObserver -{ +- (instancetype)initWithFileSystemObserver:(TOFileSystemObserver *)fileSystemObserver { if (self = [super init]) { _fileSystemObserver = fileSystemObserver; } return self; } -- (void)addDiscoveredItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL -{ +- (void)addDiscoveredItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL { if (_discoveredItems == nil) { _discoveredItems = [NSMutableDictionary dictionary]; } _discoveredItems[uuid] = fileURL; } -- (void)addModifiedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL -{ +- (void)addModifiedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL { if (_modifiedItems == nil) { _modifiedItems = [NSMutableDictionary dictionary]; } _modifiedItems[uuid] = fileURL; } -- (void)addDeletedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL -{ +- (void)addDeletedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL { if (_deletedItems == nil) { _deletedItems = [NSMutableDictionary dictionary]; } @@ -68,21 +64,18 @@ - (void)addDeletedItemWithUUID:(NSString *)uuid fileURL:(NSURL *)fileURL - (void)addMovedItemWithUUID:(NSString *)uuid oldFileURL:(NSURL *)oldFileURL - newFileURL:(NSURL *)newFileURL -{ + newFileURL:(NSURL *)newFileURL { if (_movedItems == nil) { _movedItems = [NSMutableDictionary dictionary]; } _movedItems[uuid] = @[oldFileURL, newFileURL]; } -- (void)setIsFullScan -{ +- (void)setIsFullScan { self.isFullScan = YES; } -- (NSString *)description -{ +- (NSString *)description { return [NSString stringWithFormat:@"Discovered items: %@\nModified items: %@\nDeleted Items: %@\nMoved items: %@\n", self.discoveredItems, self.modifiedItems, self.deletedItems, self.movedItems]; } diff --git a/TOFileSystemObserver/Entities/Changes/TOFileSystemItemListChanges.m b/TOFileSystemObserver/Entities/Changes/TOFileSystemItemListChanges.m index aa156db..9888054 100644 --- a/TOFileSystemObserver/Entities/Changes/TOFileSystemItemListChanges.m +++ b/TOFileSystemObserver/Entities/Changes/TOFileSystemItemListChanges.m @@ -49,8 +49,7 @@ @implementation TOFileSystemItemListChanges #pragma mark - Adding Index Values - -- (void)addDeletionIndex:(NSInteger)index -{ +- (void)addDeletionIndex:(NSInteger)index { if (self.deletions == nil) { self.deletions = [NSMutableArray array]; } @@ -58,8 +57,7 @@ - (void)addDeletionIndex:(NSInteger)index [(NSMutableArray *)self.deletions addObject:@(index)]; } -- (void)addInsertionIndex:(NSInteger)index -{ +- (void)addInsertionIndex:(NSInteger)index { if (self.insertions == nil) { self.insertions = [NSMutableArray array]; } @@ -67,8 +65,7 @@ - (void)addInsertionIndex:(NSInteger)index [(NSMutableArray *)self.insertions addObject:@(index)]; } -- (void)addModificationIndex:(NSInteger)index -{ +- (void)addModificationIndex:(NSInteger)index { if (self.modificatons == nil) { self.modificatons = [NSMutableArray array]; } @@ -77,73 +74,64 @@ - (void)addModificationIndex:(NSInteger)index } - (void)addMovementWithSourceIndex:(NSInteger)sourceIndex - destinationIndex:(NSInteger)destinationIndex -{ + destinationIndex:(NSInteger)destinationIndex { if (self.movements == nil) { self.movements = [NSMutableDictionary dictionary]; } - NSMutableDictionary *dict = (NSMutableDictionary *)self.movements; + NSMutableDictionary * const dict = (NSMutableDictionary *)self.movements; dict[@(sourceIndex)] = @(destinationIndex); } #pragma mark - Table/Collection View Converters - -- (NSArray *)indexPathsForCollection:(nullable NSArray *)collection - inSection:(NSInteger)section -{ +- (NSArray *)_indexPathsForCollection:(nullable NSArray *)collection + inSection:(NSInteger)section { if (!collection) { return [NSArray array]; } - - NSMutableArray *array = [NSMutableArray array]; - for (NSNumber *number in collection) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:number.intValue inSection:section]; + + NSMutableArray * const array = [NSMutableArray array]; + for (NSNumber * const number in collection) { + NSIndexPath * const indexPath = [NSIndexPath indexPathForRow:number.intValue inSection:section]; [array addObject:indexPath]; } - + return [NSArray arrayWithArray:array]; } -- (NSArray *)indexPathsForDeletionsInSection:(NSInteger)section -{ - return [self indexPathsForCollection:self.deletions inSection:section]; +- (NSArray *)indexPathsForDeletionsInSection:(NSInteger)section { + return [self _indexPathsForCollection:self.deletions inSection:section]; } -- (NSArray *)indexPathsForInsertionsInSection:(NSInteger)section -{ - return [self indexPathsForCollection:self.insertions inSection:section]; +- (NSArray *)indexPathsForInsertionsInSection:(NSInteger)section { + return [self _indexPathsForCollection:self.insertions inSection:section]; } -- (NSArray *)indexPathsForModificationsInSection:(NSInteger)section -{ - return [self indexPathsForCollection:self.modificatons inSection:section]; +- (NSArray *)indexPathsForModificationsInSection:(NSInteger)section { + return [self _indexPathsForCollection:self.modificatons inSection:section]; } -- (NSArray *)indexPathsForMovementSourcesInSection:(NSInteger)section -{ - return [self indexPathsForCollection:self.movements.allKeys inSection:section]; +- (NSArray *)indexPathsForMovementSourcesInSection:(NSInteger)section { + return [self _indexPathsForCollection:self.movements.allKeys inSection:section]; } -- (NSArray *)indexPathsForMovementDestinationsWithSourceIndexPaths:(NSArray *)sourceIndexPaths -{ +- (NSArray *)indexPathsForMovementDestinationsWithSourceIndexPaths:(NSArray *)sourceIndexPaths { if (self.movements == nil) { return [NSArray array]; } - - NSMutableArray *array = [NSMutableArray array]; - for (NSIndexPath *sourceIndexPath in sourceIndexPaths) { - NSInteger row = self.movements[@(sourceIndexPath.row)].intValue; - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:sourceIndexPath.section]; + + NSMutableArray * const array = [NSMutableArray array]; + for (NSIndexPath * const sourceIndexPath in sourceIndexPaths) { + const NSInteger row = self.movements[@(sourceIndexPath.row)].intValue; + NSIndexPath * const indexPath = [NSIndexPath indexPathForRow:row inSection:sourceIndexPath.section]; [array addObject:indexPath]; } - + return [NSArray arrayWithArray:array]; } -- (BOOL)hasItemMovements -{ +- (BOOL)hasItemMovements { return self.movements != nil; } -- (BOOL)hasItemChanges -{ +- (BOOL)hasItemChanges { return (self.deletions.count || self.insertions.count || self.modificatons.count); diff --git a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h index 777aba7..4ed1686 100644 --- a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h +++ b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h @@ -29,6 +29,7 @@ NS_ASSUME_NONNULL_BEGIN used to store re-usable instances of item and list objects. */ +__attribute__((objc_subclassing_restricted)) @interface TOFileSystemItemMapTable : NSObject @property (nonatomic, readonly) NSInteger count; diff --git a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m index 030df51..cc10121 100644 --- a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m +++ b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m @@ -34,8 +34,7 @@ @interface TOFileSystemItemMapTable () @implementation TOFileSystemItemMapTable -- (instancetype)init -{ +- (instancetype)init { if (self = [super init]) { _mapTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory]; @@ -45,8 +44,7 @@ - (instancetype)init return self; } -- (NSInteger)count -{ +- (NSInteger)count { __block NSInteger count = 0; dispatch_sync(self.dispatchQueue, ^{ count = self.mapTable.count; @@ -55,15 +53,13 @@ - (NSInteger)count return count; } -- (void)setItem:(id)object forUUID:(NSString *)uuid -{ +- (void)setItem:(id)object forUUID:(NSString *)uuid { dispatch_barrier_async(self.dispatchQueue, ^{ [self.mapTable setObject:object forKey:uuid]; }); } -- (id)itemForUUID:(NSString *)uuid -{ +- (id)itemForUUID:(NSString *)uuid { __block id item = nil; dispatch_sync(self.dispatchQueue, ^{ @autoreleasepool { @@ -74,15 +70,13 @@ - (id)itemForUUID:(NSString *)uuid return item; } -- (void)removeItemForUUID:(NSString *)uuid -{ +- (void)removeItemForUUID:(NSString *)uuid { dispatch_barrier_async(self.dispatchQueue, ^{ [self.mapTable removeObjectForKey:uuid]; }); } -- (NSArray *)allItems -{ +- (NSArray *)allItems { __block NSArray *items = nil; dispatch_sync(self.dispatchQueue, ^{ @autoreleasepool { @@ -92,13 +86,11 @@ - (NSArray *)allItems return items ?: @[]; } -- (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key -{ +- (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key { [self setItem:object forUUID:key]; } -- (nullable id)objectForKeyedSubscript:(NSString *)key -{ +- (nullable id)objectForKeyedSubscript:(NSString *)key { return [self itemForUUID:key]; } diff --git a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.h b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.h index 00d1f42..d6a39fd 100644 --- a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.h +++ b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.h @@ -35,13 +35,14 @@ NS_ASSUME_NONNULL_BEGIN The URLs are converted to and stored as relative URLs to save memory, and are converted back to absolute URLs when retrieved. */ +__attribute__((objc_subclassing_restricted)) @interface TOFileSystemItemURLDictionary : NSObject /** The number of items currently in the dictionary. */ @property (nonatomic, readonly) NSUInteger count; /** Create a new instance with the base URL that all items will be relatively saved against. */ -- (instancetype)initWithBaseURL:(NSURL *)baseURL; +- (instancetype)initWithBaseURL:(NSURL *)baseURL NS_DESIGNATED_INITIALIZER; /** Adds an item URL to the dictionary. May be called from multiple threads. */ - (void)setItemURL:(nullable NSURL *)itemURL forUUID:(nullable NSString *)uuid; diff --git a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.m b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.m index 0f3988d..a203915 100644 --- a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.m +++ b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemURLDictionary.m @@ -42,8 +42,8 @@ @implementation TOFileSystemItemURLDictionary #pragma mark - Class Creation - -- (instancetype)initWithBaseURL:(NSURL *)baseURL -{ +- (instancetype)initWithBaseURL:(NSURL *)baseURL { + NSParameterAssert(baseURL != nil); if (self = [super init]) { _baseURL = baseURL.URLByDeletingLastPathComponent.URLByStandardizingPath; _uuidItems = [NSMutableDictionary dictionary]; @@ -55,8 +55,7 @@ - (instancetype)initWithBaseURL:(NSURL *)baseURL return self; } -- (NSUInteger)count -{ +- (NSUInteger)count { __block NSInteger count = 0; dispatch_sync(self.itemQueue, ^{ count = self.uuidItems.count; @@ -65,37 +64,35 @@ - (NSUInteger)count return count; } -- (void)setItemURL:(nullable NSURL *)itemURL forUUID:(nullable NSString *)uuid -{ +- (void)setItemURL:(nullable NSURL *)itemURL forUUID:(nullable NSString *)uuid { if (uuid.length == 0) { return; } // If the item is nil, remove it from the store if (itemURL == nil) { dispatch_barrier_async(self.itemQueue, ^{ - NSURL *url = self.uuidItems[uuid]; + NSURL * const url = self.uuidItems[uuid]; [self.urlItems removeObjectForKey:url]; [self.uuidItems removeObjectForKey:uuid]; }); return; } - + // Use dispatch barriers to block all reads when we mutate the dictionary dispatch_barrier_async(self.itemQueue, ^{ // Purge the previously saved entries as they may be stale - NSURL *savedURL = self.uuidItems[uuid]; - NSString *savedUUID = self.urlItems[savedURL]; + NSURL * const savedURL = self.uuidItems[uuid]; + NSString * const savedUUID = self.urlItems[savedURL]; if (savedUUID) { [self.uuidItems removeObjectForKey:savedUUID]; } if (savedURL) { [self.urlItems removeObjectForKey:savedURL]; } - + // Remove the un-needed absolute path to save memory - NSURL *url = [self relativeURLForURL:itemURL]; + NSURL * const url = [self _relativeURLForURL:itemURL]; self.uuidItems[uuid] = url; self.urlItems[url] = uuid; }); } -- (nullable NSURL *)itemURLForUUID:(NSString *)uuid -{ +- (nullable NSURL *)itemURLForUUID:(NSString *)uuid { if (uuid.length == 0) { return nil; } // Use dispatch barriers to allow asynchronouse reading @@ -108,22 +105,20 @@ - (nullable NSURL *)itemURLForUUID:(NSString *)uuid return [self.baseURL URLByAppendingPathComponent:itemURL.path].URLByStandardizingPath; } -- (nullable NSString *)uuidForItemWithURL:(NSURL *)itemURL -{ +- (nullable NSString *)uuidForItemWithURL:(NSURL *)itemURL { // Convert the item URL to relative - NSURL *url = [self relativeURLForURL:itemURL]; - + NSURL * const url = [self _relativeURLForURL:itemURL]; + // Look up the URL in the dictionary __block NSString *uuid = nil; dispatch_sync(self.itemQueue, ^{ uuid = self.urlItems[url]; }); - + return uuid; } -- (nullable NSArray *)allUUIDs -{ +- (nullable NSArray *)allUUIDs { __block NSArray *uuids = nil; dispatch_sync(self.itemQueue, ^{ uuids = self.uuidItems.allKeys; @@ -131,14 +126,13 @@ - (nullable NSString *)uuidForItemWithURL:(NSURL *)itemURL return uuids; } -- (nullable NSArray *)allURLs -{ +- (nullable NSArray *)allURLs { // Loop through each item in the store, and restore its URL - __block NSMutableArray *array = [NSMutableArray array]; + NSMutableArray * const array = [NSMutableArray array]; dispatch_sync(self.itemQueue, ^{ - for (NSString *uuid in self.uuidItems) { - NSString *path = self.uuidItems[uuid].path; - NSURL *url = [self.baseURL URLByAppendingPathComponent:path]; + for (NSString * const uuid in self.uuidItems) { + NSString * const path = self.uuidItems[uuid].path; + NSURL * const url = [self.baseURL URLByAppendingPathComponent:path]; [array addObject:url.URLByStandardizingPath]; } }); @@ -150,55 +144,49 @@ - (nullable NSString *)uuidForItemWithURL:(NSURL *)itemURL return [NSArray arrayWithArray:array]; } -- (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key -{ +- (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key { [self setItemURL:object forUUID:key]; } -- (void)removeItemURLForUUID:(NSString *)uuid -{ +- (void)removeItemURLForUUID:(NSString *)uuid { if (uuid == nil) { return; } - + dispatch_barrier_async(self.itemQueue, ^{ - NSURL *url = self.uuidItems[uuid]; + NSURL * const url = self.uuidItems[uuid]; if (url == nil) { return; } [self.urlItems removeObjectForKey:url]; [self.uuidItems removeObjectForKey:uuid]; }); } -- (void)removeAllItems -{ +- (void)removeAllItems { dispatch_barrier_async(self.itemQueue, ^{ [self.urlItems removeAllObjects]; [self.uuidItems removeAllObjects]; }); } -- (nullable id)objectForKeyedSubscript:(NSString *)key -{ +- (nullable id)objectForKeyedSubscript:(NSString *)key { return [self itemURLForUUID:key]; } #pragma mark - URL Conversion - -- (NSURL *)relativeURLForURL:(NSURL *)url -{ - NSString *basePath = self.baseURL.path; - NSString *itemPath = url.URLByStandardizingPath.path; - NSString *relativePath = [itemPath stringByReplacingOccurrencesOfString:basePath withString:@""]; +- (NSURL *)_relativeURLForURL:(NSURL *)url { + NSString * const basePath = self.baseURL.path; + NSString * const itemPath = url.URLByStandardizingPath.path; + NSString * const relativePath = [itemPath stringByReplacingOccurrencesOfString:basePath withString:@""]; return [NSURL fileURLWithPath:relativePath]; } #pragma mark - Debugging - -- (NSString *)description -{ +- (NSString *)description { NSString *descriptionString = @""; - for (NSString *key in self.uuidItems.allKeys) { + for (NSString * const key in self.uuidItems.allKeys) { descriptionString = [descriptionString stringByAppendingFormat:@"%@ - %@\n", key, self.uuidItems[key]]; } - + return descriptionString; } diff --git a/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.h b/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.h index 67d3445..c6bf8f9 100644 --- a/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.h +++ b/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.h @@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN A static class to centralize all file path manipulation logic. */ +__attribute__((objc_subclassing_restricted)) @interface TOFileSystemPath : NSObject /** The path to the application sandbox. */ diff --git a/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.m b/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.m index b2a12c1..216fb7f 100644 --- a/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.m +++ b/TOFileSystemObserver/Entities/FilePaths/TOFileSystemPath.m @@ -24,14 +24,12 @@ @implementation TOFileSystemPath -+ (NSURL *)applicationSandboxURL -{ ++ (NSURL *)applicationSandboxURL { return [NSURL fileURLWithPath:NSHomeDirectory()]; } -+ (NSURL *)documentsDirectoryURL -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; ++ (NSURL *)documentsDirectoryURL { + NSFileManager * const fileManager = [NSFileManager defaultManager]; return [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject; } diff --git a/TOFileSystemObserver/Entities/Items/TOFileSystemItem+Private.h b/TOFileSystemObserver/Entities/Items/TOFileSystemItem+Private.h index 327f11f..7d3b8b8 100644 --- a/TOFileSystemObserver/Entities/Items/TOFileSystemItem+Private.h +++ b/TOFileSystemObserver/Entities/Items/TOFileSystemItem+Private.h @@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN /** Creates a new instance of an item for the target item. */ - (instancetype)initWithItemAtFileURL:(NSURL *)fileURL - fileSystemObserver:(TOFileSystemObserver *)observer; + fileSystemObserver:(TOFileSystemObserver *)observer NS_DESIGNATED_INITIALIZER; /** Adds this item as a child of a list. */ - (void)addToList:(TOFileSystemItemList *)list; diff --git a/TOFileSystemObserver/Entities/Items/TOFileSystemItem.h b/TOFileSystemObserver/Entities/Items/TOFileSystemItem.h index 7fc7572..7a34320 100644 --- a/TOFileSystemObserver/Entities/Items/TOFileSystemItem.h +++ b/TOFileSystemObserver/Entities/Items/TOFileSystemItem.h @@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN or folder on disk. */ NS_SWIFT_NAME(FileSystemItem) +__attribute__((objc_subclassing_restricted)) @interface TOFileSystemItem : NSObject /** The absolute URL path to this item. */ diff --git a/TOFileSystemObserver/Entities/Items/TOFileSystemItem.m b/TOFileSystemObserver/Entities/Items/TOFileSystemItem.m index 0d03b27..2b3f8d2 100644 --- a/TOFileSystemObserver/Entities/Items/TOFileSystemItem.m +++ b/TOFileSystemObserver/Entities/Items/TOFileSystemItem.m @@ -67,8 +67,9 @@ @implementation TOFileSystemItem #pragma mark - Class Creation - - (instancetype)initWithItemAtFileURL:(NSURL *)fileURL - fileSystemObserver:(TOFileSystemObserver *)observer -{ + fileSystemObserver:(TOFileSystemObserver *)observer { + NSParameterAssert(fileURL != nil); + NSParameterAssert(observer != nil); if (self = [super init]) { _fileURL = fileURL; _fileSystemObserver = observer; @@ -82,9 +83,9 @@ - (instancetype)initWithItemAtFileURL:(NSURL *)fileURL // If this item represents a deleted file, skip gathering the data if (!self.isDeleted) { - [self performWithLock:^{ - [self configureUUID]; - [self refreshFromItemAtURL:fileURL]; + [self _performWithLock:^{ + [self _configureUUID]; + [self _refreshFromItemAtURL:fileURL]; }]; } } @@ -94,14 +95,12 @@ - (instancetype)initWithItemAtFileURL:(NSURL *)fileURL #pragma mark - Update Properties - -- (void)configureUUID -{ - TOFileSystemPresenter *presenter = self.fileSystemObserver.fileSystemPresenter; +- (void)_configureUUID { + TOFileSystemPresenter * const presenter = self.fileSystemObserver.fileSystemPresenter; _uuid = [presenter uuidForItemAtURL:_fileURL]; } -- (BOOL)refreshFromItemAtURL:(NSURL *)url -{ +- (BOOL)_refreshFromItemAtURL:(NSURL *)url { BOOL hasChanges = NO; // Copy the new URL to this item @@ -110,45 +109,45 @@ - (BOOL)refreshFromItemAtURL:(NSURL *)url } // Copy the name of the item - NSString *name = [_fileURL lastPathComponent]; + NSString * const name = [_fileURL lastPathComponent]; if (_name.length == 0 || ![name isEqualToString:_name]) { _name = name; hasChanges = YES; } // Check if it is a file or directory - TOFileSystemItemType type = _fileURL.to_isDirectory ? TOFileSystemItemTypeDirectory : - TOFileSystemItemTypeFile; + const TOFileSystemItemType type = _fileURL.to_isDirectory ? TOFileSystemItemTypeDirectory : + TOFileSystemItemTypeFile; if (type != _type) { _type = type; hasChanges = YES; } // Get its creation date - NSDate *creationDate = _fileURL.to_creationDate; + NSDate * const creationDate = _fileURL.to_creationDate; if (![_creationDate isEqualToDate:creationDate]) { _creationDate = creationDate; hasChanges = YES; } - + // Get its modification date - NSDate *modificationDate = _fileURL.to_modificationDate; + NSDate * const modificationDate = _fileURL.to_modificationDate; if (![_modificationDate isEqualToDate:modificationDate]) { _modificationDate = modificationDate; hasChanges = YES; } - + // If the type is a file if (_type == TOFileSystemItemTypeFile) { // Fetch the item file size - long long fileSize = _fileURL.to_size; + const long long fileSize = _fileURL.to_size; if (fileSize != _size) { _size = fileSize; hasChanges = YES; } - + // Check to see if it is copying - BOOL isCopying = _fileURL.to_isCopying; + const BOOL isCopying = _fileURL.to_isCopying; if (isCopying != _isCopying) { _isCopying = isCopying; hasChanges = YES; @@ -156,7 +155,7 @@ - (BOOL)refreshFromItemAtURL:(NSURL *)url } else { // Else, it's a directory, count the number of items inside - NSInteger numberOfChildItems = [_fileURL to_numberOfSubItems]; + const NSInteger numberOfChildItems = [_fileURL to_numberOfSubItems]; if (_numberOfSubItems != numberOfChildItems) { _numberOfSubItems = numberOfChildItems; hasChanges = YES; @@ -166,23 +165,21 @@ - (BOOL)refreshFromItemAtURL:(NSURL *)url return hasChanges; } -- (BOOL)isDeleted -{ +- (BOOL)isDeleted { return ![[NSFileManager defaultManager] fileExistsAtPath:self.fileURL.path]; } #pragma mark - Lists - -- (BOOL)refreshWithURL:(nullable NSURL *)itemURL -{ +- (BOOL)refreshWithURL:(nullable NSURL *)itemURL { // Perform a re-fetch of all of the properties of the // item from disk, and re-populate all of the properties. // A lock needs to be used as this operation will ideally be done // in the background due to how heavy it could potentially be __block BOOL hasChanges = NO; - [self performWithLock:^{ - hasChanges = [self refreshFromItemAtURL:itemURL]; + [self _performWithLock:^{ + hasChanges = [self _refreshFromItemAtURL:itemURL]; }]; // If it was detected one or more of the properties were @@ -198,29 +195,25 @@ - (BOOL)refreshWithURL:(nullable NSURL *)itemURL return hasChanges; } -- (void)addToList:(TOFileSystemItemList *)list -{ +- (void)addToList:(TOFileSystemItemList *)list { self.list = list; } -- (void)removeFromList -{ +- (void)removeFromList { self.list = nil; } #pragma mark - Equality - -- (BOOL)isEqual:(id)object -{ +- (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:TOFileSystemItem.class]) { return NO; } - - TOFileSystemItem *item = (TOFileSystemItem *)object; + + TOFileSystemItem * const item = (TOFileSystemItem *)object; return [item.uuid isEqualToString:self.uuid]; } -- (NSUInteger)hash -{ +- (NSUInteger)hash { return self.uuid.hash; } @@ -228,39 +221,36 @@ - (NSUInteger)hash // To ensure thread safety, fetch the value of an object // on the barrier queue -- (id)fetchValueForObject:(NSString *)objectName -{ +- (id)_fetchValueForObject:(NSString *)objectName { __block id objectValue = nil; - [self performWithLock:^{ + [self _performWithLock:^{ objectValue = [self valueForKey:objectName]; }]; return objectValue; } -- (long long)fetchValueForInteger:(NSString *)integerName -{ +- (long long)_fetchValueForInteger:(NSString *)integerName { __block long long intValue = 0; - [self performWithLock:^{ + [self _performWithLock:^{ intValue = [[self valueForKey:integerName] longLongValue]; }]; return intValue; } -- (NSURL *)fileURL { return (NSURL *)[self fetchValueForObject:@"_fileURL"]; } -- (NSString *)uuid { return (NSString *)[self fetchValueForObject:@"_uuid"]; } -- (NSString *)name { return (NSString *)[self fetchValueForObject:@"_name"]; } -- (long long)size { return (long long)[self fetchValueForInteger:@"_size"]; } -- (NSDate *)creationDate { return (NSDate *)[self fetchValueForObject:@"_creationDate"]; } -- (NSDate *)modificationDate { return (NSDate *)[self fetchValueForObject:@"_modificationDate"]; } -- (BOOL)isCopying { return (BOOL)[self fetchValueForInteger:@"_isCopying"]; } -- (NSInteger)numberOfSubItems { return (NSInteger)[self fetchValueForInteger:@"_numberOfSubItems"]; } +- (NSURL *)fileURL { return (NSURL *)[self _fetchValueForObject:@"_fileURL"]; } +- (NSString *)uuid { return (NSString *)[self _fetchValueForObject:@"_uuid"]; } +- (NSString *)name { return (NSString *)[self _fetchValueForObject:@"_name"]; } +- (long long)size { return (long long)[self _fetchValueForInteger:@"_size"]; } +- (NSDate *)creationDate { return (NSDate *)[self _fetchValueForObject:@"_creationDate"]; } +- (NSDate *)modificationDate { return (NSDate *)[self _fetchValueForObject:@"_modificationDate"]; } +- (BOOL)isCopying { return (BOOL)[self _fetchValueForInteger:@"_isCopying"]; } +- (NSInteger)numberOfSubItems { return (NSInteger)[self _fetchValueForInteger:@"_numberOfSubItems"]; } #pragma mark - Thread Safe Access - -- (void)performWithLock:(void (^)(void))block; -{ +- (void)_performWithLock:(void (^)(void))block; { // Lock the current thread if (@available(iOS 10.0, *)) { os_unfair_lock_lock(&_unfairLock); @@ -280,15 +270,14 @@ - (void)performWithLock:(void (^)(void))block; #pragma mark - Debugging - -- (NSString *)description -{ - NSString *description = @"TOFileSystem Item - \n" - @"Name: %@\n" - @"UUID: %@\n" - @"Type: %@\n" - @"Size: %d\n" - @"Created: %@\n" - @"Modified: %@\n"; +- (NSString *)description { + NSString * const description = @"TOFileSystem Item - \n" + @"Name: %@\n" + @"UUID: %@\n" + @"Type: %@\n" + @"Size: %d\n" + @"Created: %@\n" + @"Modified: %@\n"; return [NSString stringWithFormat:description, self.name, diff --git a/TOFileSystemObserver/Entities/Items/TOFileSystemItemList+Private.h b/TOFileSystemObserver/Entities/Items/TOFileSystemItemList+Private.h index 800c4cb..e5142a1 100644 --- a/TOFileSystemObserver/Entities/Items/TOFileSystemItemList+Private.h +++ b/TOFileSystemObserver/Entities/Items/TOFileSystemItemList+Private.h @@ -34,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN /** Creates a new instance of an item for the target item. */ - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL - fileSystemObserver:(TOFileSystemObserver *)observer; + fileSystemObserver:(TOFileSystemObserver *)observer NS_DESIGNATED_INITIALIZER; /** Add a new item to the list. */ - (void)addItemWithUUID:(NSString *)uuid itemURL:(NSURL *)url; diff --git a/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.h b/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.h index 923dcea..0a313c5 100644 --- a/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.h +++ b/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.h @@ -41,6 +41,7 @@ NS_ASSUME_NONNULL_BEGIN the file system. */ NS_SWIFT_NAME(FileSystemItemList) +__attribute__((objc_subclassing_restricted)) @interface TOFileSystemItemList : NSObject /** The unique UUID string saved in the attributes of this directory. */ diff --git a/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.m b/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.m index 9586f57..1b8c48c 100644 --- a/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.m +++ b/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.m @@ -57,107 +57,107 @@ @interface TOFileSystemItemList () @property (nonatomic, strong) NSMutableArray *sortedItems; /** A set that holds all of the notification tokens generated by this list */ -@property (nonatomic, strong) NSHashTable *notificationTokens; +@property (nonatomic, strong) NSHashTable *notificationTokens; @end @implementation TOFileSystemItemList - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL - fileSystemObserver:(TOFileSystemObserver *)observer -{ + fileSystemObserver:(TOFileSystemObserver *)observer { + NSParameterAssert(directoryURL != nil); + NSParameterAssert(observer != nil); if (self = [super init]) { _fileSystemObserver = observer; _directoryURL = directoryURL; _uuid = [directoryURL to_fileSystemUUID]; - [self commonInit]; + [self _commonInit]; } return self; } -- (void)commonInit -{ +- (void)_commonInit { // Create the file list stores _items = [NSMutableDictionary dictionary]; _sortedItems = [NSMutableArray array]; } -- (void)buildItemsList -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSDirectoryEnumerator *enumerator = [fileManager to_fileSystemEnumeratorForDirectoryAtURL:_directoryURL]; - +- (void)_buildItemsList { + NSFileManager * const fileManager = [NSFileManager defaultManager]; + NSDirectoryEnumerator * const enumerator = [fileManager to_fileSystemEnumeratorForDirectoryAtURL:_directoryURL]; + // Build a new list of files from what is currently on disk - for (NSURL *url in enumerator) { - TOFileSystemItem *item = [self.fileSystemObserver itemForFileAtURL:url]; + for (NSURL * const url in enumerator) { + TOFileSystemItem * const item = [self.fileSystemObserver itemForFileAtURL:url]; // Add the list to the item's store so it can notify of updates [item addToList:self]; - + // Capture the item with its UUID in the dictionary _items[item.uuid] = item; } // Sort according to our current sort settings _sortedItems = _items.allKeys.mutableCopy; - [self sortItemsList]; + [self _sortItemsList]; } -- (void)rebuildItemListForListingOrder -{ +- (void)_rebuildItemListForListingOrder { if (self.sortedItems.count == 0) { return; } - + // Grab a copy of the current list - NSArray *previousList = [self.sortedItems copy]; - + NSArray * const previousList = [self.sortedItems copy]; + // Sort the list to the new order - [self sortItemsList]; - + [self _sortItemsList]; + // Build a dictionary of all of the UUIDs so we can map the old // ordering to the new ordering, but use the hashing features of the // dictionary to avoid doing random lookup each time for each item - NSMutableDictionary *newSortedItemsDict = [NSMutableDictionary dictionary]; + NSMutableDictionary * const newSortedItemsDict = [NSMutableDictionary dictionary]; for (NSInteger i = 0; i < self.sortedItems.count; i++) { newSortedItemsDict[self.sortedItems[i]] = @(i); } - + // Loop through and build a list of indices for each moved cell. - TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init]; + TOFileSystemItemListChanges * const changes = [[TOFileSystemItemListChanges alloc] init]; for (NSInteger i = 0; i < previousList.count; i++) { // Work out where the item in the new list went - NSInteger newIndex = [newSortedItemsDict[previousList[i]] intValue]; + const NSInteger newIndex = [newSortedItemsDict[previousList[i]] intValue]; [changes addMovementWithSourceIndex:i destinationIndex:newIndex]; } - + // Trigger the notification blocks to update any UI with this new order - for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { + for (TOFileSystemNotificationToken * const token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } } #pragma mark - Sorting Items - -- (NSComparator)sortComparator -{ +- (NSComparator)_sortComparator { __weak typeof(self) weakSelf = self; return ^NSComparisonResult(NSString *firstUUID, NSString *secondUUID) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf == nil) { return NSOrderedSame; } + // Check if the UUID matches if ([firstUUID isEqualToString:secondUUID]) { return NSOrderedSame; } - - TOFileSystemItem *firstItem = weakSelf.items[firstUUID]; - TOFileSystemItem *secondItem = weakSelf.items[secondUUID]; - + + TOFileSystemItem *firstItem = strongSelf.items[firstUUID]; + TOFileSystemItem *secondItem = strongSelf.items[secondUUID]; + // If the order is flipped, swap around the two items - if (self.isDescending) { + if (strongSelf.isDescending) { TOFileSystemItem *tempItem = firstItem; firstItem = secondItem; secondItem = tempItem; } - - switch (weakSelf.listOrder) { + + switch (strongSelf.listOrder) { case TOFileSystemItemListOrderAlphanumeric: { return [firstItem.name localizedStandardCompare:secondItem.name]; @@ -170,7 +170,7 @@ - (NSComparator)sortComparator { // File sizes always go descending by default. // Compare file names if the sizes match to keep clean ordering (Because folders are always 0) - NSComparisonResult result = [@(secondItem.size) compare:@(firstItem.size)]; + const NSComparisonResult result = [@(secondItem.size) compare:@(firstItem.size)]; if (result == NSOrderedSame) { return [firstItem.name localizedStandardCompare:secondItem.name]; } return result; } @@ -178,46 +178,40 @@ - (NSComparator)sortComparator }; } -- (void)sortItemsList -{ +- (void)_sortItemsList { // Sort all of the UUIDS - [_sortedItems sortUsingComparator:self.sortComparator]; + [_sortedItems sortUsingComparator:self._sortComparator]; } -- (NSUInteger)sortedIndexForItemWithUUID:(NSString *)uuid -{ +- (NSUInteger)_sortedIndexForItemWithUUID:(NSString *)uuid { return [self.sortedItems indexOfObject:uuid inSortedRange:(NSRange){0, self.sortedItems.count} options:NSBinarySearchingInsertionIndex - usingComparator:self.sortComparator]; + usingComparator:self._sortComparator]; } #pragma mark - External Item Access - -- (NSUInteger)count -{ +- (NSUInteger)count { // Lazy-load the list when we query for the first time. if (self.sortedItems.count == 0) { - [self buildItemsList]; + [self _buildItemsList]; } return self.items.count; } -- (TOFileSystemItem *)objectAtIndex:(NSUInteger)index -{ +- (TOFileSystemItem *)objectAtIndex:(NSUInteger)index { return self.items[self.sortedItems[index]]; } -- (id)objectAtIndexedSubscript:(NSUInteger)index -{ +- (id)objectAtIndexedSubscript:(NSUInteger)index { return self.items[self.sortedItems[index]]; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer - count:(NSUInteger)len -{ + count:(NSUInteger)len { return [_items countByEnumeratingWithState:state objects:buffer count:len]; @@ -225,67 +219,64 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state #pragma mark - Live Item Updating - -- (void)addItemWithUUID:(NSString *)uuid itemURL:(NSURL *)url -{ +- (void)addItemWithUUID:(NSString *)uuid itemURL:(NSURL *)url { // Skip if this item is already in the list if (self.items[uuid]) { return; } - + // Generate a new item and add it to our list - TOFileSystemItem *item = [self.fileSystemObserver itemForFileAtURL:url]; + TOFileSystemItem * const item = [self.fileSystemObserver itemForFileAtURL:url]; [item addToList:self]; self.items[item.uuid] = item; - + // Work out where the item should go in our sorted list - NSUInteger sortedIndex = [self sortedIndexForItemWithUUID:item.uuid]; + const NSUInteger sortedIndex = [self _sortedIndexForItemWithUUID:item.uuid]; [self.sortedItems insertObject:item.uuid atIndex:sortedIndex]; - + // Perform the broadcast to any observing objects that this update ocurred - TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init]; + TOFileSystemItemListChanges * const changes = [[TOFileSystemItemListChanges alloc] init]; [changes addInsertionIndex:sortedIndex]; - for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { + for (TOFileSystemNotificationToken * const token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } } -- (void)removeItemWithUUID:(NSString *)uuid fileURL:(NSURL *)url -{ +- (void)removeItemWithUUID:(NSString *)uuid fileURL:(NSURL *)url { // Verify the item is still here if (self.items[uuid] == nil) { return; } - + // Work out where the item is in the list - NSInteger index = [self.sortedItems indexOfObject:uuid]; + const NSInteger index = [self.sortedItems indexOfObject:uuid]; NSAssert(index != NSNotFound, @"items and sortedItems should never be out of sync"); - + // Un-assign the list [self.items[uuid] removeFromList]; - + // Remove the item from both stores [self.items removeObjectForKey:uuid]; [self.sortedItems removeObjectAtIndex:index]; - + // Trigger the notification blocks - TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init]; + TOFileSystemItemListChanges * const changes = [[TOFileSystemItemListChanges alloc] init]; [changes addDeletionIndex:index]; - - for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { + + for (TOFileSystemNotificationToken * const token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } } -- (void)itemDidRefreshWithUUID:(NSString *)uuid -{ +- (void)itemDidRefreshWithUUID:(NSString *)uuid { // Verify the item is still here if (self.items[uuid] == nil) { return; } - + // Create a changes object for the notification blocks - TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init]; - + TOFileSystemItemListChanges * const changes = [[TOFileSystemItemListChanges alloc] init]; + // Work out where it is in the list - NSInteger oldIndex = [self.sortedItems indexOfObject:uuid]; - + const NSInteger oldIndex = [self.sortedItems indexOfObject:uuid]; + // Work out where it should go in the list [self.sortedItems removeObjectAtIndex:oldIndex]; - NSInteger newIndex = [self sortedIndexForItemWithUUID:uuid]; + const NSInteger newIndex = [self _sortedIndexForItemWithUUID:uuid]; // Move it to the new location if (oldIndex != newIndex) { @@ -300,39 +291,38 @@ - (void)itemDidRefreshWithUUID:(NSString *)uuid [changes addModificationIndex:newIndex]; // Broadcast the changes - for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { + for (TOFileSystemNotificationToken * const token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } } -- (void)synchronizeWithDisk -{ +- (void)synchronizeWithDisk { // After a scan and all present files have been verified, it's possible there // are some items lingering from files that were deleted. - + // Loop through every file in this list, and double-check it's still on disk - TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init]; + TOFileSystemItemListChanges * const changes = [[TOFileSystemItemListChanges alloc] init]; for (NSInteger i = 0; i < self.sortedItems.count; i++) { - TOFileSystemItem *item = self.items[self.sortedItems[i]]; + TOFileSystemItem * const item = self.items[self.sortedItems[i]]; if (item.isDeleted) { [changes addDeletionIndex:i]; } } - + // Skip if every file was accounted for if (changes.deletions.count == 0) { return; } - + // Remove all of the deleted files from the list. Iterate from highest index // to lowest so each removal doesn't shift the indices we still need to use. - for (NSNumber *deletedIndex in [changes.deletions reverseObjectEnumerator]) { - NSString *uuid = self.sortedItems[deletedIndex.intValue]; + for (NSNumber * const deletedIndex in [changes.deletions reverseObjectEnumerator]) { + NSString * const uuid = self.sortedItems[deletedIndex.intValue]; [self.sortedItems removeObjectAtIndex:deletedIndex.intValue]; [self.items removeObjectForKey:uuid]; } - + // Broadcast the changes dispatch_async(dispatch_get_main_queue(), ^{ - for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { + for (TOFileSystemNotificationToken * const token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } }); @@ -340,9 +330,9 @@ - (void)synchronizeWithDisk #pragma mark - Notification Token - -- (TOFileSystemNotificationToken *)addNotificationBlock:(TOFileSystemItemListNotificationBlock)block -{ - TOFileSystemNotificationToken *token = [TOFileSystemNotificationToken tokenWithObservingObject:self block:block]; +- (TOFileSystemNotificationToken *)addNotificationBlock:(TOFileSystemItemListNotificationBlock)block { + NSParameterAssert(block != nil); + TOFileSystemNotificationToken * const token = [TOFileSystemNotificationToken tokenWithObservingObject:self block:block]; if (self.notificationTokens == nil) { self.notificationTokens = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; } @@ -350,29 +340,25 @@ - (TOFileSystemNotificationToken *)addNotificationBlock:(TOFileSystemItemListNot return token; } -- (void)removeNotificationToken:(TOFileSystemNotificationToken *)token -{ +- (void)removeNotificationToken:(TOFileSystemNotificationToken *)token { [self.notificationTokens removeObject:token]; } #pragma mark - Accessors - -- (void)setListOrder:(TOFileSystemItemListOrder)listOrder -{ +- (void)setListOrder:(TOFileSystemItemListOrder)listOrder { if (_listOrder == listOrder) { return; } _listOrder = listOrder; - [self rebuildItemListForListingOrder]; + [self _rebuildItemListForListingOrder]; } -- (void)setIsDescending:(BOOL)isDescending -{ +- (void)setIsDescending:(BOOL)isDescending { if (_isDescending == isDescending) { return; } _isDescending = isDescending; - [self rebuildItemListForListingOrder]; + [self _rebuildItemListForListingOrder]; } -- (BOOL)refreshWithURL:(NSURL *)directoryURL -{ +- (BOOL)refreshWithURL:(NSURL *)directoryURL { if (directoryURL == nil) { return NO; } BOOL hasChanges = NO; @@ -388,8 +374,7 @@ - (BOOL)refreshWithURL:(NSURL *)directoryURL #pragma mark - Debugging - -- (NSString *)description -{ +- (NSString *)description { return [NSString stringWithFormat:@"url = '%@', uuid = '%@', listOrder = %ld, isDescending = %d, items = '%@'", _directoryURL, _uuid, diff --git a/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.h b/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.h index 9f327cb..f112caf 100644 --- a/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.h +++ b/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.h @@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN remove itself from the observing object when deallocated. */ NS_SWIFT_NAME(FileSystemNotificationToken) +__attribute__((objc_subclassing_restricted)) @interface TOFileSystemNotificationToken : NSObject /** diff --git a/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.m b/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.m index 8402424..ef3ddab 100644 --- a/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.m +++ b/TOFileSystemObserver/Entities/Notifications/TOFileSystemNotificationToken.m @@ -28,21 +28,20 @@ @implementation TOFileSystemNotificationToken #pragma mark - Class Creation - + (instancetype)tokenWithObservingObject:(id)observingObject - block:(id)block -{ - TOFileSystemNotificationToken *token = [[TOFileSystemNotificationToken alloc] init]; + block:(id)block { + NSParameterAssert(observingObject != nil); + NSParameterAssert(block != nil); + TOFileSystemNotificationToken * const token = [[TOFileSystemNotificationToken alloc] init]; token.observingObject = observingObject; token.notificationBlock = block; return token; } -- (void)dealloc -{ +- (void)dealloc { [self invalidate]; } -- (void)invalidate -{ +- (void)invalidate { [self.observingObject removeNotificationToken:self]; } diff --git a/TOFileSystemObserver/Scanning/TOFileSystemPresenter.h b/TOFileSystemObserver/Scanning/TOFileSystemPresenter.h index 2fc2338..f09efaa 100644 --- a/TOFileSystemObserver/Scanning/TOFileSystemPresenter.h +++ b/TOFileSystemObserver/Scanning/TOFileSystemPresenter.h @@ -29,6 +29,7 @@ NS_ASSUME_NONNULL_BEGIN when items in its target directory change, and exposes a UUID accessor backed by extended file attributes. */ +__attribute__((objc_subclassing_restricted)) @interface TOFileSystemPresenter : NSObject /** The directory that will be observed by this presenter object */ diff --git a/TOFileSystemObserver/Scanning/TOFileSystemPresenter.m b/TOFileSystemObserver/Scanning/TOFileSystemPresenter.m index bda493c..bbdfe7f 100644 --- a/TOFileSystemObserver/Scanning/TOFileSystemPresenter.m +++ b/TOFileSystemObserver/Scanning/TOFileSystemPresenter.m @@ -32,7 +32,7 @@ @interface TOFileSystemPresenter () @property (nonatomic, strong) NSOperationQueue *eventsOperationQueue; /** The list of items currently detected. */ -@property (nonatomic, strong) NSMutableArray *items; +@property (nonatomic, strong) NSMutableArray *items; /** A serial queue for managing access to the list (including the timer) */ @property (nonatomic, strong) dispatch_queue_t itemListAccessQueue; @@ -46,17 +46,15 @@ @implementation TOFileSystemPresenter #pragma mark - Class Lifecycle - -- (instancetype)init -{ +- (instancetype)init { if (self = [super init]) { - [self commonInit]; + [self _commonInit]; } return self; } -- (void)commonInit -{ +- (void)_commonInit { // Create the queue to receive events _eventsOperationQueue = [[NSOperationQueue alloc] init]; _eventsOperationQueue.qualityOfService = NSQualityOfServiceBackground; @@ -71,15 +69,13 @@ - (void)commonInit _timerInterval = 0.1f; } -- (void)dealloc -{ +- (void)dealloc { [self stop]; } #pragma mark - Timer Handling - -- (void)beginTimer -{ +- (void)_beginTimer { // When the timer finishes, create a copy of the items, // and then flush what we currently have in the main item list id completionBlock = ^{ @@ -87,7 +83,7 @@ - (void)beginTimer self.isTiming = NO; @autoreleasepool { - NSArray *items = [self.items copy]; + NSArray * const items = [self.items copy]; [self.items removeAllObjects]; if (items.count == 0) { return; } @@ -112,8 +108,7 @@ - (void)beginTimer #pragma mark - Item Handling - -- (void)addItemToList:(NSURL *)itemURL -{ +- (void)_addItemToList:(NSURL *)itemURL { // Add the new item to the items list in a barrier queue access. dispatch_async(self.itemListAccessQueue, ^{ [self.items addObject:itemURL]; @@ -122,15 +117,13 @@ - (void)addItemToList:(NSURL *)itemURL #pragma mark - Public Control - -- (void)start -{ +- (void)start { if (self.isRunning) { return; } [NSFileCoordinator addFilePresenter:self]; self.isRunning = YES; } -- (void)stop -{ +- (void)stop { if (!self.isRunning) { return; } [NSFileCoordinator removeFilePresenter:self]; self.isRunning = NO; @@ -144,8 +137,7 @@ - (void)stop }); } -- (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL -{ +- (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL { // Fast path: the file already has a UUID attribute. NSString *uuid = [itemURL to_fileSystemUUID]; if (uuid.length) { return uuid; } @@ -164,19 +156,16 @@ - (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL #pragma mark - NSFilePresenter Delegate Events - -- (void)presentedSubitemDidChangeAtURL:(NSURL *)url -{ - [self addItemToList:url]; - [self beginTimer]; +- (void)presentedSubitemDidChangeAtURL:(NSURL *)url { + [self _addItemToList:url]; + [self _beginTimer]; } -- (NSURL *)presentedItemURL -{ +- (NSURL *)presentedItemURL { return self.directoryURL; } -- (NSOperationQueue *)presentedItemOperationQueue -{ +- (NSOperationQueue *)presentedItemOperationQueue { return self.eventsOperationQueue; } diff --git a/TOFileSystemObserver/Scanning/TOFileSystemScanOperation.m b/TOFileSystemObserver/Scanning/TOFileSystemScanOperation.m index 3975f8c..1416346 100644 --- a/TOFileSystemObserver/Scanning/TOFileSystemScanOperation.m +++ b/TOFileSystemObserver/Scanning/TOFileSystemScanOperation.m @@ -37,7 +37,7 @@ @interface TOFileSystemScanOperation () @property (nonatomic, strong) NSURL *directoryURL; /** A flat list of file URLs to scan. */ -@property (nonatomic, strong) NSArray *itemURLs; +@property (nonatomic, strong) NSArray *itemURLs; /** A reference to the file system presenter object so we may pause when causing file writes. */ @property (nonatomic, strong) TOFileSystemPresenter *filePresenter; @@ -46,10 +46,10 @@ @interface TOFileSystemScanOperation () @property (nonatomic, strong) NSFileManager *fileManager; /** When iterating through all the files, this array stores pending directories that need scanning*/ -@property (nonatomic, strong) NSMutableArray *pendingDirectories; +@property (nonatomic, strong) NSMutableArray *pendingDirectories; /** A list of items we've been instructed to skip. */ -@property (nonatomic, strong) NSArray *skippedItems; +@property (nonatomic, strong) NSArray *skippedItems; /** A reference to the master list of items maintained by this observer. */ @property (nonatomic, strong) TOFileSystemItemURLDictionary *allItems; @@ -69,8 +69,7 @@ @implementation TOFileSystemScanOperation - (instancetype)initForFullScanWithDirectoryAtURL:(NSURL *)directoryURL skippingItems:(NSArray *)skippedItems allItemsDictionary:(nonnull TOFileSystemItemURLDictionary *)allItems - filePresenter:(nonnull TOFileSystemPresenter *)filePresenter -{ + filePresenter:(nonnull TOFileSystemPresenter *)filePresenter { if (self = [super init]) { _isFullScan = YES; _directoryURL = directoryURL.URLByStandardizingPath; @@ -78,7 +77,7 @@ - (instancetype)initForFullScanWithDirectoryAtURL:(NSURL *)directoryURL _skippedItems = skippedItems; _allItems = allItems; _pendingDirectories = [NSMutableArray array]; - [self commonInit]; + [self _commonInit]; } return self; @@ -88,8 +87,7 @@ - (instancetype)initForItemScanWithItemURLs:(NSArray *)itemURLs baseURL:(NSURL *)baseURL skippingItems:(NSArray *)skippedItems allItemsDictionary:(nonnull TOFileSystemItemURLDictionary *)allItems - filePresenter:(nonnull TOFileSystemPresenter *)filePresenter -{ + filePresenter:(nonnull TOFileSystemPresenter *)filePresenter { if (self = [super init]) { _directoryURL = baseURL.URLByStandardizingPath; _filePresenter = filePresenter; @@ -98,22 +96,20 @@ - (instancetype)initForItemScanWithItemURLs:(NSArray *)itemURLs _allItems = allItems; _pendingDirectories = [NSMutableArray array]; _missingItems = [NSMutableDictionary dictionary]; - [self commonInit]; + [self _commonInit]; } return self; } -- (void)commonInit -{ +- (void)_commonInit { _subDirectoryLevelLimit = -1; _fileManager = [[NSFileManager alloc] init]; } #pragma mark - Scanning Implementation - -- (void)main -{ +- (void)main { // Terminate out if this operation was cancelled before it started // Once it's started however, we need to see it through to completion // to prevent leaving things in an inconsistent state. @@ -123,91 +119,87 @@ - (void)main // or a flat list of files was provided, perform // different scan patterns if (self.isFullScan) { - [self scanAllSubdirectoriesFromBaseURL]; + [self _scanAllSubdirectoriesFromBaseURL]; } else if (self.itemURLs) { - [self scanItemURLsList]; + [self _scanItemURLsList]; } } #pragma mark - Deep Hierarcy Directory Scan - -- (void)scanAllSubdirectoriesFromBaseURL -{ +- (void)_scanAllSubdirectoriesFromBaseURL { // Post the "will begin" notification before doing any work so consumers // see paired begin/complete events even when the directory is empty. [self.delegate scanOperationWillBeginFullScan:self]; // Scan all of the items in the base directory. An empty directory yields // an empty enumeration, which is a valid (no-op) state. - NSArray *childItemURLs = [self.fileManager to_fileSystemEnumeratorForDirectoryAtURL:self.directoryURL].allObjects; - for (NSURL *url in childItemURLs) { - [self scanItemAtURL:url + NSArray * const childItemURLs = [self.fileManager to_fileSystemEnumeratorForDirectoryAtURL:self.directoryURL].allObjects; + for (NSURL * const url in childItemURLs) { + [self _scanItemAtURL:url pendingDirectories:self.pendingDirectories]; } // If we were only scanning the immediate contents of the base directory, // skip the recursive pass. if (self.subDirectoryLevelLimit != 0) { - [self scanPendingSubdirectories]; + [self _scanPendingSubdirectories]; } [self.delegate scanOperationDidCompleteFullScan:self]; } -- (void)scanPendingSubdirectories -{ - NSMutableArray *pendingDirectories = self.pendingDirectories; +- (void)_scanPendingSubdirectories { + NSMutableArray * const pendingDirectories = self.pendingDirectories; // If there were any directories in the base, start a flat loop to scan // all subdirectories too (Avoiding potential stack overflows!) while (pendingDirectories.count > 0) { // Extract the item, and then remove it from the pending list - NSURL *url = pendingDirectories.firstObject; + NSURL * const url = pendingDirectories.firstObject; [pendingDirectories removeObjectAtIndex:0]; // Exit out if we've gone deeper than the specified limit if (self.subDirectoryLevelLimit > 0) { - NSInteger levels = [self numberOfDirectoryLevelsToURL:url]; + const NSInteger levels = [self _numberOfDirectoryLevelsToURL:url]; if (levels >= self.subDirectoryLevelLimit) { continue; } } // Create a new enumerator for it - NSDirectoryEnumerator *enumerator = [self.fileManager to_fileSystemEnumeratorForDirectoryAtURL:url]; - for (NSURL *url in enumerator) { - [self scanItemAtURL:url pendingDirectories:pendingDirectories]; + NSDirectoryEnumerator * const enumerator = [self.fileManager to_fileSystemEnumeratorForDirectoryAtURL:url]; + for (NSURL * const url in enumerator) { + [self _scanItemAtURL:url pendingDirectories:pendingDirectories]; } } } #pragma mark - Flat File List Scan - -- (void)scanItemURLsList -{ +- (void)_scanItemURLsList { // Loop through each reported file URL and perform a scan to see what changed - for (NSURL *itemURL in self.itemURLs) { - [self verifyEveryParentDirectoryForURL:itemURL]; - [self scanItemAtURL:itemURL pendingDirectories:self.pendingDirectories]; + for (NSURL * const itemURL in self.itemURLs) { + [self _verifyEveryParentDirectoryForURL:itemURL]; + [self _scanItemAtURL:itemURL pendingDirectories:self.pendingDirectories]; } - + // After all files are scanned, clean out any files - [self cleanUpFilesPendingDeletion]; + [self _cleanUpFilesPendingDeletion]; } #pragma mark - Scanning Logic - -- (void)scanItemAtURL:(NSURL *)url pendingDirectories:(NSMutableArray *)pendingDirectories -{ +- (void)_scanItemAtURL:(NSURL *)url pendingDirectories:(NSMutableArray *)pendingDirectories { // Sanitize the URL so we can use it in comparisons url = url.URLByStandardizingPath; - + // Make sure it's not a hidden file - NSString *name = url.lastPathComponent; + NSString * const name = url.lastPathComponent; if ([name characterAtIndex:0] == '.') { return; } - + // Check if it's a skipped one - for (NSString *skippedFileName in self.skippedItems) { - NSURL *skippedURL = [self.directoryURL URLByAppendingPathComponent:skippedFileName]; + for (NSString * const skippedFileName in self.skippedItems) { + NSURL * const skippedURL = [self.directoryURL URLByAppendingPathComponent:skippedFileName]; if ([url isEqual:skippedURL]) { return; } @@ -215,7 +207,7 @@ - (void)scanItemAtURL:(NSURL *)url pendingDirectories:(NSMutableArray *)pendingD // Double-check the file is still at that URL // (The file presenter will sometimes provide the old URL for moved files) - if (![self verifyItemIsNotMissingAtURL:url]) { + if (![self _verifyItemIsNotMissingAtURL:url]) { return; } @@ -234,24 +226,23 @@ - (void)scanItemAtURL:(NSURL *)url pendingDirectories:(NSMutableArray *)pendingD } // Check if the item had been moved - if (![self verifyIfItemWasMovedOrDeletedWithURL:url uuid:uuid]) { + if (![self _verifyIfItemWasMovedOrDeletedWithURL:url uuid:uuid]) { return; } // Verify this file has a unique UUID. - uuid = [self uniqueUUIDForItemAtURL:url withUUID:uuid]; + uuid = [self _uniqueUUIDForItemAtURL:url withUUID:uuid]; // Perform a verification of the item, and trigger the appropriate notifications - [self verifyItemAtURL:url uuid:uuid]; + [self _verifyItemAtURL:url uuid:uuid]; } -- (void)verifyEveryParentDirectoryForURL:(NSURL *)url -{ - NSURL *directoryURL = self.directoryURL.URLByStandardizingPath; - - // Make sure that this file isn't hidden, or inside a hidden folder file +- (void)_verifyEveryParentDirectoryForURL:(NSURL *)url { + NSURL * const directoryURL = self.directoryURL.URLByStandardizingPath; + + // Make sure that this file isn't hidden, or inside a hidden folder file url = url.URLByStandardizingPath; - NSString *relativePath = [url.path stringByReplacingOccurrencesOfString:directoryURL.path withString:@""]; + NSString * const relativePath = [url.path stringByReplacingOccurrencesOfString:directoryURL.path withString:@""]; if ([relativePath rangeOfString:@"/."].location != NSNotFound) { return; } @@ -282,8 +273,7 @@ - (void)verifyEveryParentDirectoryForURL:(NSURL *)url } } -- (BOOL)verifyItemIsNotMissingAtURL:(NSURL *)url -{ +- (BOOL)_verifyItemIsNotMissingAtURL:(NSURL *)url { // Exit out if we're not interested in tracking deleted files in this operation if (self.missingItems == nil) { return YES; } @@ -293,42 +283,41 @@ - (BOOL)verifyItemIsNotMissingAtURL:(NSURL *)url } // Look up in the all items store to see if we have a UUID - NSString *uuid = [self.allItems uuidForItemWithURL:url]; + NSString * const uuid = [self.allItems uuidForItemWithURL:url]; if (uuid == nil) { return NO; } - + // Save a reference to this file in case it turns up later in this operation self.missingItems[uuid] = url; - + return NO; } -- (BOOL)verifyIfItemWasMovedOrDeletedWithURL:(NSURL *)url uuid:(NSString *)uuid -{ - NSURL *savedURL = self.allItems[uuid]; +- (BOOL)_verifyIfItemWasMovedOrDeletedWithURL:(NSURL *)url uuid:(NSString *)uuid { + NSURL * const savedURL = self.allItems[uuid]; if (savedURL == nil) { return YES; } - + // If the URLs match, the item hasn't been moved if ([savedURL isEqual:url]) { return YES; } - + // Check that the saved URL still has a file there, and the UUID of that file matches this one, // (in case the user potentially deleted the file, and replaced it with one with the same name) - NSString *savedUUID = [savedURL to_fileSystemUUID]; - BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:savedURL.path]; + NSString * const savedUUID = [savedURL to_fileSystemUUID]; + const BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:savedURL.path]; if (fileExists && [savedUUID isEqualToString:uuid]) { return YES; } - + // If the file still exists, but it was moved to the Trashes folder, this means // the user deleted it via the Files app. Instead of moving the file, override // and treat it like it was deleted. - BOOL movedToTrashes = ([url.path rangeOfString:kTOFileSystemTrashFolderName].location != NSNotFound); - + const BOOL movedToTrashes = ([url.path rangeOfString:kTOFileSystemTrashFolderName].location != NSNotFound); + // Conversely, if it was moved to a level below what we had limited, also consider // this as deleting the file - NSInteger numberOfSublevels = [self numberOfDirectoryLevelsToURL:url]; - BOOL movedBeyondLevelLimit = (self.subDirectoryLevelLimit > 0 && numberOfSublevels > self.subDirectoryLevelLimit); + const NSInteger numberOfSublevels = [self _numberOfDirectoryLevelsToURL:url]; + const BOOL movedBeyondLevelLimit = (self.subDirectoryLevelLimit > 0 && numberOfSublevels > self.subDirectoryLevelLimit); if (movedToTrashes || movedBeyondLevelLimit) { [self.allItems removeItemURLForUUID:uuid]; @@ -349,20 +338,19 @@ - (BOOL)verifyIfItemWasMovedOrDeletedWithURL:(NSURL *)url uuid:(NSString *)uuid return YES; } -- (void)verifyItemAtURL:(NSURL *)url uuid:(NSString *)uuid -{ - NSURL *savedURL = self.allItems[uuid]; - +- (void)_verifyItemAtURL:(NSURL *)url uuid:(NSString *)uuid { + NSURL * const savedURL = self.allItems[uuid]; + // There's an extremely specific edge case here. // If a user suspends the app, deletes an item, and then imports // a new item with the same name, we can import the new item easily, // but there's no easy way to work out which file entry was deleted // (Because the reference to the UUID of the first file is lost). - + // To remedy this, use an inverse dictionary to access any previous UUID // values stored against this current URL, and if they don't match, // delete the previous entry - NSString *savedUUID = [self.allItems uuidForItemWithURL:url]; + NSString * const savedUUID = [self.allItems uuidForItemWithURL:url]; if (savedUUID && ![savedUUID isEqualToString:uuid]) { [self.allItems removeItemURLForUUID:savedUUID]; [self.delegate scanOperation:self didDeleteItemAtURL:url withUUID:savedUUID]; @@ -382,8 +370,7 @@ - (void)verifyItemAtURL:(NSURL *)url uuid:(NSString *)uuid [self.delegate scanOperation:self itemDidChangeAtURL:url withUUID:uuid]; } -- (void)cleanUpFilesPendingDeletion -{ +- (void)_cleanUpFilesPendingDeletion { if (self.missingItems.count == 0) { return; } // Loop through each missing item entry @@ -398,10 +385,9 @@ - (void)cleanUpFilesPendingDeletion #pragma mark - State Tracking - -- (NSString *)uniqueUUIDForItemAtURL:(NSURL *)url withUUID:(NSString *)uuid -{ +- (NSString *)_uniqueUUIDForItemAtURL:(NSURL *)url withUUID:(NSString *)uuid { // Check if we already stored an item with that same UUID - NSURL *savedURL = self.allItems[uuid]; + NSURL * const savedURL = self.allItems[uuid]; if (savedURL == nil) { return uuid; } // Check if the URLs match @@ -425,10 +411,9 @@ - (NSString *)uniqueUUIDForItemAtURL:(NSURL *)url withUUID:(NSString *)uuid return newUUID; } -- (NSInteger)numberOfDirectoryLevelsToURL:(NSURL *)url -{ +- (NSInteger)_numberOfDirectoryLevelsToURL:(NSURL *)url { NSInteger levels = 0; - NSURL *directoryURL = self.directoryURL.URLByStandardizingPath; + NSURL * const directoryURL = self.directoryURL.URLByStandardizingPath; // Loop up from the URL to the base // directory to see how many levels deep it is. diff --git a/TOFileSystemObserver/TOFileSystemObserver.h b/TOFileSystemObserver/TOFileSystemObserver.h index c9efe4c..9ff0679 100644 --- a/TOFileSystemObserver/TOFileSystemObserver.h +++ b/TOFileSystemObserver/TOFileSystemObserver.h @@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN dispatch to the main queue yourself. */ NS_SWIFT_NAME(FileSystemObserver) +__attribute__((objc_subclassing_restricted)) @interface TOFileSystemObserver : NSObject /** Whether the observer is currently active and observing its target directory. */ @@ -80,7 +81,7 @@ NS_SWIFT_NAME(FileSystemObserver) @property (nonatomic, assign) BOOL broadcastsNotifications; /** Create a new instance of the observer with the base URL that will be observed. */ -- (instancetype)initWithDirectoryURL:(NSURL *)directoryURL; +- (instancetype)initWithDirectoryURL:(NSURL *)directoryURL NS_DESIGNATED_INITIALIZER; /** A singleton instance that can be accessed globally. It is created the first time this is called. */ + (instancetype)sharedObserver; diff --git a/TOFileSystemObserver/TOFileSystemObserver.m b/TOFileSystemObserver/TOFileSystemObserver.m index 48173e4..60f4f21 100644 --- a/TOFileSystemObserver/TOFileSystemObserver.m +++ b/TOFileSystemObserver/TOFileSystemObserver.m @@ -94,7 +94,7 @@ @interface TOFileSystemObserver() *notificationTokens; @end @@ -102,28 +102,21 @@ @implementation TOFileSystemObserver #pragma mark - Object Lifecycle - -- (instancetype)init -{ - if (self = [super init]) { - _directoryURL = [TOFileSystemPath documentsDirectoryURL].URLByStandardizingPath; - [self setUp]; - } - - return self; +- (instancetype)init { + return [self initWithDirectoryURL:[TOFileSystemPath documentsDirectoryURL].URLByStandardizingPath]; } -- (instancetype)initWithDirectoryURL:(NSURL *)directoryURL -{ +- (instancetype)initWithDirectoryURL:(NSURL *)directoryURL { + NSParameterAssert(directoryURL != nil); if (self = [super init]) { _directoryURL = directoryURL; - [self setUp]; + [self _setUp]; } return self; } -+ (instancetype)sharedObserver -{ ++ (instancetype)sharedObserver { if (_sharedObserver) { return _sharedObserver; } static dispatch_once_t onceToken; @@ -135,8 +128,7 @@ + (instancetype)sharedObserver return _sharedObserver; } -+ (void)setSharedObserver:(TOFileSystemObserver *)observer -{ ++ (void)setSharedObserver:(TOFileSystemObserver *)observer { if (observer == _sharedObserver) { return; } if (_sharedObserver.isRunning) { [_sharedObserver stop]; @@ -145,8 +137,7 @@ + (void)setSharedObserver:(TOFileSystemObserver *)observer _sharedObserver = observer; } -- (void)setUp -{ +- (void)_setUp { // Set-up default property values _isRunning = NO; _excludedItems = @[@"Inbox"]; @@ -169,36 +160,35 @@ - (void)setUp _copyingItems = [[TOFileSystemItemURLDictionary alloc] initWithBaseURL:self.directoryURL]; // Change the UUID key name to match our app (for better visibility) - NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; + NSString * const bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; [NSURL to_setKeyNamePrefix:bundleIdentifier]; } #pragma mark - Observer Setup - -- (void)configureFilePresenter -{ +- (void)_configureFilePresenter { // Attach the root directory to the observer - NSURL *url = self.directoryURL; + NSURL * const url = self.directoryURL; self.fileSystemPresenter.directoryURL = url; - + // Set up the callback handler for when changes are detected __weak typeof(self) weakSelf = self; self.fileSystemPresenter.itemsDidChangeHandler = ^(NSArray *itemURLs) { - [weakSelf updateObservingObjectsWithChangedItemURLs:itemURLs]; + __strong typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf == nil) { return; } + [strongSelf _updateObservingObjectsWithChangedItemURLs:itemURLs]; }; } -- (void)beginObservingBaseDirectory -{ +- (void)_beginObservingBaseDirectory { // Configure the file presenter and start - [self configureFilePresenter]; + [self _configureFilePresenter]; [self.fileSystemPresenter start]; } #pragma mark - Observer Lifecycle - -- (void)start -{ +- (void)start { if (self.isRunning) { return; } // Set the running state @@ -209,14 +199,13 @@ - (void)start _baseDirectoryUUID = self.directoryItem.uuid; // Start the observer to watch for any system level changes - [self beginObservingBaseDirectory]; + [self _beginObservingBaseDirectory]; // Perform an initial scan of all of the files we will observe - [self performFullDirectoryScan]; + [self _performFullDirectoryScan]; } -- (void)stop -{ +- (void)stop { if (!self.isRunning) { return; } // Set the running state to off @@ -229,30 +218,28 @@ - (void)stop [self.fileSystemPresenter stop]; } -- (void)performFullDirectoryScan -{ +- (void)_performFullDirectoryScan { // Create a new scan operation - TOFileSystemScanOperation *scanOperation = nil; - scanOperation = [[TOFileSystemScanOperation alloc] initForFullScanWithDirectoryAtURL:self.directoryURL - skippingItems:self.excludedItems - allItemsDictionary:self.allItems - filePresenter:self.fileSystemPresenter]; + TOFileSystemScanOperation * const scanOperation = + [[TOFileSystemScanOperation alloc] initForFullScanWithDirectoryAtURL:self.directoryURL + skippingItems:self.excludedItems + allItemsDictionary:self.allItems + filePresenter:self.fileSystemPresenter]; scanOperation.subDirectoryLevelLimit = self.includedDirectoryLevels; scanOperation.delegate = self; - + // Begin asynchronous execution [self.operationQueue addOperation:scanOperation]; } -- (void)updateObservingObjectsWithChangedItemURLs:(NSArray *)itemURLs -{ +- (void)_updateObservingObjectsWithChangedItemURLs:(NSArray *)itemURLs { // Create a new scan operation to analyse what changed - TOFileSystemScanOperation *scanOperation = nil; - scanOperation = [[TOFileSystemScanOperation alloc] initForItemScanWithItemURLs:itemURLs - baseURL:self.directoryURL - skippingItems:self.excludedItems - allItemsDictionary:self.allItems - filePresenter:self.fileSystemPresenter]; + TOFileSystemScanOperation * const scanOperation = + [[TOFileSystemScanOperation alloc] initForItemScanWithItemURLs:itemURLs + baseURL:self.directoryURL + skippingItems:self.excludedItems + allItemsDictionary:self.allItems + filePresenter:self.fileSystemPresenter]; scanOperation.subDirectoryLevelLimit = self.includedDirectoryLevels; scanOperation.delegate = self; @@ -260,9 +247,9 @@ - (void)updateObservingObjectsWithChangedItemURLs:(NSArray *)itemURLs [self.operationQueue addOperation:scanOperation]; } -- (TOFileSystemNotificationToken *)addNotificationBlock:(TOFileSystemNotificationBlock)block -{ - TOFileSystemNotificationToken *token = [TOFileSystemNotificationToken tokenWithObservingObject:self block:block]; +- (TOFileSystemNotificationToken *)addNotificationBlock:(TOFileSystemNotificationBlock)block { + NSParameterAssert(block != nil); + TOFileSystemNotificationToken * const token = [TOFileSystemNotificationToken tokenWithObservingObject:self block:block]; if (self.notificationTokens == nil) { self.notificationTokens = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; } @@ -271,15 +258,15 @@ - (TOFileSystemNotificationToken *)addNotificationBlock:(TOFileSystemNotificatio } /** Removes the notification from the observing object. */ -- (void)removeNotificationToken:(TOFileSystemNotificationToken *)token -{ +- (void)removeNotificationToken:(TOFileSystemNotificationToken *)token { + NSParameterAssert(token != nil); [self.notificationTokens removeObject:token]; } #pragma mark - Creating and Observing Items - -- (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL -{ +- (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL { + NSParameterAssert(itemURL != nil); // See if we already have a UUID entry for this file in the global store __block NSString *uuid = nil; uuid = [self.allItems uuidForItemWithURL:itemURL]; @@ -296,13 +283,11 @@ - (nullable NSString *)uuidForItemAtURL:(NSURL *)itemURL return [self.fileSystemPresenter uuidForItemAtURL:itemURL]; } -- (nullable NSString *)uuidForParentOfItemAtURL:(NSURL *)itemURL -{ +- (nullable NSString *)uuidForParentOfItemAtURL:(NSURL *)itemURL { return [self uuidForItemAtURL:itemURL.URLByDeletingLastPathComponent]; } -- (TOFileSystemItemList *)itemListForDirectoryAtURL:(NSURL *)directoryURL -{ +- (TOFileSystemItemList *)itemListForDirectoryAtURL:(NSURL *)directoryURL { // Default to the base directory if nil is supplied if (directoryURL == nil) { directoryURL = self.directoryURL; @@ -314,7 +299,7 @@ - (TOFileSystemItemList *)itemListForDirectoryAtURL:(NSURL *)directoryURL @autoreleasepool { // Fetch the UUID for this item and see if we've cached it already NSString *uuid = [self uuidForItemAtURL:directoryURL]; - uuid = [self verifiedUniqueUUIDForItemAtURL:directoryURL uuid:uuid]; + uuid = [self _verifiedUniqueUUIDForItemAtURL:directoryURL uuid:uuid]; itemList = self.itemListTable[uuid]; if (itemList) { return; } @@ -337,13 +322,12 @@ - (TOFileSystemItemList *)itemListForDirectoryAtURL:(NSURL *)directoryURL return itemList; } -- (TOFileSystemItem *)directoryItem -{ +- (TOFileSystemItem *)directoryItem { return [self itemForFileAtURL:self.directoryURL]; } -- (TOFileSystemItem *)itemForFileAtURL:(NSURL *)fileURL -{ +- (TOFileSystemItem *)itemForFileAtURL:(NSURL *)fileURL { + NSParameterAssert(fileURL != nil); // Exit out if the URL is invalid if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) { return nil; @@ -355,7 +339,7 @@ - (TOFileSystemItem *)itemForFileAtURL:(NSURL *)fileURL @autoreleasepool { // Fetch the UUID for this item and see if we've cached it already NSString *uuid = [self uuidForItemAtURL:fileURL]; - uuid = [self verifiedUniqueUUIDForItemAtURL:fileURL uuid:uuid]; + uuid = [self _verifiedUniqueUUIDForItemAtURL:fileURL uuid:uuid]; item = self.itemTable[uuid]; if (item) { return; } @@ -379,13 +363,12 @@ - (TOFileSystemItem *)itemForFileAtURL:(NSURL *)fileURL return item; } -- (NSString *)verifiedUniqueUUIDForItemAtURL:(NSURL *)itemURL uuid:(NSString *)uuid -{ +- (NSString *)_verifiedUniqueUUIDForItemAtURL:(NSURL *)itemURL uuid:(NSString *)uuid { // If it was detected that there are two items with the same UUID // in the master list, regenerate the UUID for this one - + // If this item isn't in the master list yet, then there is no chance for conflicts - NSURL *url = self.allItems[uuid]; + NSURL * const url = self.allItems[uuid]; if (url == nil) { return uuid; } // If an item does exist, check it is at the same location @@ -412,40 +395,37 @@ - (NSString *)verifiedUniqueUUIDForItemAtURL:(NSURL *)itemURL uuid:(NSString *)u #pragma mark - Item Refreshing - -- (BOOL)refreshItemAtURL:(NSURL *)itemURL - uuid:(NSString *)uuid -{ +- (BOOL)_refreshItemAtURL:(NSURL *)itemURL + uuid:(NSString *)uuid { // Perform an update on the item and see if we need to trigger // any visual updates - BOOL hasChanges = [self.itemTable[uuid] refreshWithURL:itemURL]; - + const BOOL hasChanges = [self.itemTable[uuid] refreshWithURL:itemURL]; + // If the item is also in memory as a list, update its list entry too [self.itemListTable[uuid] refreshWithURL:itemURL]; - + return hasChanges; } -- (BOOL)refreshParentItemWithUUID:(NSString *)uuid -{ +- (BOOL)_refreshParentItemWithUUID:(NSString *)uuid { // If the parent item is an item, do a check on it to see if it has changes - BOOL hasChanges = [self.itemTable[uuid] refreshWithURL:nil]; - + const BOOL hasChanges = [self.itemTable[uuid] refreshWithURL:nil]; + // If the parent item has a list entry, perform an update on that too [self.itemListTable[uuid] refreshWithURL:nil]; - + return hasChanges; } -- (void)startTimerForCopyingItems -{ - id block = ^{ +- (void)_startTimerForCopyingItems { + id const block = ^{ // The timer is already counting down if (self.copyingTimer) { return; } // Create a new timer self.copyingTimer = [NSTimer timerWithTimeInterval:kTOFileSystemObserverCopyingTimeDelay target:self - selector:@selector(copyTimerCompleted) + selector:@selector(_copyTimerCompleted) userInfo:nil repeats:NO]; @@ -460,25 +440,24 @@ - (void)startTimerForCopyingItems [[NSOperationQueue mainQueue] addOperationWithBlock:block]; } -- (void)copyTimerCompleted -{ +- (void)_copyTimerCompleted { // Remove the timer self.copyingTimer = nil; - - id block = ^{ + + id const block = ^{ // Get all of the URLs still pending in the dictionary - NSArray *urls = self.copyingItems.allURLs; + NSArray * const urls = self.copyingItems.allURLs; if (urls == nil) { return; } - + // Remove all the items we're about to test from the list, // as they'll be re-added on the subsequent callback if they're // still copying [self.copyingItems removeAllItems]; - + // Perform a scan to see if they've changed - [self updateObservingObjectsWithChangedItemURLs:urls]; + [self _updateObservingObjectsWithChangedItemURLs:urls]; }; - + [self.operationQueue addOperationWithBlock:block]; } @@ -486,24 +465,23 @@ - (void)copyTimerCompleted - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation didDiscoverItemAtURL:(NSURL *)itemURL - withUUID:(NSString *)uuid -{ + withUUID:(NSString *)uuid { // Get the UUID of the parent so we can see if there is a list for it - NSString *parentUUID = [self uuidForParentOfItemAtURL:itemURL]; - + NSString * const parentUUID = [self uuidForParentOfItemAtURL:itemURL]; + // Refresh all of the properties of this item and its parent - [self refreshItemAtURL:itemURL uuid:uuid]; - [self refreshParentItemWithUUID:parentUUID]; - + [self _refreshItemAtURL:itemURL uuid:uuid]; + [self _refreshParentItemWithUUID:parentUUID]; + // Broadcast this event to all of the observers. - TOFileSystemChanges *changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self]; + TOFileSystemChanges * const changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self]; if (scanOperation.isFullScan) { [changes setIsFullScan]; } [changes addDiscoveredItemWithUUID:uuid fileURL:itemURL]; - [self postNotificationsWithChanges:changes]; - - id mainBlock = ^{ + [self _postNotificationsWithChanges:changes]; + + id const mainBlock = ^{ // If this is a new item that belongs to an existing list, append it - TOFileSystemItemList *parentList = self.itemListTable[parentUUID]; + TOFileSystemItemList * const parentList = self.itemListTable[parentUUID]; if (parentList) { [parentList addItemWithUUID:uuid itemURL:itemURL]; } }; [[NSOperationQueue mainQueue] addOperationWithBlock:mainBlock]; @@ -511,62 +489,60 @@ - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation itemDidChangeAtURL:(NSURL *)itemURL - withUUID:(NSString *)uuid -{ + withUUID:(NSString *)uuid { // If the item is still copying at this point (Potentially lag during the write?) // add it to our copying list so we can poll it again in a few seconds if (itemURL.to_isCopying) { [self.copyingItems setItemURL:itemURL forUUID:uuid]; - [self startTimerForCopyingItems]; + [self _startTimerForCopyingItems]; } - + // See if there is a list had been made for the parent, and add it - NSString *parentUUID = [self uuidForParentOfItemAtURL:itemURL]; - [self refreshItemAtURL:itemURL uuid:uuid]; - [self refreshParentItemWithUUID:parentUUID]; - + NSString * const parentUUID = [self uuidForParentOfItemAtURL:itemURL]; + [self _refreshItemAtURL:itemURL uuid:uuid]; + [self _refreshParentItemWithUUID:parentUUID]; + // Broadcast this event to all of the observers. - TOFileSystemChanges *changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self]; + TOFileSystemChanges * const changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self]; if (scanOperation.isFullScan) { [changes setIsFullScan]; } [changes addModifiedItemWithUUID:uuid fileURL:itemURL]; - [self postNotificationsWithChanges:changes]; + [self _postNotificationsWithChanges:changes]; } - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation itemWithUUID:(NSString *)uuid didMoveFromURL:(NSURL *)previousURL - toURL:(NSURL *)url -{ + toURL:(NSURL *)url { // If the movement occurred inside the same folder (eg, it was renamed), // cancel out here. - NSURL *oldParentURL = previousURL.URLByDeletingLastPathComponent.URLByStandardizingPath; - NSURL *newParentURL = url.URLByDeletingLastPathComponent.URLByStandardizingPath; + NSURL * const oldParentURL = previousURL.URLByDeletingLastPathComponent.URLByStandardizingPath; + NSURL * const newParentURL = url.URLByDeletingLastPathComponent.URLByStandardizingPath; if ([oldParentURL isEqual:newParentURL]) { return; } - + // See if moved from, or into a new list - NSString *oldParentUUID = [oldParentURL to_fileSystemUUID]; - NSString *newParentUUID = [newParentURL to_fileSystemUUID]; - + NSString * const oldParentUUID = [oldParentURL to_fileSystemUUID]; + NSString * const newParentUUID = [newParentURL to_fileSystemUUID]; + // Get the item and refresh its internal state with the new location - [self refreshItemAtURL:url uuid:uuid]; - + [self _refreshItemAtURL:url uuid:uuid]; + // Refresh both of the parents to update the children counts in each - [self refreshParentItemWithUUID:oldParentUUID]; - [self refreshParentItemWithUUID:newParentUUID]; + [self _refreshParentItemWithUUID:oldParentUUID]; + [self _refreshParentItemWithUUID:newParentUUID]; // Broadcast this event to all of the observers. - TOFileSystemChanges *changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self]; + TOFileSystemChanges * const changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self]; if (scanOperation.isFullScan) { [changes setIsFullScan]; } [changes addMovedItemWithUUID:uuid oldFileURL:previousURL newFileURL:url]; - [self postNotificationsWithChanges:changes]; - - id mainBlock = ^{ + [self _postNotificationsWithChanges:changes]; + + id const mainBlock = ^{ // If the item used to be in a list item, remove it from that list - TOFileSystemItemList *oldList = self.itemListTable[oldParentUUID]; + TOFileSystemItemList * const oldList = self.itemListTable[oldParentUUID]; [oldList removeItemWithUUID:uuid fileURL:url]; - + // If the destination also had a list, append it to that list - TOFileSystemItemList *newList = self.itemListTable[newParentUUID]; + TOFileSystemItemList * const newList = self.itemListTable[newParentUUID]; [newList addItemWithUUID:uuid itemURL:url]; }; [[NSOperationQueue mainQueue] addOperationWithBlock:mainBlock]; @@ -574,42 +550,40 @@ - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation didDeleteItemAtURL:(NSURL *)itemURL - withUUID:(NSString *)uuid -{ - NSString *parentUUID = [self uuidForParentOfItemAtURL:itemURL]; - + withUUID:(NSString *)uuid { + NSString * const parentUUID = [self uuidForParentOfItemAtURL:itemURL]; + // Broadcast this event to all of the observers. - TOFileSystemChanges *changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self]; + TOFileSystemChanges * const changes = [[TOFileSystemChanges alloc] initWithFileSystemObserver:self]; if (scanOperation.isFullScan) { [changes setIsFullScan]; } [changes addDeletedItemWithUUID:uuid fileURL:itemURL]; - [self postNotificationsWithChanges:changes]; - - id mainBlock = ^{ + [self _postNotificationsWithChanges:changes]; + + id const mainBlock = ^{ // If we have this item in memory, remove it from everywhere - TOFileSystemItem *item = self.itemTable[uuid]; + TOFileSystemItem * const item = self.itemTable[uuid]; [item.list removeItemWithUUID:uuid fileURL:itemURL]; [self.itemTable removeItemForUUID:uuid]; [self.itemListTable removeItemForUUID:uuid]; - + // If this item is a child of a list, update that list - TOFileSystemItem *listItem = self.itemTable[parentUUID]; + TOFileSystemItem * const listItem = self.itemTable[parentUUID]; [listItem refreshWithURL:nil]; }; [[NSOperationQueue mainQueue] addOperationWithBlock:mainBlock]; } -- (void)scanOperationWillBeginFullScan:(TOFileSystemScanOperation *)scanOperation -{ +- (void)scanOperationWillBeginFullScan:(TOFileSystemScanOperation *)scanOperation { // Perform the Notification Center broadcast if (self.broadcastsNotifications) { - NSDictionary *userInfo = [self userInfoDictionaryWithChanges:nil]; + NSDictionary * const userInfo = [self _userInfoDictionaryWithChanges:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:TOFileSystemObserverWillBeginFullScanNotification object:nil userInfo:userInfo]; } - + // Inform all notification tokens registered - for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { + for (TOFileSystemNotificationToken * const token in self.notificationTokens.allObjects) { TOFileSystemObserverCallBlock(token.notificationBlock, self, TOFileSystemObserverNotificationTypeWillBeginFullScan, @@ -617,23 +591,22 @@ - (void)scanOperationWillBeginFullScan:(TOFileSystemScanOperation *)scanOperatio } } -- (void)scanOperationDidCompleteFullScan:(TOFileSystemScanOperation *)scanOperation -{ +- (void)scanOperationDidCompleteFullScan:(TOFileSystemScanOperation *)scanOperation { // Loop through the list one more time to remove any headless entries - for (TOFileSystemItemList *list in self.itemListTable.allItems) { + for (TOFileSystemItemList * const list in self.itemListTable.allItems) { [list synchronizeWithDisk]; } - + // Perform the Notification Center broadcast if (self.broadcastsNotifications) { - NSDictionary *userInfo = [self userInfoDictionaryWithChanges:nil]; + NSDictionary * const userInfo = [self _userInfoDictionaryWithChanges:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:TOFileSystemObserverDidCompleteFullScanNotification object:nil userInfo:userInfo]; } - + // Inform all notification tokens registered - for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { + for (TOFileSystemNotificationToken * const token in self.notificationTokens.allObjects) { TOFileSystemObserverCallBlock(token.notificationBlock, self, TOFileSystemObserverNotificationTypeDidCompleteFullScan, @@ -643,8 +616,7 @@ - (void)scanOperationDidCompleteFullScan:(TOFileSystemScanOperation *)scanOperat #pragma mark - Notifications - -- (NSDictionary *)userInfoDictionaryWithChanges:(TOFileSystemChanges *)changes -{ +- (NSDictionary *)_userInfoDictionaryWithChanges:(TOFileSystemChanges *)changes { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; dictionary[TOFileSystemObserverUserInfoKey] = self; if (changes) { @@ -654,22 +626,21 @@ - (NSDictionary *)userInfoDictionaryWithChanges:(TOFileSystemChanges *)changes return [NSDictionary dictionaryWithDictionary:dictionary]; } -- (void)postNotificationsWithChanges:(TOFileSystemChanges *)changes -{ +- (void)_postNotificationsWithChanges:(TOFileSystemChanges *)changes { if (!self.broadcastsNotifications && self.notificationTokens.count == 0) { return; } - + // Perform the Notification Center broadcast if (self.broadcastsNotifications) { - NSDictionary *userInfo = [self userInfoDictionaryWithChanges:changes]; + NSDictionary * const userInfo = [self _userInfoDictionaryWithChanges:changes]; [[NSNotificationCenter defaultCenter] postNotificationName:TOFileSystemObserverDidChangeNotification object:nil userInfo:userInfo]; } - + // Inform all notification tokens registered - for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { + for (TOFileSystemNotificationToken * const token in self.notificationTokens.allObjects) { TOFileSystemObserverCallBlock(token.notificationBlock, self, TOFileSystemObserverNotificationTypeDidChange, diff --git a/TOFileSystemObserverTests/TOFileSystemObserverIntegrationTests.m b/TOFileSystemObserverTests/TOFileSystemObserverIntegrationTests.m index 318b366..0d14051 100644 --- a/TOFileSystemObserverTests/TOFileSystemObserverIntegrationTests.m +++ b/TOFileSystemObserverTests/TOFileSystemObserverIntegrationTests.m @@ -43,7 +43,7 @@ - (void)scanOperation:(TOFileSystemScanOperation *)scanOperation @end @interface TOFileSystemScanOperation (TestingHook) -- (NSInteger)numberOfDirectoryLevelsToURL:(NSURL *)url; +- (NSInteger)_numberOfDirectoryLevelsToURL:(NSURL *)url; @end @interface TOFileSystemItemList (TestingHook) @@ -623,9 +623,9 @@ - (void)testScanOperationNumberOfDirectoryLevelsCountsDepth NSURL *twoDeep = [[[self.tempDirectory URLByAppendingPathComponent:@"a"] URLByAppendingPathComponent:@"b"] URLByAppendingPathComponent:@"foo.dat"]; - XCTAssertEqual([scan numberOfDirectoryLevelsToURL:direct], 0); - XCTAssertEqual([scan numberOfDirectoryLevelsToURL:oneDeep], 1); - XCTAssertEqual([scan numberOfDirectoryLevelsToURL:twoDeep], 2); + XCTAssertEqual([scan _numberOfDirectoryLevelsToURL:direct], 0); + XCTAssertEqual([scan _numberOfDirectoryLevelsToURL:oneDeep], 1); + XCTAssertEqual([scan _numberOfDirectoryLevelsToURL:twoDeep], 2); } - (void)testStopThenStartAgainPerformsAnotherFullScan