Core Data Store: Handle concurrency properly

All concurrency from other threads should pass through the viewContext's
performBlock or performBlockAndWait functions, and no other way. So now,
all access to Core Data is either happening on the main thread, or by
using these code blocks, all of which will wait for their access to
proceed.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
main
Christopher Snowhill 2022-10-30 16:48:59 -07:00
parent e3ae28369f
commit 4bed0af868
10 changed files with 361 additions and 445 deletions

View File

@ -223,12 +223,7 @@ static AppController *kAppController = nil;
request.predicate = predicate;
NSError *error = nil;
[playlistController.persistentContainerLock lock];
NSArray *results = [playlistController.persistentContainer.viewContext executeFetchRequest:request error:&error];
if(results) {
results = [results copy];
}
[playlistController.persistentContainerLock unlock];
if(results && [results count] == 1) {
PlaylistEntry *pe = results[0];
@ -434,9 +429,7 @@ static AppController *kAppController = nil;
for(PlaylistEntry *pe in playlistController.arrangedObjects) {
if(pe.deLeted) {
[playlistController.persistentContainerLock lock];
[moc deleteObject:pe];
[playlistController.persistentContainerLock unlock];
continue;
}
if([artLeftovers objectForKey:pe.artHash]) {
@ -444,11 +437,9 @@ static AppController *kAppController = nil;
}
}
[playlistController.persistentContainerLock lock];
for(NSString *key in artLeftovers) {
[moc deleteObject:[artLeftovers objectForKey:key]];
}
[playlistController.persistentContainerLock unlock];
[playlistController commitPersistentStore];

View File

@ -68,7 +68,6 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
@property(retain) NSString *_Nullable totalTime;
@property(retain) NSString *_Nullable currentStatus;
@property(strong, nonatomic, readonly) NSLock *_Nonnull persistentContainerLock;
@property(strong, nonatomic, readonly) NSPersistentContainer *_Nonnull persistentContainer;
@property(strong, nonatomic, readonly) NSMutableDictionary<NSString *, AlbumArtwork *> *_Nonnull persistentArtStorage;
@ -143,7 +142,6 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
- (void)readShuffleListFromDataStore;
+ (NSPersistentContainer *_Nonnull)sharedPersistentContainer;
+ (NSLock *_Nonnull)sharedPersistentContainerLock;
// reload metadata of selection
- (IBAction)reloadTags:(id _Nullable)sender;

View File

@ -30,8 +30,6 @@
extern BOOL kAppControllerShuttingDown;
NSLock *kPersistentContainerLock = nil;
NSPersistentContainer *kPersistentContainer = nil;
@implementation PlaylistController
@ -127,10 +125,6 @@ static void *playlistControllerContext = &playlistControllerContext;
}];
kPersistentContainer = self.persistentContainer;
_persistentContainerLock = [[NSLock alloc] init];
kPersistentContainerLock = self.persistentContainerLock;
self.persistentContainer.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
@ -144,10 +138,6 @@ static void *playlistControllerContext = &playlistControllerContext;
return kPersistentContainer;
}
+ (NSLock *)sharedPersistentContainerLock {
return kPersistentContainerLock;
}
- (void)awakeFromNib {
[super awakeFromNib];
@ -251,63 +241,46 @@ static void *playlistControllerContext = &playlistControllerContext;
}
- (void)commitPersistentStore {
NSError *error = nil;
[self.persistentContainerLock lock];
[self.persistentContainer.viewContext save:&error];
[self.persistentContainerLock unlock];
if(error) {
ALog(@"Error committing playlist storage: %@", [error localizedDescription]);
}
[self.persistentContainer.viewContext performBlockAndWait:^{
NSError *error = nil;
[self.persistentContainer.viewContext save:&error];
if(error) {
ALog(@"Error committing playlist storage: %@", [error localizedDescription]);
}
}];
}
- (void)updatePlayCountForTrack:(PlaylistEntry *)pe {
if(pe.countAdded) return;
pe.countAdded = YES;
PlayCount *pc = pe.playCountItem;
__block PlayCount *pc = pe.playCountItem;
if(pc) {
[self.persistentContainerLock lock];
pc.count += 1;
pc.lastPlayed = [NSDate date];
[self.persistentContainerLock unlock];
[self.persistentContainer.viewContext performBlockAndWait:^{
pc.count += 1;
pc.lastPlayed = [NSDate date];
}];
} else {
[self.persistentContainerLock lock];
pc = [NSEntityDescription insertNewObjectForEntityForName:@"PlayCount" inManagedObjectContext:self.persistentContainer.viewContext];
pc.count = 1;
pc.firstSeen = pc.lastPlayed = [NSDate date];
pc.album = pe.album;
pc.artist = pe.artist;
pc.title = pe.title;
pc.filename = pe.filenameFragment;
[self.persistentContainerLock unlock];
[self.persistentContainer.viewContext performBlockAndWait:^{
pc = [NSEntityDescription insertNewObjectForEntityForName:@"PlayCount" inManagedObjectContext:self.persistentContainer.viewContext];
pc.count = 1;
pc.firstSeen = pc.lastPlayed = [NSDate date];
pc.album = pe.album;
pc.artist = pe.artist;
pc.title = pe.title;
pc.filename = pe.filenameFragment;
}];
}
[self commitPersistentStore];
}
- (void)firstSawTrack:(PlaylistEntry *)pe {
PlayCount *pc = pe.playCountItem;
__block PlayCount *pc = pe.playCountItem;
if(!pc) {
[self.persistentContainerLock lock];
pc = [NSEntityDescription insertNewObjectForEntityForName:@"PlayCount" inManagedObjectContext:self.persistentContainer.viewContext];
pc.count = 0;
pc.firstSeen = [NSDate date];
pc.album = pe.album;
pc.artist = pe.artist;
pc.title = pe.title;
pc.filename = pe.filenameFragment;
[self.persistentContainerLock unlock];
}
}
- (void)ratingUpdatedWithEntry:(PlaylistEntry *)pe rating:(CGFloat)rating {
if(pe && !pe.deLeted) {
PlayCount *pc = pe.playCountItem;
if(!pc) {
[self.persistentContainerLock lock];
[self.persistentContainer.viewContext performBlockAndWait:^{
pc = [NSEntityDescription insertNewObjectForEntityForName:@"PlayCount" inManagedObjectContext:self.persistentContainer.viewContext];
pc.count = 0;
pc.firstSeen = [NSDate date];
@ -315,12 +288,29 @@ static void *playlistControllerContext = &playlistControllerContext;
pc.artist = pe.artist;
pc.title = pe.title;
pc.filename = pe.filenameFragment;
[self.persistentContainerLock unlock];
}];
}
}
- (void)ratingUpdatedWithEntry:(PlaylistEntry *)pe rating:(CGFloat)rating {
if(pe && !pe.deLeted) {
__block PlayCount *pc = pe.playCountItem;
if(!pc) {
[self.persistentContainer.viewContext performBlockAndWait:^{
pc = [NSEntityDescription insertNewObjectForEntityForName:@"PlayCount" inManagedObjectContext:self.persistentContainer.viewContext];
pc.count = 0;
pc.firstSeen = [NSDate date];
pc.album = pe.album;
pc.artist = pe.artist;
pc.title = pe.title;
pc.filename = pe.filenameFragment;
}];
}
[self.persistentContainerLock lock];
pc.rating = rating;
[self.persistentContainerLock unlock];
[self.persistentContainer.viewContext performBlockAndWait:^{
pc.rating = rating;
}];
[self commitPersistentStore];
}
@ -330,7 +320,9 @@ static void *playlistControllerContext = &playlistControllerContext;
PlayCount *pc = pe.playCountItem;
if(pc) {
pc.count = 0;
[self.persistentContainer.viewContext performBlockAndWait:^{
pc.count = 0;
}];
}
}
@ -338,7 +330,9 @@ static void *playlistControllerContext = &playlistControllerContext;
PlayCount *pc = pe.playCountItem;
if(pc) {
pc.rating = 0;
[self.persistentContainer.viewContext performBlockAndWait:^{
pc.rating = 0;
}];
}
}
@ -1380,19 +1374,16 @@ static void *playlistControllerContext = &playlistControllerContext;
request.predicate = predicate;
request.sortDescriptors = @[sortDescriptor];
NSError *error = nil;
[self.persistentContainerLock lock];
NSArray *results = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if(results) {
results = [results copy];
}
[self.persistentContainerLock unlock];
[self.persistentContainer.viewContext performBlockAndWait:^{
NSError *error = nil;
NSArray *results = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if(results && [results count] > 0) {
[queueList removeAllObjects];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [results count])];
[queueList insertObjects:results atIndexes:indexSet];
}
if(results && [results count] > 0) {
[queueList removeAllObjects];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [results count])];
[queueList insertObjects:results atIndexes:indexSet];
}
}];
}
- (void)readShuffleListFromDataStore {
@ -1404,19 +1395,16 @@ static void *playlistControllerContext = &playlistControllerContext;
request.predicate = predicate;
request.sortDescriptors = @[sortDescriptor];
NSError *error = nil;
[self.persistentContainerLock lock];
NSArray *results = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if(results) {
results = [results copy];
}
[self.persistentContainerLock unlock];
[self.persistentContainer.viewContext performBlockAndWait:^{
NSError *error = nil;
NSArray *results = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if(results && [results count] > 0) {
[shuffleList removeAllObjects];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [results count])];
[shuffleList insertObjects:results atIndexes:indexSet];
}
if(results && [results count] > 0) {
[shuffleList removeAllObjects];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [results count])];
[shuffleList insertObjects:results atIndexes:indexSet];
}
}];
}
- (void)addShuffledListToFront {
@ -1946,16 +1934,18 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
- (BOOL)pathSuggesterEmpty {
BOOL rval;
__block BOOL rval;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"];
NSError *error = nil;
[self.persistentContainerLock lock];
NSArray *results = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if(!results || [results count] < 1) rval = YES;
else rval = NO;
[self.persistentContainerLock unlock];
[self.persistentContainer.viewContext performBlockAndWait:^{
NSError *error = nil;
NSArray *results = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
if(!results || [results count] < 1)
rval = YES;
else
rval = NO;
}];
return rval;
}

View File

@ -16,7 +16,6 @@
#import "SHA256Digest.h"
#import "SecondsFormatter.h"
extern NSLock *kPersistentContainerLock;
extern NSPersistentContainer *kPersistentContainer;
extern NSMutableDictionary<NSString *, AlbumArtwork *> *kArtworkDictionary;
@ -364,11 +363,9 @@ extern NSMutableDictionary<NSString *, AlbumArtwork *> *kArtworkDictionary;
self.artHash = imageCacheTag;
if(![kArtworkDictionary objectForKey:imageCacheTag]) {
[kPersistentContainerLock lock];
AlbumArtwork *art = [NSEntityDescription insertNewObjectForEntityForName:@"AlbumArtwork" inManagedObjectContext:kPersistentContainer.viewContext];
art.artHash = imageCacheTag;
art.artData = albumArtInternal;
[kPersistentContainerLock unlock];
[kArtworkDictionary setObject:art forKey:imageCacheTag];
}
@ -585,30 +582,30 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path) {
NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[albumPredicate, artistPredicate, titlePredicate]];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"PlayCount"];
request.predicate = predicate;
__block PlayCount *item = nil;
NSError *error = nil;
[kPersistentContainerLock lock];
NSArray *results = [kPersistentContainer.viewContext executeFetchRequest:request error:&error];
[kPersistentContainer.viewContext performBlockAndWait:^{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"PlayCount"];
request.predicate = predicate;
if(!results || [results count] < 1) {
NSPredicate *filenamePredicate = [NSPredicate predicateWithFormat:@"filename == %@", self.filenameFragment];
NSError *error = nil;
NSArray *results = [kPersistentContainer.viewContext executeFetchRequest:request error:&error];
request = [NSFetchRequest fetchRequestWithEntityName:@"PlayCount"];
request.predicate = filenamePredicate;
if(!results || [results count] < 1) {
NSPredicate *filenamePredicate = [NSPredicate predicateWithFormat:@"filename == %@", self.filenameFragment];
results = [kPersistentContainer.viewContext executeFetchRequest:request error:&error];
}
if(results) {
results = [results copy];
}
[kPersistentContainerLock unlock];
request = [NSFetchRequest fetchRequestWithEntityName:@"PlayCount"];
request.predicate = filenamePredicate;
if(!results || [results count] < 1) return nil;
results = [kPersistentContainer.viewContext executeFetchRequest:request error:&error];
}
return results[0];
if(!results || [results count] < 1) return;
item = results[0];
}];
return item;
}
@dynamic playCount;

