diff --git a/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m b/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m index 5430f9b..28ea91c 100644 --- a/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m +++ b/TOFileSystemObserver/Categories/NSURL+TOFileSystemAttributes.m @@ -30,7 +30,9 @@ - (BOOL)to_isCopying { // When files are still being copied, their // modification date is equal to the current device time. - return [self.to_modificationDate timeIntervalSinceDate:[NSDate date]] + NSDate *modificationDate = self.to_modificationDate; + if (modificationDate == nil) { return NO; } + return [modificationDate timeIntervalSinceDate:[NSDate date]] > (-kTOFileSystemObserverCopyingTimeDelay - FLT_EPSILON); } diff --git a/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.h b/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.h index 8c2f53f..17372e7 100644 --- a/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.h +++ b/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.h @@ -40,11 +40,11 @@ NS_ASSUME_NONNULL_BEGIN /** Returns the unique UUID value assigned to this file. */ - (nullable NSString *)to_fileSystemUUID; -/** Sets a predetermined UUID to be the value of the file. */ -- (void)to_setFileSystemUUID:(NSString *)uuid; +/** Sets a predetermined UUID to be the value of the file. Returns YES if the attribute was written. */ +- (BOOL)to_setFileSystemUUID:(NSString *)uuid; -/** Regardless if one exists, generate and save a new UUID. */ -- (NSString *)to_generateFileSystemUUID; +/** Regardless if one exists, generate and save a new UUID. Returns nil if the write failed. */ +- (nullable NSString *)to_generateFileSystemUUID; @end diff --git a/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m b/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m index d793d6e..66a7f14 100644 --- a/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m +++ b/TOFileSystemObserver/Categories/NSURL+TOFileSystemUUID.m @@ -40,11 +40,14 @@ - (NSString *)to_fileSystemUUID // Allocate a buffer for the value (UUID values are always 36 characters) char value[36]; - // Fetch the value from disk - getxattr(filePath, keyName, value, 36, 0, 0); + // 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); + if (bytesRead != (ssize_t)sizeof(value)) { + return nil; + } // Convert to a string, and return if successful - NSString *uuid = [[NSString alloc] initWithBytes:value length:36 encoding:NSUTF8StringEncoding]; + NSString *uuid = [[NSString alloc] initWithBytes:value length:bytesRead encoding:NSUTF8StringEncoding]; if (uuid.length == 0) { return nil; } @@ -64,9 +67,10 @@ - (NSString *)to_fileSystemUUID return uuid; } -- (void)to_setFileSystemUUID:(NSString *)uuid +- (BOOL)to_setFileSystemUUID:(NSString *)uuid { - if (uuid.length > 0 && uuid.length != 36) { + if (uuid.length == 0) { return NO; } + if (uuid.length != 36) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"UUID must be 36 characters long!" userInfo:nil]; @@ -78,15 +82,16 @@ - (void)to_setFileSystemUUID:(NSString *)uuid // Convert the string to a C byte string const char *uuidString = [uuid cStringUsingEncoding:NSUTF8StringEncoding]; + if (uuidString == NULL) { return NO; } - // Save it to this file - setxattr(filePath, keyName, uuidString, strlen(uuidString), 0, 0); + // Save it to this file. UUID strings are always 36 ASCII bytes. + return setxattr(filePath, keyName, uuidString, 36, 0, 0) == 0; } - (NSString *)to_generateFileSystemUUID { NSString *uuid = [NSUUID UUID].UUIDString; - [self to_setFileSystemUUID:uuid]; + if (![self to_setFileSystemUUID:uuid]) { return nil; } return uuid; } diff --git a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h index 5a07f75..777aba7 100644 --- a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h +++ b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.h @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN used to store re-usable instances of item and list objects. */ -@interface TOFileSystemItemMapTable : NSObject +@interface TOFileSystemItemMapTable : NSObject @property (nonatomic, readonly) NSInteger count; @@ -37,6 +37,9 @@ NS_ASSUME_NONNULL_BEGIN - (id)itemForUUID:(NSString *)uuid; - (void)removeItemForUUID:(NSString *)uuid; +/** A point-in-time snapshot of all current items. Safe to iterate while the table is being mutated. */ +- (NSArray *)allItems; + /** Implementations for allowing dictionary style literal syntax. */ - (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key; - (nullable id)objectForKeyedSubscript:(NSString *)key; diff --git a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m index 8e452ef..030df51 100644 --- a/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m +++ b/TOFileSystemObserver/Entities/Collections/TOFileSystemItemMapTable.m @@ -81,6 +81,17 @@ - (void)removeItemForUUID:(NSString *)uuid }); } +- (NSArray *)allItems +{ + __block NSArray *items = nil; + dispatch_sync(self.dispatchQueue, ^{ + @autoreleasepool { + items = self.mapTable.objectEnumerator.allObjects; + } + }); + return items ?: @[]; +} + - (void)setObject:(nullable id)object forKeyedSubscript:(nonnull NSString *)key { [self setItem:object forUUID:key]; @@ -91,13 +102,4 @@ - (nullable id)objectForKeyedSubscript:(NSString *)key return [self itemForUUID:key]; } -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state - objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer - count:(NSUInteger)len -{ - return [_mapTable countByEnumeratingWithState:state - objects:buffer - count:len]; -} - @end diff --git a/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.m b/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.m index 6d83cdd..9586f57 100644 --- a/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.m +++ b/TOFileSystemObserver/Entities/Items/TOFileSystemItemList.m @@ -131,7 +131,7 @@ - (void)rebuildItemListForListingOrder } // Trigger the notification blocks to update any UI with this new order - for (TOFileSystemNotificationToken *token in self.notificationTokens) { + for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } } @@ -242,7 +242,7 @@ - (void)addItemWithUUID:(NSString *)uuid itemURL:(NSURL *)url // Perform the broadcast to any observing objects that this update ocurred TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init]; [changes addInsertionIndex:sortedIndex]; - for (TOFileSystemNotificationToken *token in self.notificationTokens) { + for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } } @@ -267,7 +267,7 @@ - (void)removeItemWithUUID:(NSString *)uuid fileURL:(NSURL *)url TOFileSystemItemListChanges *changes = [[TOFileSystemItemListChanges alloc] init]; [changes addDeletionIndex:index]; - for (TOFileSystemNotificationToken *token in self.notificationTokens) { + for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } } @@ -300,7 +300,7 @@ - (void)itemDidRefreshWithUUID:(NSString *)uuid [changes addModificationIndex:newIndex]; // Broadcast the changes - for (TOFileSystemNotificationToken *token in self.notificationTokens) { + for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } } @@ -322,8 +322,9 @@ - (void)synchronizeWithDisk // Skip if every file was accounted for if (changes.deletions.count == 0) { return; } - // Remove all of the deleted files from the list - for (NSNumber *deletedIndex in changes.deletions) { + // 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]; [self.sortedItems removeObjectAtIndex:deletedIndex.intValue]; [self.items removeObjectForKey:uuid]; @@ -331,7 +332,7 @@ - (void)synchronizeWithDisk // Broadcast the changes dispatch_async(dispatch_get_main_queue(), ^{ - for (TOFileSystemNotificationToken *token in self.notificationTokens) { + for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { TOFileSystemItemListCallBlock(token.notificationBlock, self, changes); } }); diff --git a/TOFileSystemObserver/TOFileSystemObserver.m b/TOFileSystemObserver/TOFileSystemObserver.m index 3ffa3ac..e78881e 100644 --- a/TOFileSystemObserver/TOFileSystemObserver.m +++ b/TOFileSystemObserver/TOFileSystemObserver.m @@ -607,7 +607,7 @@ - (void)scanOperationWillBeginFullScan:(TOFileSystemScanOperation *)scanOperatio } // Inform all notification tokens registered - for (TOFileSystemNotificationToken *token in self.notificationTokens) { + for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { TOFileSystemObserverCallBlock(token.notificationBlock, self, TOFileSystemObserverNotificationTypeWillBeginFullScan, @@ -618,8 +618,8 @@ - (void)scanOperationWillBeginFullScan:(TOFileSystemScanOperation *)scanOperatio - (void)scanOperationDidCompleteFullScan:(TOFileSystemScanOperation *)scanOperation { // Loop through the list one more time to remove any headless entries - for (NSString *listUUID in self.itemListTable) { - [self.itemListTable[listUUID] synchronizeWithDisk]; + for (TOFileSystemItemList *list in self.itemListTable.allItems) { + [list synchronizeWithDisk]; } // Perform the Notification Center broadcast @@ -631,7 +631,7 @@ - (void)scanOperationDidCompleteFullScan:(TOFileSystemScanOperation *)scanOperat } // Inform all notification tokens registered - for (TOFileSystemNotificationToken *token in self.notificationTokens) { + for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { TOFileSystemObserverCallBlock(token.notificationBlock, self, TOFileSystemObserverNotificationTypeDidCompleteFullScan, @@ -667,7 +667,7 @@ - (void)postNotificationsWithChanges:(TOFileSystemChanges *)changes } // Inform all notification tokens registered - for (TOFileSystemNotificationToken *token in self.notificationTokens) { + for (TOFileSystemNotificationToken *token in self.notificationTokens.allObjects) { TOFileSystemObserverCallBlock(token.notificationBlock, self, TOFileSystemObserverNotificationTypeDidChange,