[Sandbox] Support bookmarking individual files

Individually added files, directly opened by the user, may now store
bookmarks in settings.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
swiftingly
Christopher Snowhill 2022-06-29 11:56:50 -07:00
parent 35400e1320
commit 7d26150c26
5 changed files with 114 additions and 27 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21223.12" systemVersion="22A5286j" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AlbumArtwork" representedClassName="AlbumArtwork" syncable="YES" codeGenerationType="class"> <entity name="AlbumArtwork" representedClassName="AlbumArtwork" syncable="YES" codeGenerationType="class">
<attribute name="artData" optional="YES" attributeType="Binary"/> <attribute name="artData" optional="YES" attributeType="Binary"/>
<attribute name="artHash" optional="YES" attributeType="String"/> <attribute name="artHash" optional="YES" attributeType="String"/>
@ -64,12 +64,13 @@
</entity> </entity>
<entity name="SandboxToken" representedClassName="SandboxToken" syncable="YES" codeGenerationType="class"> <entity name="SandboxToken" representedClassName="SandboxToken" syncable="YES" codeGenerationType="class">
<attribute name="bookmark" optional="YES" attributeType="Binary"/> <attribute name="bookmark" optional="YES" attributeType="Binary"/>
<attribute name="folder" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="path" optional="YES" attributeType="String"/> <attribute name="path" optional="YES" attributeType="String"/>
</entity> </entity>
<elements> <elements>
<element name="AlbumArtwork" positionX="0" positionY="207" width="128" height="59"/> <element name="AlbumArtwork" positionX="0" positionY="207" width="128" height="59"/>
<element name="PlayCount" positionX="-18" positionY="171" width="128" height="149"/> <element name="PlayCount" positionX="-18" positionY="171" width="128" height="149"/>
<element name="PlaylistEntry" positionX="-36" positionY="9" width="128" height="719"/> <element name="PlaylistEntry" positionX="-36" positionY="9" width="128" height="719"/>
<element name="SandboxToken" positionX="-18" positionY="171" width="128" height="59"/> <element name="SandboxToken" positionX="-18" positionY="171" width="128" height="74"/>
</elements> </elements>
</model> </model>

View File

@ -399,6 +399,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[[SandboxBroker sharedSandboxBroker] addFolderIfMissing:url]; [[SandboxBroker sharedSandboxBroker] addFolderIfMissing:url];
[expandedURLs addObjectsFromArray:[self fileURLsAtPath:[url path]]]; [expandedURLs addObjectsFromArray:[self fileURLsAtPath:[url path]]];
} else { } else {
[[SandboxBroker sharedSandboxBroker] addFileIfMissing:url];
[expandedURLs addObject:[NSURL fileURLWithPath:[url path]]]; [expandedURLs addObject:[NSURL fileURLWithPath:[url path]]];
} }
} }

View File

@ -22,6 +22,7 @@
@interface SandboxToken : NSManagedObject @interface SandboxToken : NSManagedObject
@property(nonatomic, strong) NSString *path; @property(nonatomic, strong) NSString *path;
@property(nonatomic, strong) NSData *bookmark; @property(nonatomic, strong) NSData *bookmark;
@property(nonatomic) BOOL folder;
@end @end
@implementation SandboxPathBehaviorController @implementation SandboxPathBehaviorController

View File

@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)shutdown; - (void)shutdown;
- (void)addFolderIfMissing:(NSURL *)folderUrl; - (void)addFolderIfMissing:(NSURL *)folderUrl;
- (void)addFileIfMissing:(NSURL *)fileUrl;
- (const void *)beginFolderAccess:(NSURL *)fileUrl; - (const void *)beginFolderAccess:(NSURL *)fileUrl;
- (void)endFolderAccess:(const void *)handle; - (void)endFolderAccess:(const void *)handle;

View File