View File

@ -565,15 +565,13 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
__block NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count];
for(NSURL *url in validURLs) {
__block PlaylistEntry *pe;
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
[self->playlistController.persistentContainerLock lock];
pe = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:self->playlistController.persistentContainer.viewContext];
pe.url = url;
pe.index = index + i;
pe.rawTitle = [[url path] lastPathComponent];
pe.queuePosition = -1;
[self->playlistController.persistentContainerLock unlock];
});
[entries addObject:pe];
@ -589,14 +587,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(xmlData) {
for(NSDictionary *entry in [xmlData objectForKey:@"entries"]) {
__block PlaylistEntry *pe;
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
[self->playlistController.persistentContainerLock lock];
pe = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:self->playlistController.persistentContainer.viewContext];
[pe setValuesForKeysWithDictionary:entry];
pe.index = index + i;
pe.queuePosition = -1;
[self->playlistController.persistentContainerLock unlock];
});
[entries addObject:pe];
@ -791,25 +787,22 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[deletedPredicate, hasUrlPredicate]];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"PlaylistEntry"];
request.predicate = predicate;
[moc performBlockAndWait:^{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"PlaylistEntry"];
request.predicate = predicate;
NSError *error;
[playlistController.persistentContainerLock lock];
NSArray *results = [moc executeFetchRequest:request error:&error];
if(results) {
results = [results copy];
}
[playlistController.persistentContainerLock unlock];
NSError *error;
NSArray *results = [moc executeFetchRequest:request error:&error];
if(results && [results count] > 0) {
for(PlaylistEntry *pe in results) {
NSMutableArray *entrySet = [uniquePathsEntries objectForKey:pe.urlString];
if(entrySet) {
[entrySet addObject:pe];
if(results && [results count] > 0) {
for(PlaylistEntry *pe in results) {
NSMutableArray *entrySet = [uniquePathsEntries objectForKey:pe.urlString];
if(entrySet) {
[entrySet addObject:pe];
}
}
}
}
}];
for(size_t i = 0, j = [outArray count]; i < j; i += 2) {
__block NSString *entryKey = [outArray objectAtIndex:i];
@ -927,10 +920,8 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"AlbumArtwork"];
NSError *error = nil;
[playlistController.persistentContainerLock lock];
NSArray *results = [moc executeFetchRequest:request error:&error];
if(!results) {
[playlistController.persistentContainerLock unlock];
ALog(@"Error fetching AlbumArtwork objects: %@\n%@", [error localizedDescription], [error userInfo]);
abort();
}
@ -938,7 +929,6 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
for(AlbumArtwork *art in results) {
[kArtworkDictionary setObject:art forKey:art.artHash];
}
[playlistController.persistentContainerLock unlock];
request = [NSFetchRequest fetchRequestWithEntityName:@"PlaylistEntry"];
@ -946,30 +936,24 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
request.sortDescriptors = @[sortDescriptor];
[playlistController.persistentContainerLock lock];
results = [moc executeFetchRequest:request error:&error];
if(!results) {
[playlistController.persistentContainerLock unlock];
ALog(@"Error fetching PlaylistEntry objects: %@\n%@", [error localizedDescription], [error userInfo]);
abort();
}
if([results count] == 0) {
[playlistController.persistentContainerLock unlock];
return NO;
}
NSMutableArray *resultsCopy = [results mutableCopy];
[playlistController.persistentContainerLock unlock];
NSMutableIndexSet *pruneSet = [[NSMutableIndexSet alloc] init];
NSUInteger index = 0;
for(PlaylistEntry *pe in resultsCopy) {
if(pe.deLeted || !pe.urlString || [pe.urlString length] < 1) {
[pruneSet addIndex:index];
[playlistController.persistentContainerLock lock];
[moc deleteObject:pe];
[playlistController.persistentContainerLock unlock];
}
++index;
}

View File

@ -55,7 +55,6 @@
[pathsList removeObjects:[pathsList arrangedObjects]];
NSLock *lock = [NSClassFromString(@"PlaylistController") sharedPersistentContainerLock];
NSPersistentContainer *pc = [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
NSPredicate *hasUrlPredicate = [NSPredicate predicateWithFormat:@"urlString != nil && urlString != %@", @""];
@ -67,12 +66,7 @@
request.predicate = predicate;
NSError *error = nil;
[lock lock];
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
if(results) {
results = [results copy];
}
[lock unlock];
if(!results || [results count] < 1) return;

View File

@ -36,18 +36,12 @@
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path" ascending:YES];
[self setSortDescriptors:@[sortDescriptor]];
NSLock *lock = [NSClassFromString(@"PlaylistController") sharedPersistentContainerLock];
NSPersistentContainer *pc = [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"];
NSError *error = nil;
[lock lock];
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
if(results) {
results = [results copy];
}
[lock unlock];
if(results && [results count] > 0) {
for(SandboxToken *token in results) {
@ -67,31 +61,31 @@
return;
}
NSLock *lock = [NSClassFromString(@"PlaylistController") sharedPersistentContainerLock];
NSPersistentContainer *pc = [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
[lock lock];
SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext];
[pc.viewContext performBlockAndWait:^{
SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext];
if(token) {
token.path = [url path];
token.bookmark = bookmark;
[pc.viewContext save:&err];
[lock unlock];
if(err) {
ALog(@"Error saving bookmark: %@", [err localizedDescription]);
} else {
[self addObject:@{ @"path": [url path], @"valid": NSLocalizedPrefString(@"ValidYes"), @"isFolder": @(token.folder), @"token": token }];
[NSClassFromString(@"SandboxBroker") cleanupFolderAccess];
[self refresh];
if(token) {
NSError *err = nil;
token.path = [url path];
token.bookmark = bookmark;
[pc.viewContext save:&err];
if(err) {
ALog(@"Error saving bookmark: %@", [err localizedDescription]);
} else {
[self addObject:@{@"path": [url path],
@"valid": NSLocalizedPrefString(@"ValidYes"),
@"isFolder": @(token.folder),
@"token": token}];
[NSClassFromString(@"SandboxBroker") cleanupFolderAccess];
[self refresh];
}
}
} else {
[lock unlock];
}
}];
}
- (void)removeToken:(id)token {
NSLock *lock = [NSClassFromString(@"PlaylistController") sharedPersistentContainerLock];
NSPersistentContainer *pc = [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
NSArray *objects = [[self arrangedObjects] copy];
@ -101,46 +95,45 @@
for(NSDictionary *obj in objects) {
if([[obj objectForKey:@"token"] isEqualTo:token]) {
[self removeObject:obj];
[lock lock];
[pc.viewContext deleteObject:token];
[lock unlock];
[pc.viewContext performBlockAndWait:^{
[pc.viewContext deleteObject:token];
}];
updated = YES;
break;
}
}
if(updated) {
NSError *error;
[lock lock];
[pc.viewContext save:&error];
[lock unlock];
if(error) {
ALog(@"Error deleting bookmark: %@", [error localizedDescription]);
}
[pc.viewContext performBlockAndWait:^{
NSError *error;
[pc.viewContext save:&error];
if(error) {
ALog(@"Error deleting bookmark: %@", [error localizedDescription]);
}
}];
}
}
- (void)removeStaleEntries {
BOOL updated = NO;
NSLock *lock = [NSClassFromString(@"PlaylistController") sharedPersistentContainerLock];
NSPersistentContainer *pc = [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
for(NSDictionary *entry in [[self arrangedObjects] copy]) {
if([[entry objectForKey:@"valid"] isEqualToString:NSLocalizedPrefString(@"ValidNo")]) {
[self removeObject:entry];
[lock lock];
[pc.viewContext deleteObject:[entry objectForKey:@"token"]];
[lock unlock];
[pc.viewContext performBlockAndWait:^{
[pc.viewContext deleteObject:[entry objectForKey:@"token"]];
}];
updated = YES;
}
}
if(updated) {
NSError *error;
[lock lock];
[pc.viewContext save:&error];
[lock unlock];
if(error) {
ALog(@"Error saving after removing stale bookmarks: %@", [error localizedDescription]);
}
[pc.viewContext performBlockAndWait:^{
NSError *error;
[pc.viewContext save:&error];
if(error) {
ALog(@"Error saving after removing stale bookmarks: %@", [error localizedDescription]);
}
}];
}
}

View File

@ -13,7 +13,6 @@
// with format (entryKey, transformerName)
static NSDictionary *importKeys;
extern NSLock *kPersistentContainerLock;
extern NSPersistentContainer *kPersistentContainer;
@implementation SpotlightPlaylistEntry
@ -38,53 +37,54 @@ extern NSPersistentContainer *kPersistentContainer;
}
+ (PlaylistEntry *)playlistEntryWithMetadataItem:(NSMetadataItem *)metadataItem {
[kPersistentContainerLock lock];
PlaylistEntry *entry = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:kPersistentContainer.viewContext];
__block PlaylistEntry *entry = nil;
[kPersistentContainer.viewContext performBlockAndWait:^{
entry = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:kPersistentContainer.viewContext];
entry.deLeted = YES;
entry.deLeted = YES;
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
// loop through the keys we want to extract
for(NSString *mdKey in importKeys) {
if(![metadataItem valueForAttribute:mdKey]) continue;
id importTarget = [importKeys objectForKey:mdKey];
// Just copy the object from metadata
if([importTarget isKindOfClass:[NSString class]]) {
if([importTarget isEqualToString:@"length"]) {
// fake it
NSNumber *number = [metadataItem valueForAttribute:mdKey];
[dict setValue:@(44100.0) forKey:@"samplerate"];
[dict setValue:@(44100.0 * [number doubleValue]) forKey:@"totalFrames"];
} else {
[dict setValue:[metadataItem valueForAttribute:mdKey]
forKey:importTarget];
// loop through the keys we want to extract
for(NSString *mdKey in importKeys) {
if(![metadataItem valueForAttribute:mdKey]) continue;
id importTarget = [importKeys objectForKey:mdKey];
// Just copy the object from metadata
if([importTarget isKindOfClass:[NSString class]]) {
if([importTarget isEqualToString:@"length"]) {
// fake it
NSNumber *number = [metadataItem valueForAttribute:mdKey];
[dict setValue:@(44100.0) forKey:@"samplerate"];
[dict setValue:@(44100.0 * [number doubleValue]) forKey:@"totalFrames"];
} else {
[dict setValue:[metadataItem valueForAttribute:mdKey]
forKey:importTarget];
}
}
// Transform the value in metadata before copying it in
else if([importTarget isKindOfClass:[NSArray class]]) {
NSString *importKey = [importTarget objectAtIndex:0];
NSValueTransformer *transformer =
[NSValueTransformer valueTransformerForName:[importTarget objectAtIndex:1]];
id transformedValue = [transformer transformedValue:
[metadataItem valueForAttribute:mdKey]];
[dict setValue:transformedValue forKey:importKey];
}
// The importKeys dictionary contains something strange...
else {
NSString *errString =
[NSString stringWithFormat:@"ERROR: Could not import key %@", mdKey];
NSAssert(NO, errString);
}
}
// Transform the value in metadata before copying it in
else if([importTarget isKindOfClass:[NSArray class]]) {
NSString *importKey = [importTarget objectAtIndex:0];
NSValueTransformer *transformer =
[NSValueTransformer valueTransformerForName:[importTarget objectAtIndex:1]];
id transformedValue = [transformer transformedValue:
[metadataItem valueForAttribute:mdKey]];
[dict setValue:transformedValue forKey:importKey];
}
// The importKeys dictionary contains something strange...
else {
NSString *errString =
[NSString stringWithFormat:@"ERROR: Could not import key %@", mdKey];
NSAssert(NO, errString);
}
}
NSURL *url = [dict objectForKey:@"url"];
[dict removeObjectForKey:@"url"];
NSURL *url = [dict objectForKey:@"url"];
[dict removeObjectForKey:@"url"];
entry.url = url;
entry.url = url;
[entry setMetadata:dict];
[kPersistentContainerLock unlock];
[entry setMetadata:dict];
}];
return entry;
}

View File

@ -14,7 +14,6 @@
#import "SHA256Digest.h"
extern NSLock *kPersistentContainerLock;
extern NSPersistentContainer *kPersistentContainer;
NSString *getDatabasePath(void) {
@ -1444,114 +1443,112 @@ static SQLiteStore *g_sharedStore = nil;
#endif
- (PlaylistEntry *_Nonnull)getTrack:(int64_t)trackId {
[kPersistentContainerLock lock];
PlaylistEntry *entry = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:kPersistentContainer.viewContext];
__block PlaylistEntry *entry = nil;
[kPersistentContainer.viewContext performBlockAndWait:^{
entry = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:kPersistentContainer.viewContext];
if(trackId < 0) {
entry.error = YES;
entry.errorMessage = NSLocalizedString(@"ErrorInvalidTrackId", @"");
entry.deLeted = YES;
[kPersistentContainerLock unlock];
return entry;
}
if(trackId < 0) {
entry.error = YES;
entry.errorMessage = NSLocalizedString(@"ErrorInvalidTrackId", @"");
entry.deLeted = YES;
return;
}
sqlite3_stmt *st = stmt[stmt_select_track_data];
sqlite3_stmt *st = stmt[stmt_select_track_data];
if(sqlite3_reset(st) ||
sqlite3_bind_int64(st, select_track_data_in_id, trackId)) {
entry.error = YES;
entry.errorMessage = NSLocalizedString(@"ErrorSqliteProblem", @"");
entry.deLeted = YES;
[kPersistentContainerLock unlock];
return entry;
}
if(sqlite3_reset(st) ||
sqlite3_bind_int64(st, select_track_data_in_id, trackId)) {
entry.error = YES;
entry.errorMessage = NSLocalizedString(@"ErrorSqliteProblem", @"");
entry.deLeted = YES;
return;
}
int rc = sqlite3_step(st);
int rc = sqlite3_step(st);
if(rc != SQLITE_ROW && rc != SQLITE_DONE) {
sqlite3_reset(st);
entry.error = YES;
entry.errorMessage = NSLocalizedString(@"ErrorSqliteProblem", @"");
entry.deLeted = YES;
return;
}
if(rc == SQLITE_ROW) {
int64_t urlId = sqlite3_column_int64(st, select_track_data_out_url_id);
int64_t artId = sqlite3_column_int64(st, select_track_data_out_art_id);
int64_t albumId = sqlite3_column_int64(st, select_track_data_out_album_id);
int64_t albumartistId = sqlite3_column_int64(st, select_track_data_out_albumartist_id);
int64_t artistId = sqlite3_column_int64(st, select_track_data_out_artist_id);
int64_t titleId = sqlite3_column_int64(st, select_track_data_out_title_id);
int64_t genreId = sqlite3_column_int64(st, select_track_data_out_genre_id);
int64_t codecId = sqlite3_column_int64(st, select_track_data_out_codec_id);
int64_t cuesheetId = sqlite3_column_int64(st, select_track_data_out_cuesheet_id);
int64_t encodingId = sqlite3_column_int64(st, select_track_data_out_encoding_id);
int64_t trackNr = sqlite3_column_int64(st, select_track_data_out_track);
int64_t year = sqlite3_column_int64(st, select_track_data_out_year);
int64_t unsignedFmt = sqlite3_column_int64(st, select_track_data_out_unsigned);
int64_t bitrate = sqlite3_column_int64(st, select_track_data_out_bitrate);
double samplerate = sqlite3_column_double(st, select_track_data_out_samplerate);
int64_t bitspersample = sqlite3_column_int64(st, select_track_data_out_bitspersample);
int64_t channels = sqlite3_column_int64(st, select_track_data_out_channels);
int64_t channelConfig = sqlite3_column_int64(st, select_track_data_out_channelconfig);
int64_t endianId = sqlite3_column_int64(st, select_track_data_out_endian_id);
int64_t floatingpoint = sqlite3_column_int64(st, select_track_data_out_floatingpoint);
int64_t totalframes = sqlite3_column_int64(st, select_track_data_out_totalframes);
int64_t metadataloaded = sqlite3_column_int64(st, select_track_data_out_metadataloaded);
int64_t seekable = sqlite3_column_int64(st, select_track_data_out_seekable);
double volume = sqlite3_column_double(st, select_track_data_out_volume);
double replaygainalbumgain = sqlite3_column_double(st, select_track_data_out_replaygainalbumgain);
double replaygainalbumpeak = sqlite3_column_double(st, select_track_data_out_replaygainalbumpeak);
double replaygaintrackgain = sqlite3_column_double(st, select_track_data_out_replaygaintrackgain);
double replaygaintrackpeak = sqlite3_column_double(st, select_track_data_out_replaygaintrackpeak);
uint64_t discNr = ((uint64_t)trackNr) >> 32;
trackNr &= (1UL << 32) - 1;
entry.url = urlForPath([self getString:urlId]);
entry.album = [self getString:albumId];
entry.albumartist = [self getString:albumartistId];
entry.artist = [self getString:artistId];
entry.rawTitle = [self getString:titleId];
entry.genre = [self getString:genreId];
entry.codec = [self getString:codecId];
entry.cuesheet = [self getString:cuesheetId];
entry.encoding = [self getString:encodingId];
entry.track = (int32_t)trackNr;
entry.disc = (int32_t)discNr;
entry.year = (int32_t)year;
entry.unSigned = !!unsignedFmt;
entry.bitrate = (int32_t)bitrate;
entry.sampleRate = samplerate;
entry.bitsPerSample = (int32_t)bitspersample;
entry.channels = (int32_t)channels;
entry.channelConfig = (uint32_t)channelConfig;
entry.endian = [self getString:endianId];
entry.floatingPoint = !!floatingpoint;
entry.totalFrames = totalframes;
entry.seekable = !!seekable;
entry.volume = volume;
entry.replayGainAlbumGain = replaygainalbumgain;
entry.replayGainAlbumPeak = replaygainalbumpeak;
entry.replayGainTrackGain = replaygaintrackgain;
entry.replayGainTrackPeak = replaygaintrackpeak;
entry.albumArtInternal = [self getArt:artId];
entry.metadataLoaded = !!metadataloaded;
entry.dbIndex = trackId;
} else {
entry.error = YES;
entry.errorMessage = NSLocalizedString(@"ErrorTrackMissing", @"");
entry.deLeted = YES;
}
if(rc != SQLITE_ROW && rc != SQLITE_DONE) {
sqlite3_reset(st);
entry.error = YES;
entry.errorMessage = NSLocalizedString(@"ErrorSqliteProblem", @"");
entry.deLeted = YES;
[kPersistentContainerLock unlock];
return entry;
}
if(rc == SQLITE_ROW) {
int64_t urlId = sqlite3_column_int64(st, select_track_data_out_url_id);
int64_t artId = sqlite3_column_int64(st, select_track_data_out_art_id);
int64_t albumId = sqlite3_column_int64(st, select_track_data_out_album_id);
int64_t albumartistId = sqlite3_column_int64(st, select_track_data_out_albumartist_id);
int64_t artistId = sqlite3_column_int64(st, select_track_data_out_artist_id);
int64_t titleId = sqlite3_column_int64(st, select_track_data_out_title_id);
int64_t genreId = sqlite3_column_int64(st, select_track_data_out_genre_id);
int64_t codecId = sqlite3_column_int64(st, select_track_data_out_codec_id);
int64_t cuesheetId = sqlite3_column_int64(st, select_track_data_out_cuesheet_id);
int64_t encodingId = sqlite3_column_int64(st, select_track_data_out_encoding_id);
int64_t trackNr = sqlite3_column_int64(st, select_track_data_out_track);
int64_t year = sqlite3_column_int64(st, select_track_data_out_year);
int64_t unsignedFmt = sqlite3_column_int64(st, select_track_data_out_unsigned);
int64_t bitrate = sqlite3_column_int64(st, select_track_data_out_bitrate);
double samplerate = sqlite3_column_double(st, select_track_data_out_samplerate);
int64_t bitspersample = sqlite3_column_int64(st, select_track_data_out_bitspersample);
int64_t channels = sqlite3_column_int64(st, select_track_data_out_channels);
int64_t channelConfig = sqlite3_column_int64(st, select_track_data_out_channelconfig);
int64_t endianId = sqlite3_column_int64(st, select_track_data_out_endian_id);
int64_t floatingpoint = sqlite3_column_int64(st, select_track_data_out_floatingpoint);
int64_t totalframes = sqlite3_column_int64(st, select_track_data_out_totalframes);
int64_t metadataloaded = sqlite3_column_int64(st, select_track_data_out_metadataloaded);
int64_t seekable = sqlite3_column_int64(st, select_track_data_out_seekable);
double volume = sqlite3_column_double(st, select_track_data_out_volume);
double replaygainalbumgain = sqlite3_column_double(st, select_track_data_out_replaygainalbumgain);
double replaygainalbumpeak = sqlite3_column_double(st, select_track_data_out_replaygainalbumpeak);
double replaygaintrackgain = sqlite3_column_double(st, select_track_data_out_replaygaintrackgain);
double replaygaintrackpeak = sqlite3_column_double(st, select_track_data_out_replaygaintrackpeak);
uint64_t discNr = ((uint64_t)trackNr) >> 32;
trackNr &= (1UL << 32) - 1;
entry.url = urlForPath([self getString:urlId]);
entry.album = [self getString:albumId];
entry.albumartist = [self getString:albumartistId];
entry.artist = [self getString:artistId];
entry.rawTitle = [self getString:titleId];
entry.genre = [self getString:genreId];
entry.codec = [self getString:codecId];
entry.cuesheet = [self getString:cuesheetId];
entry.encoding = [self getString:encodingId];
entry.track = (int32_t)trackNr;
entry.disc = (int32_t)discNr;
entry.year = (int32_t)year;
entry.unSigned = !!unsignedFmt;
entry.bitrate = (int32_t)bitrate;
entry.sampleRate = samplerate;
entry.bitsPerSample = (int32_t)bitspersample;
entry.channels = (int32_t)channels;
entry.channelConfig = (uint32_t)channelConfig;
entry.endian = [self getString:endianId];
entry.floatingPoint = !!floatingpoint;
entry.totalFrames = totalframes;
entry.seekable = !!seekable;
entry.volume = volume;
entry.replayGainAlbumGain = replaygainalbumgain;
entry.replayGainAlbumPeak = replaygainalbumpeak;
entry.replayGainTrackGain = replaygaintrackgain;
entry.replayGainTrackPeak = replaygaintrackpeak;
entry.albumArtInternal = [self getArt:artId];
entry.metadataLoaded = !!metadataloaded;
entry.dbIndex = trackId;
} else {
entry.error = YES;
entry.errorMessage = NSLocalizedString(@"ErrorTrackMissing", @"");
entry.deLeted = YES;
}
[kPersistentContainerLock unlock];
sqlite3_reset(st);
}];
return entry;
}
@ -1846,7 +1843,7 @@ static SQLiteStore *g_sharedStore = nil;
}
- (PlaylistEntry *)playlistGetItem:(int64_t)index {
PlaylistEntry *entry = nil;
__block PlaylistEntry *entry = nil;
sqlite3_stmt *st = stmt[stmt_select_playlist];
@ -1866,15 +1863,15 @@ static SQLiteStore *g_sharedStore = nil;
int64_t entryId = sqlite3_column_int64(st, select_playlist_out_entry_id);
entry = [self getTrack:trackId];
if(!entry.deLeted && !entry.error) {
[kPersistentContainerLock lock];
entry.index = index;
entry.entryId = entryId;
[kPersistentContainerLock unlock];
[kPersistentContainer.viewContext performBlockAndWait:^{
entry.index = index;
entry.entryId = entryId;
}];
} else {
[kPersistentContainerLock lock];
[kPersistentContainer.viewContext deleteObject:entry];
[kPersistentContainerLock unlock];
entry = nil;
[kPersistentContainer.viewContext performBlockAndWait:^{
[kPersistentContainer.viewContext deleteObject:entry];
entry = nil;
}];
}
}

View File

@ -92,10 +92,6 @@ static SandboxBroker *kSharedSandboxBroker = nil;
return kSharedSandboxBroker;
}
+ (NSLock *)sharedPersistentContainerLock {
return [NSClassFromString(@"PlaylistController") sharedPersistentContainerLock];
}
+ (NSPersistentContainer *)sharedPersistentContainer {
return [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
}
@ -151,57 +147,49 @@ static SandboxBroker *kSharedSandboxBroker = nil;
}
- (SandboxEntry *)recursivePathTest:(NSURL *)url {
SandboxEntry *ret = nil;
__block SandboxEntry *ret = nil;
NSLock *lock = [SandboxBroker sharedPersistentContainerLock];
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
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.predicate = predicate;
NSError *error = nil;
[lock lock];
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
if(results) {
results = [results copy];
}
[lock unlock];
if(results && [results count] > 0) {
ret = [[SandboxEntry alloc] initWithToken:results[0]];
}
[pc.viewContext performBlockAndWait:^{
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[folderPredicate, filePredicate]];
if(!ret) {
predicate = [NSPredicate predicateWithFormat:@"folder == YES"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path.length" ascending:NO];
request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"];
request.sortDescriptors = @[sortDescriptor];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"];
request.predicate = predicate;
error = nil;
[lock lock];
results = [pc.viewContext executeFetchRequest:request error:&error];
if(results) {
results = [results copy];
}
[lock unlock];
NSError *error = nil;
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
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 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;
}
}
}
}
}
}];
if(ret) {
BOOL isStale;
@ -212,7 +200,9 @@ static SandboxBroker *kSharedSandboxBroker = nil;
return nil;
}
ret.secureUrl = secureUrl;
[pc.viewContext performBlockAndWait:^{
ret.secureUrl = secureUrl;
}];
return ret;
}
@ -253,22 +243,17 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
return;
}
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
NSLock *lock = [SandboxBroker sharedPersistentContainerLock];
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
[lock lock];
[pc.viewContext performBlockAndWait:^{
SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext];
if(token) {
token.path = [folderUrl path];
token.bookmark = bookmark;
[lock unlock];
[SandboxBroker cleanupFolderAccess];
} else {
[lock unlock];
}
});
}];
}
}
}
@ -303,11 +288,9 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
return;
}
NSLock *lock = [SandboxBroker sharedPersistentContainerLock];
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
[lock lock];
[pc.viewContext performBlockAndWait:^{
SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext];
if(token) {
@ -315,8 +298,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
token.bookmark = bookmark;
token.folder = NO;
}
[lock unlock];
});
}];
}
}
}
@ -372,19 +354,14 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
return;
}
NSLock *lock = [SandboxBroker sharedPersistentContainerLock];
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
[lock lock];
SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext];
if(token) {
token.path = [folderUrl path];
token.bookmark = bookmark;
[lock unlock];
[SandboxBroker cleanupFolderAccess];
} else {
[lock unlock];
}
}
});
@ -393,52 +370,47 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
+ (void)cleanupFolderAccess {
NSLock *lock = [SandboxBroker sharedPersistentContainerLock];
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path.length" ascending:YES];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"];
request.sortDescriptors = @[sortDescriptor];
NSError *error = nil;
NSMutableArray *resultsCopy = nil;
[lock lock];
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
if(results) {
resultsCopy = [results mutableCopy];
}
[lock unlock];
[pc.viewContext performBlockAndWait:^{
NSError *error = nil;
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
NSMutableArray *resultsCopy = nil;
if(results) {
resultsCopy = [results mutableCopy];
}
BOOL isUpdated = NO;
BOOL isUpdated = NO;
if(resultsCopy && [resultsCopy count]) {
for(NSUInteger i = 0; i < [resultsCopy count] - 1; ++i) {
SandboxToken *token = resultsCopy[i];
NSURL *url = [NSURL fileURLWithPath:token.path];
for(NSUInteger j = i + 1; j < [resultsCopy count];) {
SandboxToken *compareToken = resultsCopy[j];
if([SandboxBroker isPath:[NSURL fileURLWithPath:compareToken.path] aSubdirectoryOf:url]) {
[lock lock];
[pc.viewContext deleteObject:compareToken];
[lock unlock];
isUpdated = YES;
[resultsCopy removeObjectAtIndex:j];
} else {
++j;
if(resultsCopy && [resultsCopy count]) {
for(NSUInteger i = 0; i < [resultsCopy count] - 1; ++i) {
SandboxToken *token = resultsCopy[i];
NSURL *url = [NSURL fileURLWithPath:token.path];
for(NSUInteger j = i + 1; j < [resultsCopy count];) {
SandboxToken *compareToken = resultsCopy[j];
if([SandboxBroker isPath:[NSURL fileURLWithPath:compareToken.path] aSubdirectoryOf:url]) {
[pc.viewContext deleteObject:compareToken];
isUpdated = YES;
[resultsCopy removeObjectAtIndex:j];
} else {
++j;
}
}
}
}
}
if(isUpdated) {
NSError *error;
[lock lock];
[pc.viewContext save:&error];
[lock unlock];
if(error) {
ALog(@"Error saving data: %@", [error localizedDescription]);
if(isUpdated) {
NSError *error;
[pc.viewContext save:&error];
if(error) {
ALog(@"Error saving data: %@", [error localizedDescription]);
}
}
}
}];
}
- (const void *)beginFolderAccess:(NSURL *)fileUrl {