Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
310 views
in Technique[技术] by (71.8m points)

objective c - Can I access the files used for external binary storage in Core Data?

I am working on a media DB application. I have a custom model with data storage and think about rewriting it to Core Data. One use case that is of particular interest to me is movie storage. I store movie files in the DB, but the media framework can only read movies from files (not data).

Core Data offers a handy feature called “external binary storage”, where the entity data is not stored in the DB, but in an external file. This is transparent to the Core Data API user. My question is, can I get the path to the external file, so that I could store a movie using Core Data and then easily load it from its Core Data external file?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Yes, you CAN access the files stored in External Storage. It takes a bit of hacking, and may not be completely kosher with Apple's App Store, but you can do it fairly easily.

Assuming we have an NSManagedObject Subclass 'Media', with a 'data' property that has been set to 'Allows External Storage' in the Core Data Editor:

//  Media.h
//  Examples
//
//  Created by Garrett Shearer on 11/21/12.
//  Copyright (c) 2012 Garrett Shearer. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>


@interface CRMMedia : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSData * data;

@end

And a handy-dandy NSString category:

//  NSString+Parse.m
//  Examples
//
//  Created by Garrett Shearer on 11/21/12.
//  Copyright (c) 2012 Garrett Shearer. All rights reserved.
//

#import "NSString+Parse.h"

@implementation NSString (Parse)

- (NSString*)returnBetweenString:(NSString *)inString1
                       andString:(NSString *)inString2
{
    NSRange substringRange = [self rangeBetweenString:inString1
                                            andString:inString2];
    logger(@"substringRange: (%d, %d)",substringRange.location,substringRange.length);
    logger(@"string (self): %@",self);
    return [self substringWithRange:substringRange];
}


/*
 Return the range of a substring, searching between a starting and ending delimeters
 Original Source: <http://cocoa.karelia.com/Foundation_Categories/NSString/Return_the_range_of.m>
 (See copyright notice at <http://cocoa.karelia.com>)
 */

/*" Find a string between the two given strings with the default options; the delimeter strings are not included in the result.
 "*/

- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
{
    return [self rangeBetweenString:inString1 andString:inString2 options:0];
}

/*" Find a string between the two given strings with the given options inMask; the delimeter strings are not included in the result.  The inMask parameter is the same as is passed to [NSString rangeOfString:options:range:].
 "*/

- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
                       options:(unsigned)inMask
{
    return [self rangeBetweenString:inString1 andString:inString2
                            options:inMask
                              range:NSMakeRange(0,[self length])];
}

/*" Find a string between the two given strings with the given options inMask and the given substring range inSearchRange; the delimeter strings are not included in the result.  The inMask parameter is the same as is passed to [NSString rangeOfString:options:range:].
 "*/

- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
                       options:(unsigned)inMask range:(NSRange)inSearchRange
{
    NSRange result;
    unsigned int foundLocation = inSearchRange.location;    // if no start string, start here
    NSRange stringEnd = NSMakeRange(NSMaxRange(inSearchRange),0); // if no end string, end here
    NSRange endSearchRange;
    if (nil != inString1)
    {
        // Find the range of the list start
        NSRange stringStart = [self rangeOfString:inString1 options:inMask range:inSearchRange];
        if (NSNotFound == stringStart.location)
        {
            return stringStart; // not found
        }
        foundLocation = NSMaxRange(stringStart);
    }
    endSearchRange = NSMakeRange( foundLocation, NSMaxRange(inSearchRange) - foundLocation );
    if (nil != inString2)
    {
        stringEnd = [self rangeOfString:inString2 options:inMask range:endSearchRange];
        if (NSNotFound == stringEnd.location)
        {
            return stringEnd;   // not found
        }
    }
    result = NSMakeRange( foundLocation, stringEnd.location - foundLocation );
    return result;
}


@end

Now its time for some magic.... We are going to create a Category method that parses the filename from the [data description] string. When operating on an instance of the Media subclass, 'data' is actually an 'External Storage Reference', not an NSData object. The filename of the actual data is stored in the description string.

//  Media+ExternalData.m
//  Examples
//
//  Created by Garrett Shearer on 11/21/12.
//  Copyright (c) 2012 Garrett Shearer. All rights reserved.
//

#import "Media+ExternalData.h"
#import "NSString+Parse.h"

@implementation Media (ExternalData)

- (NSString*)filePathString
{
    // Parse out the filename
    NSString *description = [self.data description];
    NSString *filename = [description returnBetweenString:@"path = " andString:@" ;"];
    // Determine the name of the store
    NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
    NSPersistentStore *ps = [psc.persistentStores objectAtIndex:0];
    NSURL *storeURL = [psc URLForPersistentStore:ps];
    NSString *storeNameWithExt = [storeURL lastPathComponent];
    NSString *storeName = [storeNameWithExt stringByDeletingPathExtension];
    // Generate path to the 'external data' directory
    NSString *documentsPath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                                       inDomains:NSUserDomainMask] lastObject] path];
    NSString *pathComponentToExternalStorage = [NSString stringWithFormat:@".%@_SUPPORT/_EXTERNAL_DATA",storeName];
    NSString *pathToExternalStorage = [documentsPath stringByAppendingPathComponent:pathComponentToExternalStorage];
    // Generate path to the media file
    NSString *pathToMedia = [pathToExternalStorage stringByAppendingPathComponent:filename];
    logger(@"pathToMedia: %@",pathToMedia);
    return pathToMedia;
}

- (NSURL*)filePathUrl
{
    NSURL *urlToMedia = [NSURL fileURLWithPath:[self filePathString]];
    return urlToMedia;
}

@end

Now you have an NSString path and a NSURL path to the file. JOY!!!

Something to take note of, I have had issues loading movies with this method... but I also came up with a workaround. It appears that MPMoviePlayer will not access the files in this directory, so the solution was to temporarily copy the file to the documents directory, and play that. Then delete the temp copy when I unload my view:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self copyTmpFile];
}

- (void)viewDidUnload
{
    logger(@"viewDidUnload");
    [_moviePlayer stop];
    [_moviePlayer.view removeFromSuperview];
    [self cleanupTmpFile];
    [super viewDidUnload];
}

- (NSString*)tmpFilePath
{
    NSString *documentsPath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                                       inDomains:NSUserDomainMask] lastObject] path];
    NSString *tmpFilePath = [documentsPath stringByAppendingPathComponent:@"temp_video.m4v"];
    return tmpFilePath;
}

- (void)copyTmpFile
{
    NSString *tmpFilePath = [self tmpFilePath];
    NSFileManager *mgr = [NSFileManager defaultManager];
    NSError *err = nil;
    if([mgr fileExistsAtPath:tmpFilePath])
    {
        [mgr removeItemAtPath:tmpFilePath error:nil];
    }

    [mgr copyItemAtPath:_media.filePathString toPath:tmpFilePath error:&err];
    if(err)
    {
        logger(@"error: %@",err.description);
    }
}

- (void)cleanupTmpFile
{
    NSString *tmpFilePath = [self tmpFilePath];
    NSFileManager *mgr = [NSFileManager defaultManager];
    if([mgr fileExistsAtPath:tmpFilePath])
    {
        [mgr removeItemAtPath:tmpFilePath error:nil];
    }
}

Good Luck!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...