@ -24,6 +24,7 @@ static SandboxBroker *kSharedSandboxBroker = nil;
NSInteger _refCount; NSInteger _refCount;
NSURL *_secureUrl; NSURL *_secureUrl;
NSString *_path; NSString *_path;
BOOL _isFolder;
}; };
@property(readonly) SandboxToken *token; @property(readonly) SandboxToken *token;
@ -34,6 +35,8 @@ static SandboxBroker *kSharedSandboxBroker = nil;
@property NSInteger refCount; @property NSInteger refCount;
@property(readonly) BOOL isFolder;
- (id)initWithToken:(SandboxToken *)token; - (id)initWithToken:(SandboxToken *)token;
@end @end
@ -45,6 +48,7 @@ static SandboxBroker *kSharedSandboxBroker = nil;
obj->_secureUrl = nil; obj->_secureUrl = nil;
obj->_token = token; obj->_token = token;
obj->_path = token.path; obj->_path = token.path;
obj->_isFolder = token.folder;
} }
return obj; return obj;
} }
@ -72,6 +76,10 @@ static SandboxBroker *kSharedSandboxBroker = nil;
- (NSString *)path { - (NSString *)path {
return _path; return _path;
} }
- (BOOL)isFolder {
return _isFolder;
}
@end @end
@implementation SandboxBroker @implementation SandboxBroker
@ -99,17 +107,22 @@ static SandboxBroker *kSharedSandboxBroker = nil;
NSString *s = [url path]; 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 if(lastComponent) {
// to get the last one lastComponent = @"#";
NSRange fragmentRange = [s rangeOfString:lastComponent // Find that last component in the string from the end to make sure
options:NSBackwardsSearch]; // to get the last one
NSRange fragmentRange = [s rangeOfString:lastComponent
options:NSBackwardsSearch];
// Chop the fragment. // Chop the fragment.
NSString *newURLString = [s substringToIndex:fragmentRange.location + fragmentRange.length]; NSString *newURLString = [s substringToIndex:fragmentRange.location + fragmentRange.length];
return [NSURL fileURLWithPath:newURLString]; return [NSURL fileURLWithPath:newURLString];
} else {
return url;
}
} }
- (id)init { - (id)init {
@ -149,22 +162,40 @@ static SandboxBroker *kSharedSandboxBroker = nil;
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer]; 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"]; NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"];
request.sortDescriptors = @[sortDescriptor]; request.predicate = predicate;
NSError *error = nil; NSError *error = nil;
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error]; NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
if(results) results = [results copy];
if(results && [results count] > 0) { if(results && [results count] > 0) {
for(SandboxToken *token in results) { ret = [[SandboxEntry alloc] initWithToken:results[0]];
if(token.path && [SandboxBroker isPath:url aSubdirectoryOf:[NSURL fileURLWithPath:token.path]]) { }
SandboxEntry *entry = [[SandboxEntry alloc] initWithToken:token];
ret = entry; if(!ret) {
break; 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; SandboxEntry *_entry = nil;
for(SandboxEntry *entry in storage) { 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; _entry = entry;
break; 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 { - (const void *)beginFolderAccess:(NSURL *)fileUrl {
NSURL *folderUrl = [[SandboxBroker urlWithoutFragment:fileUrl] URLByDeletingLastPathComponent]; NSURL *folderUrl = [SandboxBroker urlWithoutFragment:fileUrl];
if(![folderUrl isFileURL]) return NULL; if(![folderUrl isFileURL]) return NULL;
SandboxEntry *_entry = nil; SandboxEntry *_entry = nil;
NSString *sandboxPath = [folderUrl path];
@synchronized(self) { @synchronized(self) {
for(SandboxEntry *entry in storage) { for(SandboxEntry *entry in storage) {
if(entry.path && [SandboxBroker isPath:folderUrl aSubdirectoryOf:[NSURL fileURLWithPath:entry.path]]) { if(entry.path) {
entry.refCount += 1; if((entry.isFolder && [SandboxBroker isPath:folderUrl aSubdirectoryOf:[NSURL fileURLWithPath:entry.path]]) ||
_entry = entry; (!entry.isFolder && [entry.path isEqualToString:sandboxPath])) {
break; entry.refCount += 1;
_entry = entry;
break;
}
} }
} }