From 82179a5f10cfc3a01088a7788dc80bb2fe7a9ced Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Wed, 29 Jun 2022 11:56:50 -0700 Subject: [PATCH] [Sandbox] Support bookmarking individual files Individually added files, directly opened by the user, may now store bookmarks in settings. Signed-off-by: Christopher Snowhill --- .../DataModel.xcdatamodel/contents | 5 +- Playlist/PlaylistLoader.m | 1 + .../SandboxPathBehaviorController.m | 1 + Utils/SandboxBroker.h | 1 + Utils/SandboxBroker.m | 133 ++++++++++++++---- 5 files changed, 114 insertions(+), 27 deletions(-) diff --git a/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents b/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents index 201a5da34..96a65fb5c 100644 --- a/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents +++ b/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -64,12 +64,13 @@ + - + \ No newline at end of file diff --git a/Playlist/PlaylistLoader.m b/Playlist/PlaylistLoader.m index 569127156..130f55846 100644 --- a/Playlist/PlaylistLoader.m +++ b/Playlist/PlaylistLoader.m @@ -399,6 +399,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc [[SandboxBroker sharedSandboxBroker] addFolderIfMissing:url]; [expandedURLs addObjectsFromArray:[self fileURLsAtPath:[url path]]]; } else { + [[SandboxBroker sharedSandboxBroker] addFileIfMissing:url]; [expandedURLs addObject:[NSURL fileURLWithPath:[url path]]]; } } diff --git a/Preferences/Preferences/SandboxPathBehaviorController.m b/Preferences/Preferences/SandboxPathBehaviorController.m index cf0c3a2ee..f01f92bf2 100644 --- a/Preferences/Preferences/SandboxPathBehaviorController.m +++ b/Preferences/Preferences/SandboxPathBehaviorController.m @@ -22,6 +22,7 @@ @interface SandboxToken : NSManagedObject @property(nonatomic, strong) NSString *path; @property(nonatomic, strong) NSData *bookmark; +@property(nonatomic) BOOL folder; @end @implementation SandboxPathBehaviorController diff --git a/Utils/SandboxBroker.h b/Utils/SandboxBroker.h index a5b658c9a..d46bfb419 100644 --- a/Utils/SandboxBroker.h +++ b/Utils/SandboxBroker.h @@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)shutdown; - (void)addFolderIfMissing:(NSURL *)folderUrl; +- (void)addFileIfMissing:(NSURL *)fileUrl; - (const void *)beginFolderAccess:(NSURL *)fileUrl; - (void)endFolderAccess:(const void *)handle; diff --git a/Utils/SandboxBroker.m b/Utils/SandboxBroker.m index 427d572ac..eed972d6b 100644 --- a/Utils/SandboxBroker.m +++ b/Utils/SandboxBroker.m @@ -24,6 +24,7 @@ static SandboxBroker *kSharedSandboxBroker = nil; NSInteger _refCount; NSURL *_secureUrl; NSString *_path; + BOOL _isFolder; }; @property(readonly) SandboxToken *token; @@ -34,6 +35,8 @@ static SandboxBroker *kSharedSandboxBroker = nil; @property NSInteger refCount; +@property(readonly) BOOL isFolder; + - (id)initWithToken:(SandboxToken *)token; @end @@ -45,6 +48,7 @@ static SandboxBroker *kSharedSandboxBroker = nil; obj->_secureUrl = nil; obj->_token = token; obj->_path = token.path; + obj->_isFolder = token.folder; } return obj; } @@ -72,6 +76,10 @@ static SandboxBroker *kSharedSandboxBroker = nil; - (NSString *)path { return _path; } + +- (BOOL)isFolder { + return _isFolder; +} @end @implementation SandboxBroker @@ -99,17 +107,22 @@ static SandboxBroker *kSharedSandboxBroker = nil; NSString *s = [url path]; - NSString *lastComponent = [url lastPathComponent]; + NSString *lastComponent = [url fragment]; - // Find that last component in the string from the end to make sure - // to get the last one - NSRange fragmentRange = [s rangeOfString:lastComponent - options:NSBackwardsSearch]; + if(lastComponent) { + lastComponent = @"#"; + // Find that last component in the string from the end to make sure + // to get the last one + NSRange fragmentRange = [s rangeOfString:lastComponent + options:NSBackwardsSearch]; - // Chop the fragment. - NSString *newURLString = [s substringToIndex:fragmentRange.location + fragmentRange.length]; + // Chop the fragment. + NSString *newURLString = [s substringToIndex:fragmentRange.location + fragmentRange.length]; - return [NSURL fileURLWithPath:newURLString]; + return [NSURL fileURLWithPath:newURLString]; + } else { + return url; + } } - (id)init { @@ -148,23 +161,41 @@ static SandboxBroker *kSharedSandboxBroker = nil; SandboxEntry *ret = nil; NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer]; - - NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path.length" ascending:NO]; + + NSPredicate *folderPredicate = [NSPredicate predicateWithFormat:@"folder == NO"]; + NSPredicate *filePredicate = [NSPredicate predicateWithFormat:@"path == %@", [url path]]; + NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[folderPredicate, filePredicate]]; NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"]; - request.sortDescriptors = @[sortDescriptor]; - + request.predicate = predicate; + NSError *error = nil; NSArray *results = [pc.viewContext executeFetchRequest:request error:&error]; - if(results) results = [results copy]; - if(results && [results count] > 0) { - for(SandboxToken *token in results) { - if(token.path && [SandboxBroker isPath:url aSubdirectoryOf:[NSURL fileURLWithPath:token.path]]) { - SandboxEntry *entry = [[SandboxEntry alloc] initWithToken:token]; + ret = [[SandboxEntry alloc] initWithToken:results[0]]; + } - ret = entry; - break; + if(!ret) { + predicate = [NSPredicate predicateWithFormat:@"folder == YES"]; + + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path.length" ascending:NO]; + + request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"]; + request.sortDescriptors = @[sortDescriptor]; + request.predicate = predicate; + + error = nil; + results = [pc.viewContext executeFetchRequest:request error:&error]; + if(results) results = [results copy]; + + if(results && [results count] > 0) { + for(SandboxToken *token in results) { + if(token.path && [SandboxBroker isPath:url aSubdirectoryOf:[NSURL fileURLWithPath:token.path]]) { + SandboxEntry *entry = [[SandboxEntry alloc] initWithToken:token]; + + ret = entry; + break; + } } } } @@ -193,7 +224,7 @@ static SandboxBroker *kSharedSandboxBroker = nil; SandboxEntry *_entry = nil; for(SandboxEntry *entry in storage) { - if(entry.path && [SandboxBroker isPath:folderUrl aSubdirectoryOf:[NSURL fileURLWithPath:entry.path]]) { + if(entry.path && entry.isFolder && [SandboxBroker isPath:folderUrl aSubdirectoryOf:[NSURL fileURLWithPath:entry.path]]) { _entry = entry; break; } @@ -227,18 +258,70 @@ static SandboxBroker *kSharedSandboxBroker = nil; } } +- (void)addFileIfMissing:(NSURL *)fileUrl { + if(![fileUrl isFileURL]) return; + + NSURL *url = [SandboxBroker urlWithoutFragment:fileUrl]; + + @synchronized (self) { + SandboxEntry *_entry = nil; + + for(SandboxEntry *entry in storage) { + if(entry.path) { + if((entry.isFolder && [SandboxBroker isPath:url aSubdirectoryOf:[NSURL fileURLWithPath:entry.path]]) || + (!entry.isFolder && [url isEqualTo:[NSURL fileURLWithPath:entry.path]])) { + _entry = entry; + break; + } + } + } + + if(!_entry) { + _entry = [self recursivePathTest:url]; + } + + if(!_entry) { + NSError *err = nil; + NSData *bookmark = [fileUrl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&err]; + if(!bookmark && err) { + ALog(@"Failed to add bookmark for URL: %@, with error: %@", url, [err localizedDescription]); + return; + } + + NSPersistentContainer *pc = [NSClassFromString(@"PlaylistController") sharedPersistentContainer]; + + SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext]; + + if(token) { + token.path = [url path]; + token.bookmark = bookmark; + token.folder = NO; + [pc.viewContext save:&err]; + if(err) { + ALog(@"Error saving bookmark: %@", [err localizedDescription]); + } + } + } + } +} + - (const void *)beginFolderAccess:(NSURL *)fileUrl { - NSURL *folderUrl = [[SandboxBroker urlWithoutFragment:fileUrl] URLByDeletingLastPathComponent]; + NSURL *folderUrl = [SandboxBroker urlWithoutFragment:fileUrl]; if(![folderUrl isFileURL]) return NULL; SandboxEntry *_entry = nil; + + NSString *sandboxPath = [folderUrl path]; @synchronized(self) { for(SandboxEntry *entry in storage) { - if(entry.path && [SandboxBroker isPath:folderUrl aSubdirectoryOf:[NSURL fileURLWithPath:entry.path]]) { - entry.refCount += 1; - _entry = entry; - break; + if(entry.path) { + if((entry.isFolder && [SandboxBroker isPath:folderUrl aSubdirectoryOf:[NSURL fileURLWithPath:entry.path]]) || + (!entry.isFolder && [entry.path isEqualToString:sandboxPath])) { + entry.refCount += 1; + _entry = entry; + break; + } } }