SimpleAssetManagerSample/SAMDocument.m
/* |
File: SAMDocument.m |
Abstract: SimpleAssetManager document class. |
Version: 1.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2013 Apple Inc. All Rights Reserved. |
*/ |
#import "SAMDocument.h" |
#import "FCPXMetadataKeys.h" |
#import <AppKit/NSApplication.h> |
#import "SAMAsset.h" |
#import "scriptLog.h" |
#import "MediaAssetHelperKeys.h" |
#import "SAMWindowController.h" |
@implementation SAMDocument |
{ |
// properties of the colllection |
NSMutableArray *collection; // array of SAMAsset |
NSMutableDictionary *URLHash; // asset URL to index |
NSMutableDictionary *UniqueIDHash; // asset UniqueID to index |
// scripting bookkeeping |
id container; /* reference to the object containing this object */ |
NSString* containerProperty; /* name of the cocoa key on container specifying the |
list property where this object is stored */ |
/* storage for our id and name AppleScript properties. */ |
NSString* uniqueID; /* a unique id value for this object */ |
NSDictionary* defaultAssetLocation; // default location for new asset |
// the window controller for the document window |
SAMWindowController *primaryWindowController; |
} |
- (instancetype)init |
{ |
self = [super init]; |
if (self) { |
// create the collection array |
collection = [NSMutableArray arrayWithCapacity:0]; |
URLHash = [NSMutableDictionary dictionaryWithCapacity:0]; |
UniqueIDHash = [NSMutableDictionary dictionaryWithCapacity:0]; |
collectionName = [self displayName]; |
uniqueID = [SAMObject calculateNewUniqueID]; |
} |
return self; |
} |
- (instancetype)initWithContentsOfURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError **)outError |
{ |
self = [super initWithContentsOfURL:url ofType:typeName error:outError]; |
if (self) { |
// create the collection array |
collection = [NSMutableArray arrayWithCapacity:0]; |
URLHash = [NSMutableDictionary dictionaryWithCapacity:0]; |
UniqueIDHash = [NSMutableDictionary dictionaryWithCapacity:0]; |
collectionName = [[url URLByDeletingPathExtension] lastPathComponent]; |
uniqueID = [SAMObject calculateNewUniqueID]; |
} |
return self; |
} |
- (void)makeWindowControllers |
{ |
primaryWindowController = [[SAMWindowController alloc] init]; |
[self addWindowController:primaryWindowController]; |
} |
@synthesize collectionName; |
@synthesize collectionDescription; |
@synthesize collection; |
@synthesize defaultAssetLocation; |
@synthesize primaryWindowController; |
#pragma mark <<< Scripting Support Methods >>> |
/* standard setter and getter methods for the container and |
containerProperty slots. The only thing that's unusual here is that |
we have lumped the setter functions together because we will always |
call them together. */ |
- (id)container { |
SLOG(@" of %@ as %@", self.uniqueID, container); |
return container; |
} |
- (NSString *)containerProperty { |
SLOG(@" return %@ as '%@'", self.uniqueID, containerProperty); |
return containerProperty; |
} |
- (void)setContainer:(id)value andProperty:(NSString *)property { |
SLOG(@" of %@ to %@ and '%@'", self.uniqueID, [value class], property); |
if (container != value) { |
container = value; |
} |
if (containerProperty != property) { |
containerProperty = [property copy]; |
} |
} |
/* standard setter and getter methods for the 'uniqueID' property |
nothing out of the ordinary here. */ |
- (NSString *)uniqueID { |
return uniqueID; |
} |
- (void)setUniqueID:(NSString *)value { |
SLOG(@" of %@ to '%@'", self.uniqueID, value); |
if (uniqueID != value) { |
uniqueID = [value copy]; |
} |
} |
/* standard setter and getter methods for the 'name' property |
nothing out of the ordinary here. */ |
- (NSString *)name { |
SLOG(@" of %@ as '%@'", self.uniqueID, self.collectionName); |
return self.collectionName; |
} |
- (void)setName:(NSString *)value { |
SLOG(@" of %@ to '%@'", self.uniqueID, value); |
if (self.collectionName != value) { |
self.collectionName = [value copy]; |
} |
} |
#pragma mark <<< Asset Access Methods >>> |
- (NSURL*)assetURLAtIndex:(NSUInteger)index |
{ |
SAMAsset *asset = (SAMAsset*)[self.collection objectAtIndex:index]; |
if ( asset == nil ) |
return nil; |
else |
return [asset principalURL]; |
} |
- (NSUInteger)assetIndexForURL:(NSURL*)url |
{ |
NSNumber *indexObject = [URLHash objectForKey:url]; |
if ( indexObject != NULL ) |
return [indexObject integerValue]; |
else |
return -1; |
} |
- (NSUInteger)assetIndexForUniqueID:(NSString*)theID |
{ |
NSNumber *indexObject = [UniqueIDHash objectForKey:theID]; |
if ( indexObject != NULL ) |
return [indexObject integerValue]; |
else |
return -1; |
} |
- (NSUInteger)assetIndexForLocation:(NSDictionary*)locationInfo; |
{ |
NSURL *folderURL = [locationInfo objectForKey:kMediaAssetLocationFolderKey]; |
NSString *baseName = [locationInfo objectForKey:kMediaAssetLocationBasenameKey]; |
if ( folderURL == nil || baseName == nil ) |
return -1; |
NSURL *principalURL = [folderURL URLByAppendingPathComponent:baseName]; |
return [self assetIndexForURL:principalURL]; |
} |
- (NSUInteger)addAssetAtLocation:(NSDictionary*)locationInfo |
content:(BOOL)load |
metadata:(NSDictionary*)metadataset |
dataOptions:(NSDictionary*)options; |
{ |
SAMAsset *theAsset = nil; |
NSUInteger assetIndex = [self assetIndexForLocation:locationInfo]; |
NSNumber *hasMediaObject = [locationInfo objectForKey:kMediaAssetLocationHasMediaKey]; |
NSNumber *hasDescObject = [locationInfo objectForKey:kMediaAssetLocationHasDescriptionKey]; |
BOOL hasMedia = hasMediaObject != nil && [hasMediaObject boolValue]; |
BOOL hasDesc = hasDescObject != nil && [hasDescObject boolValue]; |
NSString* mediaExtension = nil; |
NSString* descExtension = nil; |
if ( mediaExtension == nil && hasMedia ) |
mediaExtension = @"mov"; |
if ( descExtension == nil && hasDesc ) |
descExtension = @"fcpxml"; |
if ( assetIndex == -1 ) { |
assetIndex = [self.assets count]; |
theAsset = [[SAMAsset alloc] init:[locationInfo objectForKey:kMediaAssetLocationBasenameKey] |
at:[locationInfo objectForKey:kMediaAssetLocationFolderKey] |
media:mediaExtension |
desc:descExtension]; |
[theAsset setMetadata:metadataset]; |
[theAsset addMetadata:@"1" forKey:kSAMMetadataKeyManagedAsset]; |
[theAsset setDataOptions:options]; |
[self insertInAssets:theAsset atIndex:(unsigned int)assetIndex]; |
} |
else { |
theAsset = [self.assets objectAtIndex:assetIndex]; |
if ( mediaExtension != nil ) |
[theAsset setMediaExtension:mediaExtension]; |
else if ( hasMediaObject != nil && ! hasMedia ) |
[theAsset setMediaExtension:nil]; // remove the extension if that was an explicit NO |
if ( descExtension != nil ) |
[theAsset setDescExtension:descExtension]; |
else if ( hasDescObject != nil && ! hasDesc ) |
[theAsset setDescExtension:nil]; // remove the extension if that was an explicit NO |
for ( NSString* key in metadataset ) { |
[theAsset addMetadata:[metadataset objectForKey:key] forKey:key]; |
} |
for ( NSString* key in options ) { |
[theAsset setDataOption:[options objectForKey:key] forKey:key]; |
} |
} |
if ( load ) { |
if ( hasMedia ) |
[theAsset loadMedia]; |
if ( hasDesc ) |
[theAsset loadDescription]; |
} |
// update the UI |
[primaryWindowController updateOutlineView:nil]; |
[primaryWindowController updateSelectionDetailFields]; |
return assetIndex; |
} |
- (NSUInteger)addAssetAtURL:(NSURL*)url content:(BOOL)load metadata:(NSDictionary*)metadataset dataOptions:(NSDictionary*)options |
{ |
NSURL *principalURL = [url URLByDeletingPathExtension]; |
NSURL *folderURL = [principalURL URLByDeletingLastPathComponent]; |
NSString *baseName = [principalURL lastPathComponent]; |
NSString *extension = [url pathExtension]; |
NSDictionary *locationInfo = nil; |
if ( [SAMAsset isMediaExtension:extension] ) { |
locationInfo = [NSDictionary dictionaryWithObjectsAndKeys:folderURL, kMediaAssetLocationFolderKey, |
baseName, kMediaAssetLocationBasenameKey, |
[NSNumber numberWithBool:YES], kMediaAssetLocationHasMediaKey, |
nil]; |
} |
else if ( [SAMAsset isDescExtension:extension] ) { |
locationInfo = [NSDictionary dictionaryWithObjectsAndKeys:folderURL, kMediaAssetLocationFolderKey, |
baseName, kMediaAssetLocationBasenameKey, |
[NSNumber numberWithBool:YES], kMediaAssetLocationHasDescriptionKey, |
nil]; |
} |
else { |
// register with basename only |
locationInfo = [NSDictionary dictionaryWithObjectsAndKeys:folderURL, kMediaAssetLocationFolderKey, baseName, kMediaAssetLocationBasenameKey, nil]; |
} |
return [self addAssetAtLocation:locationInfo content:load metadata:metadataset dataOptions:options]; |
} |
- (void)removeAssetAtIndex:(NSUInteger)index |
{ |
[self removeFromAssetsAtIndex:(unsigned int)index]; |
} |
- (void)removeAsset:(SAMAsset*)asset |
{ |
[URLHash removeObjectForKey:[asset principalURL]]; |
[UniqueIDHash removeObjectForKey:asset.uniqueID]; |
[self.collection removeObject:asset]; |
} |
- (NSUInteger)addURL:(NSURL*)url |
content:(BOOL)load |
metadata:(NSDictionary*)metadataset |
dataOptions:(NSDictionary*)dataOptions |
{ |
NSURL* principalURL = nil; |
NSUInteger assetIndex = -1; |
if ( principalURL == nil ) { |
// if the URL does not represent a role file then add the URL as a standalone non-role asset |
assetIndex = [self addAssetAtURL:url content:load metadata:metadataset dataOptions:dataOptions]; |
} |
// update UI |
[primaryWindowController updateOutlineView:nil]; |
[primaryWindowController updateSelectionDetailFields]; |
return assetIndex; |
} |
#pragma mark <<< KVC Asset Accessors >>> |
/* kvc methods for the 'assets' AppleScript element. Here we implement the methods |
necessary for maintaining the list of assets inside of a Bucket. Note the names. |
I our scripting definition file we specified that the 'bucket' class contains an |
element of type 'asset', like so: |
<element type="asset"/> |
Cocoa will use the plural form of the class name, 'assets', when naming the |
property used by AppleScript to access the list of buckets, and we should use |
the property name when naming our methods. So, using the property name, we |
name our methods as follows: |
- (NSArray*) assets; |
-(void) insertInAssets:(id) asset; |
-(void) insertInAssets:(id) asset atIndex:(unsigned)index; |
-(void) removeFromAssetsAtIndex:(unsigned)index; |
*/ |
/* return the entire list of assets */ |
- (NSArray*) assets { |
SLOG(@"returning assets from a bucket %@", self.uniqueID); |
return self.collection; |
} |
/* insert a asset at the beginning of the list */ |
-(void) insertInAssets:(id) asset { |
SAMAsset* assetObject = (SAMAsset*)asset; |
NSNumber *indexObject = [NSNumber numberWithInteger:0]; |
SLOG(@"inserting asset %@ into bucket %@", assetObject.uniqueID, self.uniqueID); |
[asset setContainer:self andProperty:@"assets"]; |
[self.collection insertObject:assetObject atIndex:0]; |
[URLHash setObject:indexObject forKey:[assetObject principalURL]]; |
[UniqueIDHash setObject:indexObject forKey:assetObject.uniqueID]; |
} |
/* insert a asset at some position in the list */ |
-(void) insertInAssets:(id) asset atIndex:(unsigned)index { |
SAMAsset* assetObject = (SAMAsset*)asset; |
NSNumber *indexObject = [NSNumber numberWithInteger:index]; |
SLOG(@"insert asset %@ at index %d into bucket %@", assetObject.uniqueID, index, self.uniqueID); |
[asset setContainer:self andProperty:@"assets"]; |
[self.collection insertObject:assetObject atIndex:index]; |
[URLHash setObject:indexObject forKey:[assetObject principalURL]]; |
[UniqueIDHash setObject:indexObject forKey:assetObject.uniqueID]; |
} |
/* remove a asset from the list */ |
-(void) removeFromAssetsAtIndex:(unsigned)index { |
SLOG(@"removing asset at %d from bucket %@", index, self.uniqueID); |
SAMAsset* assetObject = [self.collection objectAtIndex:index]; |
[URLHash removeObjectForKey:[assetObject principalURL]]; |
[UniqueIDHash removeObjectForKey:assetObject.uniqueID]; |
[self.collection removeObjectAtIndex:index]; |
} |
/* resolve an object specifier into an asset index */ |
- (NSArray *)indicesOfObjectsByEvaluatingObjectSpecifier:(NSScriptObjectSpecifier *)specifier |
{ |
if ([specifier isKindOfClass:[NSUniqueIDSpecifier class]]) { |
NSUniqueIDSpecifier *theSpecifier = (NSUniqueIDSpecifier*)specifier; |
NSString *theID = [theSpecifier uniqueID]; |
NSInteger index = [self assetIndexForUniqueID:theID]; |
if ( index == -1 ) { |
return [NSArray array]; |
} |
else { |
return [NSArray arrayWithObject:[NSNumber numberWithInteger:index]]; |
} |
} |
else |
return nil; |
} |
#pragma mark <<< Serialization / Deserialization >>> |
+ (BOOL)autosavesInPlace |
{ |
return YES; |
} |
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError |
{ |
NSData *dataToWrite = nil; |
if ( [typeName isEqualToString:@"Asset Collection"]) { |
NSMutableArray *urlStrings = [NSMutableArray arrayWithCapacity:0]; |
for ( SAMAsset *asset in self.collection ) { |
NSString *string = [[asset mediaFile] absoluteString]; |
[urlStrings addObject:string]; |
} |
dataToWrite = [[urlStrings componentsJoinedByString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]; |
} |
else { |
if ( outError != NULL ) |
*outError = [NSError errorWithDomain:@"SimpleAssetManaer" code:304 userInfo:nil]; |
} |
return dataToWrite; |
} |
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError |
{ |
if ( [typeName isEqualToString:@"Asset Collection"]) { |
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; |
NSArray *urlStrings = [dataString componentsSeparatedByString:@"\n"]; |
// empty the content of the document |
[self.collection removeAllObjects]; |
// populate the new content |
for ( NSString *urlString in urlStrings ) { |
NSURL *url = [NSURL URLWithString:urlString]; |
SAMAsset *newAsset = [[SAMAsset alloc] init:url]; |
[self insertInAssets:newAsset atIndex:(unsigned int)[self.assets count]]; |
} |
return YES; |
} |
else { |
if ( outError != NULL ) |
*outError = [NSError errorWithDomain:@"SimpleAssetManaer" code:204 userInfo:nil]; |
return NO; |
} |
} |
#pragma mark ======== load and save data methods ========= |
/* |
** -------------------------------------------------------- |
** Standard NSDocument load and save data methods |
** -------------------------------------------------------- |
These methods create an archive of the collection and unarchive an existing archive to reconstitute the collection. |
For more details, see: |
- NSDocument Class Reference |
- Document-Based Applications Overview |
*/ |
- (NSData *)dataRepresentationOfType:(NSString *)aType |
{ |
// create an archive of the collection and its attributes |
NSKeyedArchiver *archiver; |
NSMutableData *data = [NSMutableData data]; |
archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; |
[archiver encodeObject:self.collectionName forKey:@"name"]; |
[archiver encodeObject:self.collectionDescription forKey:@"collectionDescription"]; |
[archiver encodeObject:self.collection forKey:@"collection"]; |
[archiver finishEncoding]; |
return data; |
} |
- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType |
{ |
NSKeyedUnarchiver *unarchiver; |
// extract an archive of the collection and its attributes |
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; |
self.collectionName = [unarchiver decodeObjectForKey:@"name"]; |
self.collectionDescription = [unarchiver decodeObjectForKey:@"collectionDescription"]; |
self.collection = [unarchiver decodeObjectForKey:@"collection"]; |
[unarchiver finishDecoding]; |
return YES; |
} |
@end |
Copyright © 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-12-19