Skip to content

Instantly share code, notes, and snippets.

@curthard89
Created July 26, 2013 11:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curthard89/6088064 to your computer and use it in GitHub Desktop.
Save curthard89/6088064 to your computer and use it in GitHub Desktop.
//
// UpgradeController.m
// Caffeinated
//
// Created by Curtis Hard on 25/07/2013.
//
//
#import "UpgradeController.h"
@implementation UpgradeController
#define UPGRADE_CLASS_PREFIX @"UpgradeVersion"
- (void)dealloc
{
[window release], window = nil;
[context release], context = nil;
[super dealloc];
}
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)aContext
isNew:(BOOL)flag
{
if( ( self = [super init] ) != nil )
{
context = [aContext retain];
isNew = flag;
}
return self;
}
- (void)step
{
[progressIndicator incrementBy:1];
[NSThread sleepForTimeInterval:0.001];
}
- (void)upgrade
{
// we need to check the current DB version
NSEntityDescription * configEntity = [NSEntityDescription entityForName:@"Config"
inManagedObjectContext:context];
// find the version
NSFetchRequest * request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:configEntity];
[request setFetchLimit:1];
NSError * error = nil;
NSArray * results = [context executeFetchRequest:request
error:&error];
NSInteger currentVersion = 0;
Config * config = nil;
if( [results count] != 0 )
{
config = [results objectAtIndex:0];
currentVersion = [[config version] integerValue];
} else
config = [NSEntityDescription insertNewObjectForEntityForName:[configEntity name]
inManagedObjectContext:context];
// find the classes
NSInteger start = currentVersion;
NSInteger counter = start;
NSInteger latestFoundVersion = 0;
NSMutableArray * objs = [[[NSMutableArray alloc] init] autorelease];
while( TRUE )
{
counter++;
// see if there is a class available from the start version upwards...
Class upgradeClass = NSClassFromString( [NSString stringWithFormat:@"%@%ld",UPGRADE_CLASS_PREFIX,counter] );
if( upgradeClass == nil )
break;
// add the upgrade
Upgrade * upgrade = [[[upgradeClass alloc] initWithManagedObjectContext:context
controller:self] autorelease];
[objs addObject:upgrade];
// store the latest version
if( [upgrade versionNumber] > latestFoundVersion )
latestFoundVersion = [upgrade versionNumber];
}
// nothing to do
if( [objs count] == 0 )
return;
// are we new?
if( isNew )
{
[config setVersion:[NSNumber numberWithInteger:latestFoundVersion]];
[context save:&error];
return;
}
// there is an upgrade to perform...
// load the nib and run the modal window
[NSBundle loadNibNamed:@"Upgrade"
owner:self];
[label setStringValue:@"Preparing database upgrade..."];
// set up the indicator
[progressIndicator setUsesThreadedAnimation:YES];
[progressIndicator startAnimation:nil];
[progressIndicator setIndeterminate:YES];
NSPersistentStoreCoordinator * coordinator = [context persistentStoreCoordinator];
// start the async...
dispatch_block_t upgradeBlock = ^{
NSInteger itemsToUpgrade = 0;
for( Upgrade * up in objs )
{
// create the context per request
NSManagedObjectContext * mContext = [[[NSManagedObjectContext alloc] init] autorelease];
[mContext setUndoManager:nil];
[mContext setPersistentStoreCoordinator:coordinator];
// set the context
[up setManagedObjectContext:mContext];
[up prefetch];
itemsToUpgrade += [up numberOfUpgradingObjects];
}
// set the min and max on the progress indicator
[progressIndicator setMinValue:0];
[progressIndicator setMaxValue:itemsToUpgrade];
[progressIndicator setIndeterminate:NO];
// label
dispatch_sync( dispatch_get_main_queue(), ^{
[label setStringValue:@"Upgrading database..."];
});
// run the main
for( Upgrade * up in objs )
{
NSLog(@"Upgrading DB to version %ld",[up versionNumber]);
[up main];
[up save];
[up clean];
[config setVersion:[NSNumber numberWithInteger:[up versionNumber]]];
NSLog(@"Upgraded DB to version %ld",[up versionNumber]);
}
// stop the modal
dispatch_sync( dispatch_get_main_queue(), ^{
[label setStringValue:@"Cleaning up..."];
// clean up...
[progressIndicator setIndeterminate:YES];
[progressIndicator startAnimation:nil];
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
// set the label back
[label setStringValue:@"Database upgrade complete."];
[progressIndicator setIndeterminate:NO];
[progressIndicator startAnimation:nil];
// kill the modal
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// abort the modal
[NSApp abortModal];
});
});
});
};
// dispatch the async block after a delay
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^(void)
{
dispatch_async( dispatch_get_main_queue(), ^{
// set the label to backup
[label setStringValue:@"Creating database backup..."];
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{
// create backup here...
NSURL * storeURL = [[[[context persistentStoreCoordinator] persistentStores] objectAtIndex:0] URL];
if( [[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]] )
{
NSDateFormatter * dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH-mm-ss"];
NSError * error = nil;
NSString * location = [NSString stringWithFormat:@"%@/%@.cafdb",NSUserLibraryPathWithDirectory( @"Backup/Databases" ),[dateFormatter stringFromDate:[NSDate date]]];
[[NSFileManager defaultManager] copyItemAtPath:[storeURL path]
toPath:location
error:&error];
}
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_current_queue(), upgradeBlock );
});
});
});
// stop the run loop and make it modal...basically just block the window
while( [NSApp runModalForWindow:window] == NSRunContinuesResponse )
continue;
// save the objects
[context save:&error];
// now its done we need to store the new version number
[window close], window = nil;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment