diff --git a/.gitmodules b/.gitmodules index 29843d51402..c6aac2ddfd5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ url=https://github.com/slembcke/Chipmunk2D.git path = external/ObjectAL url = https://github.com/spritebuilder/ObjectAL-for-Cocos2D.git branch = apportable +[submodule "external/SSZipArchive"] + path = external/SSZipArchive + url = https://github.com/spritebuilder/ssziparchive.git diff --git a/UnitTests/CCEffectTests.m b/UnitTests/CCEffectTests.m new file mode 100644 index 00000000000..3f12ecdc02f --- /dev/null +++ b/UnitTests/CCEffectTests.m @@ -0,0 +1,293 @@ +// +// CCEffectTests.m +// cocos2d-tests-ios +// +// Created by Thayer J Andrews on 9/26/14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import "cocos2d.h" +#import "CCEffectUtils.h" + +@interface CCEffectTests : XCTestCase +@end + +@implementation CCEffectTests + +-(void)testNodeAncestry +{ + CCSprite *s1 = [CCSprite spriteWithImageNamed:@"f1.png"]; + CCSprite *s2 = [CCSprite spriteWithImageNamed:@"f1.png"]; + + BOOL commonAncestor = NO; + + CCEffectUtilsTransformFromNodeToNode(s1, s2, &commonAncestor); + XCTAssertFalse(commonAncestor, @"Common ancestor found where there is none."); + + CCEffectUtilsTransformFromNodeToNode(s2, s1, &commonAncestor); + XCTAssertFalse(commonAncestor, @"Common ancestor found where there is none."); +} + +-(void)testSameNode +{ + CCSprite *s1 = [CCSprite spriteWithImageNamed:@"f1.png"]; + + BOOL commonAncestor = NO; + GLKMatrix4 transform; + + transform = CCEffectUtilsTransformFromNodeToNode(s1, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + + XCTAssertEqual(transform.m00, 1.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m01, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m02, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m03, 0.0f, @"Unexpected transform value."); + + XCTAssertEqual(transform.m10, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m11, 1.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m12, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m13, 0.0f, @"Unexpected transform value."); + + XCTAssertEqual(transform.m20, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m21, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m22, 1.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m23, 0.0f, @"Unexpected transform value."); + + XCTAssertEqual(transform.m30, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m31, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m32, 0.0f, @"Unexpected transform value."); + XCTAssertEqual(transform.m33, 1.0f, @"Unexpected transform value."); +} + +-(void)testSiblingTransforms +{ + CCSprite *root = [CCSprite spriteWithImageNamed:@"f1.png"]; + root.name = @"root"; + root.positionType = CCPositionTypePoints; + root.position = ccp(0.0f, 0.0f); + root.anchorPoint = ccp(0.0f, 0.0f); + + CCSprite *s1 = [CCSprite spriteWithImageNamed:@"f1.png"]; + s1.name = @"s1"; + s1.positionType = CCPositionTypePoints; + s1.position = ccp(10.0f, 10.0f); + s1.anchorPoint = ccp(0.0f, 0.0f); + + CCSprite *s2 = [CCSprite spriteWithImageNamed:@"f1.png"]; + s2.name = @"s2"; + s2.positionType = CCPositionTypePoints; + s2.position = ccp(100.0f, 100.0f); + s2.anchorPoint = ccp(0.0f, 0.0f); + + CCSprite *s3 = [CCSprite spriteWithImageNamed:@"f1.png"]; + s3.name = @"s3"; + s3.positionType = CCPositionTypePoints; + s3.position = ccp(1000.0f, 1000.0f); + s3.anchorPoint = ccp(0.0f, 0.0f); + + BOOL commonAncestor = NO; + GLKMatrix4 transform; + + + + // Test this hierarchy: + // + // root + // \ + // s1 + // / \ + // s2 s3 + // + [root addChild:s1]; + [s1 addChild:s2]; + [s1 addChild:s3]; + transform = CCEffectUtilsTransformFromNodeToNode(s1, s2, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -100.0f, @""); + XCTAssertEqual(transform.m31, -100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s2, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 100.0f, @""); + XCTAssertEqual(transform.m31, 100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s2, s3, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -900.0f, @""); + XCTAssertEqual(transform.m31, -900.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s3, s2, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 900.0f, @""); + XCTAssertEqual(transform.m31, 900.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s1, s3, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -1000.0f, @""); + XCTAssertEqual(transform.m31, -1000.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s3, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 1000.0f, @""); + XCTAssertEqual(transform.m31, 1000.0f, @""); + + + // Test this hierarchy: + // + // s1 + // / \ + // s2 s3 + // + [root removeChild:s1]; + transform = CCEffectUtilsTransformFromNodeToNode(s1, s2, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -100.0f, @""); + XCTAssertEqual(transform.m31, -100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s2, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 100.0f, @""); + XCTAssertEqual(transform.m31, 100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s2, s3, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -900.0f, @""); + XCTAssertEqual(transform.m31, -900.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s3, s2, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 900.0f, @""); + XCTAssertEqual(transform.m31, 900.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s1, s3, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -1000.0f, @""); + XCTAssertEqual(transform.m31, -1000.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s3, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 1000.0f, @""); + XCTAssertEqual(transform.m31, 1000.0f, @""); + + [s1 removeChild:s2]; + [s1 removeChild:s3]; +} + +- (void)testAncestorTransforms +{ + CCSprite *root = [CCSprite spriteWithImageNamed:@"f1.png"]; + root.name = @"root"; + root.positionType = CCPositionTypePoints; + root.position = ccp(0.0f, 0.0f); + root.anchorPoint = ccp(0.0f, 0.0f); + + CCSprite *s1 = [CCSprite spriteWithImageNamed:@"f1.png"]; + s1.name = @"s1"; + s1.positionType = CCPositionTypePoints; + s1.position = ccp(10.0f, 10.0f); + s1.anchorPoint = ccp(0.0f, 0.0f); + + CCSprite *s2 = [CCSprite spriteWithImageNamed:@"f1.png"]; + s2.name = @"s2"; + s2.positionType = CCPositionTypePoints; + s2.position = ccp(100.0f, 100.0f); + s2.anchorPoint = ccp(0.0f, 0.0f); + + CCSprite *s3 = [CCSprite spriteWithImageNamed:@"f1.png"]; + s3.name = @"s3"; + s3.positionType = CCPositionTypePoints; + s3.position = ccp(1000.0f, 1000.0f); + s3.anchorPoint = ccp(0.0f, 0.0f); + + BOOL commonAncestor = NO; + GLKMatrix4 transform; + + + // Test this hierarchy: + // + // root + // \ + // s1 + // \ + // s2 + // \ + // s3 + // + [root addChild:s1]; + [s1 addChild:s2]; + [s2 addChild:s3]; + transform = CCEffectUtilsTransformFromNodeToNode(s1, s2, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -100.0f, @""); + XCTAssertEqual(transform.m31, -100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s2, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 100.0f, @""); + XCTAssertEqual(transform.m31, 100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s2, s3, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -1000.0f, @""); + XCTAssertEqual(transform.m31, -1000.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s3, s2, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 1000.0f, @""); + XCTAssertEqual(transform.m31, 1000.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s1, s3, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -1100.0f, @""); + XCTAssertEqual(transform.m31, -1100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s3, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 1100.0f, @""); + XCTAssertEqual(transform.m31, 1100.0f, @""); + + + // Test this hierarchy: + // + // s1 + // \ + // s2 + // \ + // s3 + // + [root removeChild:s1]; + transform = CCEffectUtilsTransformFromNodeToNode(s1, s2, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -100.0f, @""); + XCTAssertEqual(transform.m31, -100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s2, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 100.0f, @""); + XCTAssertEqual(transform.m31, 100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s2, s3, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -1000.0f, @""); + XCTAssertEqual(transform.m31, -1000.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s3, s2, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 1000.0f, @""); + XCTAssertEqual(transform.m31, 1000.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s1, s3, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, -1100.0f, @""); + XCTAssertEqual(transform.m31, -1100.0f, @""); + + transform = CCEffectUtilsTransformFromNodeToNode(s3, s1, &commonAncestor); + XCTAssertTrue(commonAncestor, @"No common ancestor found where there is one."); + XCTAssertEqual(transform.m30, 1100.0f, @""); + XCTAssertEqual(transform.m31, 1100.0f, @""); + [s1 removeChild:s2]; + [s2 removeChild:s3]; +} + +@end diff --git a/UnitTests/CCPackageCocos2dEnablerTests.m b/UnitTests/CCPackageCocos2dEnablerTests.m new file mode 100644 index 00000000000..14122dbdcf2 --- /dev/null +++ b/UnitTests/CCPackageCocos2dEnablerTests.m @@ -0,0 +1,90 @@ +// +// CCPackageCocos2dEnablerTests.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 23.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import "CCPackageCocos2dEnabler.h" +#import "CCPackage.h" +#import "CCFileUtils.h" +#import "CCSprite.h" +#import "CCBReader.h" +#import "AppDelegate.h" +#import "CCPackage_private.h" + +@interface CCPackageCocos2dEnablerTests : XCTestCase + +@property (nonatomic, strong) CCPackage *package; +@property (nonatomic, copy) NSURL *installURL; + +@end + + +@implementation CCPackageCocos2dEnablerTests + +- (void)setUp +{ + [super setUp]; + [(AppController *)[UIApplication sharedApplication].delegate configureCocos2d]; + + self.package = [[CCPackage alloc] initWithName:@"Foo" + resolution:@"phonehd" + os:@"iOS" + remoteURL:[NSURL URLWithString:@"http://foo.fake/Foo-iOS-phonehd.zip"]]; + + NSString *pathToPackage = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Resources-shared/Packages/testpackage-iOS-phonehd_unzipped"]; + self.installURL = [[NSURL fileURLWithPath:pathToPackage] URLByAppendingPathComponent:@"testpackage-iOS-phonehd"]; + _package.installURL = _installURL; +} + + +#pragma mark - Tests + +- (void)testEnablePackage +{ + CCPackageCocos2dEnabler *packageEnabler = [[CCPackageCocos2dEnabler alloc] init]; + [packageEnabler enablePackages:@[_package]]; + + XCTAssertTrue([self isPackageInSearchPath]); + XCTAssertEqual(_package.status, CCPackageStatusInstalledEnabled); + + CCSprite *sprite = [CCSprite spriteWithImageNamed:@"boredSmiley.png"]; + XCTAssertNotNil(sprite); +} + +- (void)testDisablePackage +{ + [self testEnablePackage]; + + CCPackageCocos2dEnabler *packageEnabler = [[CCPackageCocos2dEnabler alloc] init]; + [packageEnabler disablePackages:@[_package]]; + + XCTAssertEqual(_package.status, CCPackageStatusInstalledDisabled); + + XCTAssertFalse([self isPackageInSearchPath]); + + // Can't use [CCSprite spriteWithImageNamed:@"boredSmiley.png"], assertion exception is screwing up test result + CGFloat *scale; + NSString *path = [[CCFileUtils sharedFileUtils] fullPathForFilename:@"boredSmiley.png" contentScale:&scale]; + XCTAssertNil(path); +} + + +#pragma mark - Helper + +- (BOOL)isPackageInSearchPath +{ + for (NSString *aSearchPath in [CCFileUtils sharedFileUtils].searchPath) + { + if ([aSearchPath isEqualToString:_package.installURL.path]) + { + return YES; + } + } + return NO; +} + +@end diff --git a/UnitTests/CCPackageDownloadManagerTests.m b/UnitTests/CCPackageDownloadManagerTests.m new file mode 100644 index 00000000000..b0fea7f9cfc --- /dev/null +++ b/UnitTests/CCPackageDownloadManagerTests.m @@ -0,0 +1,223 @@ +// +// CCPackageDownloadManagerTests.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 23.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import "CCPackageDownloadManager.h" +#import "CCPackageDownloadManagerDelegate.h" +#import "CCPackage.h" +#import "CCDirector.h" +#import "AppDelegate.h" +#import "CCUnitTestAssertions.h" + +@interface CCPackageDownloadManagerTestURLProtocol : NSURLProtocol @end + +@implementation CCPackageDownloadManagerTestURLProtocol + ++ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest +{ + return [theRequest.URL.scheme caseInsensitiveCompare:@"http"] == NSOrderedSame; +} + ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)theRequest +{ + return theRequest; +} + +- (void)startLoading +{ + // just send back what was received in URL as last path component + NSString *payload = [self.request.URL lastPathComponent]; + NSData *data = [payload dataUsingEncoding:NSUTF8StringEncoding]; + + NSHTTPURLResponse *response; + response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL + statusCode:200 + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + + id client = [self client]; + [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [client URLProtocol:self didLoadData:data]; + [client URLProtocolDidFinishLoading:self]; +} + +- (void)stopLoading +{ + // Nothing to do +} + +@end + + +@interface CCPackageDownloadManagerTests : XCTestCase + +@property (nonatomic, strong) CCPackageDownloadManager *downloadManager; +@property (nonatomic) BOOL allDownloadsReturned; +@property (nonatomic, copy) NSString *downloadPath; + +@end + +@implementation CCPackageDownloadManagerTests + +- (void)setUp +{ + [super setUp]; + + [(AppController *)[UIApplication sharedApplication].delegate configureCocos2d]; + [[CCDirector sharedDirector] stopAnimation]; + // Spin the runloop a bit otherwise nondeterministic exceptions are thrown in the CCScheduler. + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeInterval:0.2 sinceDate:[NSDate date]]]; + + [NSURLProtocol registerClass:[CCPackageDownloadManagerTestURLProtocol class]]; + + self.downloadManager = [[CCPackageDownloadManager alloc] init]; + self.allDownloadsReturned = NO; + + self.downloadPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"Downloads"]; + + [self deleteOldDownloads]; + + _downloadManager.downloadPath = _downloadPath; + _downloadManager.delegate = self; +} + +- (void)tearDown +{ + [NSURLProtocol unregisterClass:[CCPackageDownloadManagerTestURLProtocol class]]; + [super tearDown]; +} + +- (void)deleteOldDownloads +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + [fileManager removeItemAtPath:_downloadPath error:nil]; +} + + +#pragma mark - Tests + +- (void)testSetDownloadPath +{ + NSString *newPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"NewDownloads"]; + _downloadManager.downloadPath = newPath; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDir; + + XCTAssert([fileManager fileExistsAtPath:newPath isDirectory:&isDir]); + XCTAssertTrue(isDir); + CCAssertEqualStrings(newPath, _downloadManager.downloadPath); +} + +- (void)testTwoDownloads +{ + NSArray *packages = @[[self completePackageWithName:@"package1"], [self completePackageWithName:@"package2"]]; + + for (CCPackage *aPackage in packages) + { + [_downloadManager enqueuePackageForDownload:aPackage]; + } + + [self waitUntilDelegateReturns]; + + [self assertPackagesDownloadedAndContentsAreAsExpected:packages]; +} + +- (void)testCancelDownload +{ + CCPackage *package1 = [self completePackageWithName:@"package1"]; + + [_downloadManager enqueuePackageForDownload:package1]; + [_downloadManager cancelDownloadOfPackage:package1]; + + // Can't wait for delegate since cancelling won't trigger them + // Just wait a short amount of time and see if nothing has been written to disk + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeInterval:0.5 sinceDate:[NSDate date]]]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + XCTAssertFalse([fileManager fileExistsAtPath:package1.localDownloadURL.path]); +} + +- (void)testPauseAndResumeAllDownloads +{ + NSArray *packages = @[[self completePackageWithName:@"package1"], + [self completePackageWithName:@"package2"], + [self completePackageWithName:@"package3"]]; + + for (CCPackage *aPackage in packages) + { + [_downloadManager enqueuePackageForDownload:aPackage]; + } + + [_downloadManager pauseAllDownloads]; + [_downloadManager resumeAllDownloads]; + + [self waitUntilDelegateReturns]; + + [self assertPackagesDownloadedAndContentsAreAsExpected:packages]; +} + +- (void)testEnqueuePausedPackage +{ + CCPackage *package1 = [self completePackageWithName:@"package1"]; + + [_downloadManager enqueuePackageForDownload:package1]; + [_downloadManager pauseDownloadOfPackage:package1]; + [_downloadManager enqueuePackageForDownload:package1]; + + [self waitUntilDelegateReturns]; + + [self assertPackagesDownloadedAndContentsAreAsExpected:@[package1]]; +} + +- (void)waitUntilDelegateReturns +{ + while (!_allDownloadsReturned) + { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + } +} + + +#pragma mark - Helpers + +- (void)assertPackagesDownloadedAndContentsAreAsExpected:(NSArray *)packages +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + for (CCPackage *aPackage in packages) + { + XCTAssertTrue([fileManager fileExistsAtPath:aPackage.localDownloadURL.path]); + CCAssertEqualStrings(aPackage.name, [NSString stringWithContentsOfFile:aPackage.localDownloadURL.path encoding:NSUTF8StringEncoding error:nil]); + } +} + + +#pragma mark - Fixtures + +- (CCPackage *)completePackageWithName:(NSString *)name +{ + CCPackage *package = [[CCPackage alloc] initWithName:name resolution:@"phonehd" os:@"iOS" remoteURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://package.fake/%@", name]]]; + return package; +} + + +#pragma mark - CCPackageDownloadManagerDelegate + +- (void)downloadFinishedOfPackage:(CCPackage *)package +{ + NSLog(@"%@ finished", package); + self.allDownloadsReturned = _downloadManager.allDownloads.count == 0; +} + +- (void)downloadFailedOfPackage:(CCPackage *)package error:(NSError *)error +{ + NSLog(@"%@ failed", package); + self.allDownloadsReturned = _downloadManager.allDownloads.count == 0; +} + +@end diff --git a/UnitTests/CCPackageDownloadTests.m b/UnitTests/CCPackageDownloadTests.m new file mode 100644 index 00000000000..8b91430afa0 --- /dev/null +++ b/UnitTests/CCPackageDownloadTests.m @@ -0,0 +1,393 @@ +// +// CCPackageDownloadTests.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 23.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import +#import "CCPackageDownload.h" +#import "CCPackage.h" +#import "CCPackageDownloadDelegate.h" +#import "CCDirector.h" +#import "AppDelegate.h" + +static NSUInteger __fileDownloadSize = 0; +static BOOL __support_range_request = YES; + +@interface CCPackageDownloadTestURLProtocol : NSURLProtocol @end + +@implementation CCPackageDownloadTestURLProtocol + ++ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest +{ + return [theRequest.URL.scheme caseInsensitiveCompare:@"http"] == NSOrderedSame; +} + ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)theRequest +{ + return theRequest; +} + +- (NSUInteger)parseRangeHeaderValue:(NSString *)string +{ + if (!string) + { + return 0; + } + + NSError *error; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"bytes=(\\d+)-" + options:NSRegularExpressionCaseInsensitive + error:&error]; + + NSTextCheckingResult *match = [regex firstMatchInString:string + options:NSMatchingAnchored + range:NSMakeRange(0, string.length)]; + + if (match.numberOfRanges == 2) + { + NSString *byteStr = [string substringWithRange:[match rangeAtIndex:1]]; + return (NSUInteger) [byteStr integerValue]; + } + + return 0; +} + +- (void)startLoading +{ + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *fileName = [self.request.URL lastPathComponent]; + NSString *pathToPackage = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"Resources-shared/Packages/%@", fileName] ofType:nil]; + NSDictionary *attribs = [fileManager attributesOfItemAtPath:pathToPackage error:nil]; + NSUInteger fileSize = [attribs[NSFileSize] unsignedIntegerValue]; + + NSUInteger byteRangeStart = 0; + if (__support_range_request) + { + byteRangeStart = [self parseRangeHeaderValue:self.request.allHTTPHeaderFields[@"Range"]]; + headers[@"Accept-Ranges"] = @"bytes"; + headers[@"Content-Range"] = [NSString stringWithFormat:@"bytes %u-%u/%u", byteRangeStart, fileSize - 1, fileSize]; + } + + NSData *data = [[NSData dataWithContentsOfFile:pathToPackage] subdataWithRange:NSMakeRange(byteRangeStart, fileSize - byteRangeStart)]; + + __fileDownloadSize = fileSize; + + NSHTTPURLResponse *response; + if (pathToPackage) + { + headers[@"Content-Length"] = [NSString stringWithFormat:@"%u", [data length]]; + response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL + statusCode:200 + HTTPVersion:@"HTTP/1.1" + headerFields:headers]; + } + else + { + response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL + statusCode:404 + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + } + + id client = [self client]; + [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [client URLProtocol:self didLoadData:data]; + [client URLProtocolDidFinishLoading:self]; +} + +- (void)stopLoading +{ + // Nothing to do +} + +@end + + +@interface CCPackageDownloadTests : XCTestCase + +@property (nonatomic, strong) CCPackageDownload *download; +@property (nonatomic, strong) CCPackage *package; +@property (nonatomic, copy) NSString *downloadPath; +@property (nonatomic) BOOL downloadReturned; +@property (nonatomic) BOOL downloadSuccessful; +@property (nonatomic, strong) NSError *downloadError; +@property (nonatomic, copy) NSURL *localURL; +@property (nonatomic) BOOL shouldOverwriteDownloadedFile; + +@end + +@implementation CCPackageDownloadTests + +- (void)setUp +{ + [super setUp]; + + [(AppController *)[UIApplication sharedApplication].delegate configureCocos2d]; + [[CCDirector sharedDirector] stopAnimation]; + // Spin the runloop a bit otherwise nondeterministic exceptions are thrown in the CCScheduler. + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeInterval:0.2 sinceDate:[NSDate date]]]; + + [NSURLProtocol registerClass:[CCPackageDownloadTestURLProtocol class]]; + + self.downloadPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"Downloads"]; + + [self deleteDownloadFolder]; + [self createDownloadFolder]; + + self.downloadReturned = NO; + self.downloadError = nil; + self.downloadSuccessful = NO; + self.shouldOverwriteDownloadedFile; + + self.package = [[CCPackage alloc] initWithName:@"testpackage" + resolution:@"phonehd" + os:@"iOS" + remoteURL:[NSURL URLWithString:@"http://package.request.fake/testpackage-iOS-phonehd.zip"]]; + + self.localURL = [[NSURL fileURLWithPath:_downloadPath] URLByAppendingPathComponent:@"testdownload.zip"]; + self.download = [[CCPackageDownload alloc] initWithPackage:_package localURL:_localURL]; + _download.delegate = self; +} + +- (void)tearDown +{ + [NSURLProtocol unregisterClass:[CCPackageDownloadTestURLProtocol class]]; + + [[CCDirector sharedDirector] startAnimation]; + + [super tearDown]; +} + +- (void)deleteDownloadFolder +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error; + if (![fileManager removeItemAtPath:_downloadPath error:&error]) + { + NSLog(@"%@",error); + } +} + +- (void)createDownloadFolder +{ + NSError *error; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager createDirectoryAtURL:[NSURL fileURLWithPath:_downloadPath] + withIntermediateDirectories:YES + attributes:nil + error:&error]) + { + NSLog(@"%@", error); + } +} + + +#pragma mark - Tests + +- (void)testDownloadPackage +{ + [_download start]; + XCTAssertEqual(_package.status, CCPackageStatusDownloading); + + [self waitForDelegateToReturn]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSDictionary *attribs = [fileManager attributesOfItemAtPath:_localURL.path error:nil]; + XCTAssertTrue(_downloadSuccessful); + XCTAssertTrue([fileManager fileExistsAtPath:_localURL.path]); + XCTAssertEqual([attribs[NSFileSize] unsignedIntegerValue], __fileDownloadSize); +} + +- (void)testResumeDownloadAKARangeRequest +{ + [self setupPartialDownloadOnDisk]; + + [_download start]; + XCTAssertEqual(_package.status, CCPackageStatusDownloading); + + [self waitForDelegateToReturn]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSDictionary *attribs = [fileManager attributesOfItemAtPath:_localURL.path error:nil]; + XCTAssertTrue(_downloadSuccessful); + XCTAssertTrue([fileManager fileExistsAtPath:_localURL.path]); + XCTAssertEqual([attribs[NSFileSize] unsignedIntegerValue], __fileDownloadSize); +} + +- (void)testDownloadOfExistingFile +{ + self.shouldOverwriteDownloadedFile = NO; + + NSUInteger filesize = [self createDownloadFile]; + + [_download start]; + XCTAssertEqual(_package.status, CCPackageStatusDownloaded); + + [self waitForDelegateToReturn]; + + NSFileManager *fileManager = [NSFileManager defaultManager];; + NSDictionary *attribs = [fileManager attributesOfItemAtPath:_localURL.path error:nil]; + XCTAssertTrue(_downloadSuccessful); + XCTAssertTrue([fileManager fileExistsAtPath:_localURL.path]); + XCTAssertEqual([attribs[NSFileSize] unsignedIntegerValue], filesize); +} + +- (void)testOverwriteExistingDownload +{ + self.shouldOverwriteDownloadedFile = YES; + + [self createDownloadFile]; + + [_download start]; + XCTAssertEqual(_package.status, CCPackageStatusDownloading); + + [self waitForDelegateToReturn]; + + NSFileManager *fileManager = [NSFileManager defaultManager];; + NSDictionary *attribs = [fileManager attributesOfItemAtPath:_localURL.path error:nil]; + XCTAssertTrue(_downloadSuccessful); + XCTAssertTrue([fileManager fileExistsAtPath:_localURL.path]); + XCTAssertEqual([attribs[NSFileSize] unsignedIntegerValue], __fileDownloadSize); +} + +- (void)testDownloadWith404Response +{ + [_package setValue:[NSURL URLWithString:@"http://package.request.fake/DOES_NOT_EXIST.zip"] forKey:NSStringFromSelector(@selector(remoteURL))]; + + [_download start]; + XCTAssertEqual(_package.status, CCPackageStatusDownloading); + + [self waitForDelegateToReturn]; + + XCTAssertFalse(_downloadSuccessful); + XCTAssertNotNil(_downloadError); + XCTAssertEqual(_package.status, CCPackageStatusDownloadFailed); +} + +- (void)testDownloadFolderNotAccessible +{ + // Writing to root level is supposed to fail + [_download setValue:[NSURL fileURLWithPath:@"/test.zip"] forKey:NSStringFromSelector(@selector(localURL))]; + + [_download start]; + + [self waitForDelegateToReturn]; + + XCTAssertFalse(_downloadSuccessful); + XCTAssertNotNil(_downloadError); + XCTAssertEqual(_package.status, CCPackageStatusDownloadFailed); +} + +- (void)testCancelDownload +{ + [_download start]; + [_download cancel]; + + // Can't wait for delegate since cancelling won't trigger them + // Just wait a short amount of time and see if nothing has been written to disk + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeInterval:0.5 sinceDate:[NSDate date]]]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + XCTAssertFalse([fileManager fileExistsAtPath:_download.localURL.path]); +} + +- (void)testPauseDownload +{ + [_download start]; + [_download pause]; + + // Can't wait for delegate since cancelling won't trigger them + // Just wait a short amount of time and see if nothing has been written to disk + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeInterval:0.5 sinceDate:[NSDate date]]]; + + XCTAssertEqual(_package.status, CCPackageStatusDownloadPaused); + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *tempName = [_download performSelector:@selector(createTempName)]; + + BOOL success = [fileManager fileExistsAtPath:[[_localURL.path stringByDeletingLastPathComponent] stringByAppendingPathComponent:tempName]] + || [fileManager fileExistsAtPath:_download.localURL.path]; + + XCTAssertTrue(success, @"Temp file nor downloaded file exists."); +} + +- (void)testResumeDownload +{ + [_download start]; + [_download pause]; + [_download resume]; + + while (!_downloadReturned) + { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSDictionary *attribs = [fileManager attributesOfItemAtPath:_localURL.path error:nil]; + XCTAssertTrue(_downloadSuccessful); + XCTAssertTrue([fileManager fileExistsAtPath:_localURL.path]); + XCTAssertEqual([attribs[NSFileSize] unsignedIntegerValue], __fileDownloadSize); +} + + +#pragma mark - Helper + +- (void)setupPartialDownloadOnDisk +{ + NSString *fileName = [_package.remoteURL lastPathComponent]; + NSString *pathToPackage = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"Resources-shared/Packages/%@", fileName] ofType:nil]; + NSData *data = [[NSData dataWithContentsOfFile:pathToPackage] subdataWithRange:NSMakeRange(0, 5000)]; + NSString *tempName = [_download performSelector:@selector(createTempName)]; + [data writeToFile:[[_localURL.path stringByDeletingLastPathComponent] stringByAppendingPathComponent:tempName] atomically:YES]; +} + +- (void)waitForDelegateToReturn +{ + while (!_downloadReturned) + { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + } +} + +- (NSUInteger)createDownloadFile +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + [fileManager createFileAtPath:_localURL.path + contents:[@"nothing in here, really" dataUsingEncoding:NSUTF8StringEncoding] + attributes:nil]; + + NSDictionary *attribs = [fileManager attributesOfItemAtPath:_localURL.path error:nil]; + return [attribs[NSFileSize] unsignedIntegerValue]; +} + + +#pragma mark - CCPackageDownloadDelegate + +- (void)downloadFinished:(CCPackageDownload *)download +{ + self.downloadReturned = YES; + self.downloadSuccessful = YES; +} + +- (void)downloadFailed:(CCPackageDownload *)download error:(NSError *)error +{ + self.downloadReturned = YES; + self.downloadError = error; + self.downloadSuccessful = NO; +} + +- (BOOL)shouldResumeDownload:(CCPackageDownload *)download +{ + return YES; +} + +- (BOOL)shouldOverwriteDownloadedFile:(CCPackageDownload *)download +{ + return _shouldOverwriteDownloadedFile; +} + +@end diff --git a/UnitTests/CCPackageHelperTests.m b/UnitTests/CCPackageHelperTests.m new file mode 100644 index 00000000000..5d5a1b84cf9 --- /dev/null +++ b/UnitTests/CCPackageHelperTests.m @@ -0,0 +1,49 @@ +// +// CCPackageHelperTests.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 29.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import "CCFileUtils.h" +#import "CCPackageHelper.h" +#import "CCUnitTestAssertions.h" + +@interface CCPackageHelperTests : XCTestCase + +@property (nonatomic, strong) NSMutableArray *searchResolutionsOrderBackup; + +@end + + +@implementation CCPackageHelperTests + + +- (void)testDefaultResolution +{ + // Standard test + [CCFileUtils sharedFileUtils].searchResolutionsOrder = [@[CCFileUtilsSuffixiPhoneHD, CCFileUtilsSuffixiPadHD] mutableCopy]; + NSString *mappedResolution = [CCPackageHelper ccFileUtilsSuffixToResolution:CCFileUtilsSuffixiPhoneHD]; + NSString *defaultResolution = [CCPackageHelper defaultResolution]; + CCAssertEqualStrings(defaultResolution, mappedResolution); + + // Ignore non mappable entriy and pick next + [CCFileUtils sharedFileUtils].searchResolutionsOrder = [@[@"weird_nonsense", CCFileUtilsSuffixiPadHD] mutableCopy]; + NSString *mappedResolution2 = [CCPackageHelper ccFileUtilsSuffixToResolution:CCFileUtilsSuffixiPadHD]; + NSString *defaultResolution2 = [CCPackageHelper defaultResolution]; + CCAssertEqualStrings(defaultResolution2, mappedResolution2); + + // Return default since nothing can be mapped + [CCFileUtils sharedFileUtils].searchResolutionsOrder = [@[@"nothing_to_be_mapped"] mutableCopy]; + NSString *defaultResolution3 = [CCPackageHelper defaultResolution]; + CCAssertEqualStrings(defaultResolution3, @"phonehd"); + + // Empty array + [CCFileUtils sharedFileUtils].searchResolutionsOrder = [@[] mutableCopy]; + NSString *defaultResolution4 = [CCPackageHelper defaultResolution]; + CCAssertEqualStrings(defaultResolution4, @"phonehd"); +} + +@end diff --git a/UnitTests/CCPackageInstallerTests.m b/UnitTests/CCPackageInstallerTests.m new file mode 100644 index 00000000000..d67577083af --- /dev/null +++ b/UnitTests/CCPackageInstallerTests.m @@ -0,0 +1,132 @@ +// +// CCPackageInstallerTests.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 23.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import "CCPackage.h" +#import "CCPackageInstaller.h" +#import "CCPackageConstants.h" +#import "CCPackage_private.h" + + +@interface CCPackageInstallerTests : XCTestCase + +@property (nonatomic, strong) CCPackage *package; +@property (nonatomic, copy) NSString *installPath; +@property (nonatomic, strong) CCPackageInstaller *installer; + +@end + + +@implementation CCPackageInstallerTests + +- (void)setUp +{ + [super setUp]; + + self.package = [[CCPackage alloc] initWithName:@"Test" + resolution:@"phonehd" + os:@"iOS" + remoteURL:[NSURL URLWithString:@"http://test.foo"]]; + + NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; + self.installPath = [cachesPath stringByAppendingPathComponent:@"tests.Packages"]; + + self.installer = [[CCPackageInstaller alloc] initWithPackage:_package installPath:_installPath]; + + + [self deleteInstallData]; + + [self createPackageInstallFolder]; +} + +- (void)createPackageInstallFolder +{ + NSError *error; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager createDirectoryAtURL:[NSURL fileURLWithPath:_installPath] + withIntermediateDirectories:YES + attributes:nil + error:&error]) + { + NSLog(@"%@", error); + } +} + +- (void)tearDown +{ + [self deleteInstallData]; + + [super tearDown]; +} + +- (void)deleteInstallData +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSError *error; + if (![fileManager removeItemAtPath:_installPath error:&error]) + { + // NSLog(@"%@", error); + } +} + + +#pragma mark - Tests + +- (void)testInstallWithoutEnablingPackage +{ + [self setupInstallablePackage]; + + NSError *error; + BOOL success = [_installer installWithError:&error]; + XCTAssertTrue(success, @"Installation was unsuccessful: %@", error); + XCTAssertEqual(_package.status, CCPackageStatusInstalledDisabled); +} + +- (void)testInstallFailingUnzippedPackageDoesNotExist +{ + NSString *pathToPackage = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Resources-shared/Packages/DOES_NOT_EXIST"]; + _package.unzipURL = [NSURL fileURLWithPath:pathToPackage]; + + NSError *error; + BOOL success = [_installer installWithError:&error]; + XCTAssertFalse(success, @"Installation was successful: %@", error); + XCTAssertEqual(_package.status, CCPackageStatusInstallationFailed); + XCTAssertEqual(error.code, PACKAGE_ERROR_INSTALL_UNZIPPED_PACKAGE_NOT_FOUND); +} + +- (void)testInstallFailingPackageAlreadyExists +{ + [self setupInstallablePackage]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + [fileManager createDirectoryAtPath:[_installPath stringByAppendingPathComponent:@"testpackage-iOS-phonehd"] + withIntermediateDirectories:YES + attributes:nil + error:nil]; + + NSError *error; + BOOL success = [_installer installWithError:&error]; + XCTAssertFalse(success, @"Installation was successful: %@", error); + XCTAssertEqual(_package.status, CCPackageStatusInstallationFailed); + XCTAssertEqual(error.code, PACKAGE_ERROR_INSTALL_COULD_NOT_MOVE_PACKAGE_TO_INSTALL_FOLDER); +} + + +#pragma mark - Helper + +- (void)setupInstallablePackage +{ + NSString *pathToPackage = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Resources-shared/Packages/testpackage-iOS-phonehd_unzipped"]; + + _package.unzipURL = [NSURL fileURLWithPath:pathToPackage]; + _package.folderName = @"testpackage-iOS-phonehd"; + _package.enableOnDownload = NO; +} + +@end diff --git a/UnitTests/CCPackageManagerTests.m b/UnitTests/CCPackageManagerTests.m new file mode 100644 index 00000000000..163ef749ea8 --- /dev/null +++ b/UnitTests/CCPackageManagerTests.m @@ -0,0 +1,44 @@ +// +// CCPackageManagerTests.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 23.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import "CCPackageManager.h" +#import "CCPackage.h" +#import "CCFileUtils.h" + +@interface CCPackageManagerTests : XCTestCase + +@property (nonatomic, strong) CCPackageManager *packageManager; + +@end + +@implementation CCPackageManagerTests + +- (void)setUp +{ + [super setUp]; + self.packageManager = [[CCPackageManager alloc] init]; +} + +- (void)testPackageWithName +{ + [CCFileUtils sharedFileUtils].searchResolutionsOrder = [@[CCFileUtilsSuffixiPadHD] mutableCopy]; + + CCPackage *aPackage = [[CCPackage alloc] initWithName:@"foo" + resolution:@"tablethd" // See note above + os:@"iOS" + remoteURL:[NSURL URLWithString:@"http://foo.fake"]]; + + [_packageManager addPackage:aPackage]; + + CCPackage *result = [_packageManager packageWithName:@"foo"]; + + XCTAssertEqual(aPackage, result); +} + +@end diff --git a/UnitTests/CCPackageTests.m b/UnitTests/CCPackageTests.m new file mode 100644 index 00000000000..d0d84a63fe0 --- /dev/null +++ b/UnitTests/CCPackageTests.m @@ -0,0 +1,105 @@ +// +// CCPackageTests.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 23.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import "CCPackage.h" +#import "CCPackage_private.h" +#import "CCUnitTestAssertions.h" + +@interface CCPackageTests : XCTestCase + +@end + + +@implementation CCPackageTests + +- (void)testInitializer +{ + CCPackage *package = [[CCPackage alloc] initWithName:@"DLC" + resolution:@"tablethd" + os:@"iOS" + remoteURL:[NSURL URLWithString:@"http://foo.fake"]]; + + CCAssertEqualStrings(package.name, @"DLC"); + CCAssertEqualStrings(package.resolution, @"tablethd"); + CCAssertEqualStrings(package.os, @"iOS"); + XCTAssertEqualObjects(package.remoteURL, [NSURL URLWithString:@"http://foo.fake"]); + XCTAssertEqual(package.status, CCPackageStatusInitial); +} + + +#pragma mark - Tests + +- (void)testStandardIdentifier +{ + CCPackage *package = [[CCPackage alloc] initWithName:@"DLC" + resolution:@"tablethd" + os:@"iOS" + remoteURL:[NSURL URLWithString:@"http://foo.fake"]]; + + CCAssertEqualStrings([package standardIdentifier], @"DLC-iOS-tablethd"); +} + +- (void)testInitWithDictionary +{ + NSDictionary *dictionary = @{ + @"name" : @"DLC", + @"resolution" : @"tablethd", + @"os" : @"iOS", + @"remoteURL" : @"http://foo.fake", + @"installURL" : @"/Library/Caches/Packages", + @"status" : @(CCPackageStatusInstalledDisabled), + @"localDownloadURL" : @"/downloadfolder/baa.zip", + @"localUnzipURL" : @"/unzupfolder/foo", + @"folderName" : @"somename", + @"enableOnDownload" : @(YES) + }; + + CCPackage *package = [[CCPackage alloc] initWithDictionary:dictionary]; + + CCAssertEqualStrings(package.name, @"DLC"); + CCAssertEqualStrings(package.resolution, @"tablethd"); + CCAssertEqualStrings(package.os, @"iOS"); + XCTAssertEqualObjects(package.remoteURL, [NSURL URLWithString:@"http://foo.fake"]); + XCTAssertEqualObjects(package.installURL, [NSURL fileURLWithPath:@"/Library/Caches/Packages"]); + XCTAssertEqual(package.status, CCPackageStatusInstalledDisabled); + XCTAssertEqualObjects(package.localDownloadURL, [NSURL fileURLWithPath:@"/downloadfolder/baa.zip"]); + XCTAssertEqualObjects(package.unzipURL, [NSURL fileURLWithPath:@"/unzupfolder/foo"]); + CCAssertEqualStrings(package.folderName, @"somename"); + XCTAssertTrue(package.enableOnDownload); +} + +- (void)testToDictionary +{ + CCPackage *package = [[CCPackage alloc] initWithName:@"DLC" + resolution:@"tablethd" + os:@"iOS" + remoteURL:[NSURL URLWithString:@"http://foo.fake"]]; + + package.status = CCPackageStatusInstalledDisabled; + package.installURL = [NSURL fileURLWithPath:@"/Library/Caches/Packages"]; + package.unzipURL = [NSURL fileURLWithPath:@"/unzupfolder/foo"]; + package.folderName = @"somename"; + package.localDownloadURL = [NSURL fileURLWithPath:@"/downloadfolder/baa.zip"]; + package.enableOnDownload = NO; + + NSDictionary *dictionary = [package toDictionary]; + + CCAssertEqualStrings(dictionary[@"name"], @"DLC"); + CCAssertEqualStrings(dictionary[@"resolution"], @"tablethd"); + CCAssertEqualStrings(dictionary[@"os"], @"iOS"); + CCAssertEqualStrings(dictionary[@"remoteURL"], @"http://foo.fake"); + CCAssertEqualStrings(dictionary[@"installURL"], @"/Library/Caches/Packages"); + XCTAssertEqual([dictionary[@"status"] integerValue], CCPackageStatusInstalledDisabled); + CCAssertEqualStrings(dictionary[@"localDownloadURL"], @"/downloadfolder/baa.zip"); + CCAssertEqualStrings(dictionary[@"localUnzipURL"], @"/unzupfolder/foo"); + CCAssertEqualStrings(dictionary[@"folderName"], @"somename"); + XCTAssertFalse([dictionary[@"enableOnDownload"] boolValue]); +} + +@end diff --git a/UnitTests/CCPackageUnzipperTests.m b/UnitTests/CCPackageUnzipperTests.m new file mode 100644 index 00000000000..12e3fdb333a --- /dev/null +++ b/UnitTests/CCPackageUnzipperTests.m @@ -0,0 +1,171 @@ +// +// CCPackageUnzipperTests.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 23.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// + +#import +#import "CCPackageUnzipper.h" +#import "CCPackage.h" +#import "CCPackageConstants.h" +#import "CCPackageUnzipperDelegate.h" +#import "CCUnitTestAssertions.h" +#import "CCPackage_private.h" + +@interface CCPackageUnzipperTests : XCTestCase + +@property (nonatomic, strong) CCPackage *package; +@property (nonatomic, copy) NSString *unzipFolderPath; +@property (nonatomic, strong) NSCondition *condition; +@property (nonatomic) BOOL unzipperReturned; +@property (nonatomic) BOOL unzippingSuccessful; +@property (nonatomic, strong) NSError *unzippingError; + +@end + + +@implementation CCPackageUnzipperTests + +- (void)setUp +{ + [super setUp]; + + [self deleteGeneratedFiles]; + self.unzipperReturned = NO; + self.unzippingError = nil; + self.unzippingSuccessful = NO; + self.unzipFolderPath = [NSTemporaryDirectory() stringByAppendingPathComponent:PACKAGE_REL_UNZIP_FOLDER]; + + [self createUnzipFolder]; + + self.package = [[CCPackage alloc] initWithName:@"Foo" + resolution:@"phonehd" + os:@"iOS" + remoteURL:[NSURL URLWithString:@"http://foo.fake/Foo-iOS-phonehd.zip"]]; + + NSString *pathToZip = [[NSBundle mainBundle] pathForResource:@"Resources-shared/Packages/testpackage-iOS-phonehd" ofType:@"zip"]; + _package.localDownloadURL = [NSURL fileURLWithPath:pathToZip]; + _package.unzipURL = [NSURL fileURLWithPath:[_unzipFolderPath stringByAppendingPathComponent:[_package standardIdentifier]]]; +} + +- (void)createUnzipFolder +{ + NSError *error; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager createDirectoryAtURL:[NSURL fileURLWithPath:_unzipFolderPath] + withIntermediateDirectories:YES + attributes:nil + error:&error]) + { + NSLog(@"%@", error); + } +} + +- (void)tearDown +{ + [self deleteGeneratedFiles]; + + [super tearDown]; +} + +- (void)deleteGeneratedFiles +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + [fileManager removeItemAtPath:_unzipFolderPath error:nil]; +} + + +#pragma mark - Tests + +- (void)testUnzipping +{ + [self unzipUntilDelegateMethodsReturn:nil]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSArray *contents = [fileManager contentsOfDirectoryAtURL:[_package.unzipURL URLByAppendingPathComponent:@"testpackage-iOS-phonehd"] + includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey] + options:NSDirectoryEnumerationSkipsSubdirectoryDescendants + error:nil]; + + XCTAssertEqual(contents.count, 4); + XCTAssertTrue(_unzippingSuccessful); +} + +- (void)testUnzippingOfNonExistingArchive +{ + _package.localDownloadURL = [NSURL fileURLWithPath:@"/foo.zip"]; + + [self unzipUntilDelegateMethodsReturn:nil]; + + XCTAssertFalse(_unzippingSuccessful); +} + +// This test is not working as SSZipArchive will return success although file operations fail +// This requires some modifications of SSZipArchive +/* +- (void)testUnzippingOfInaccessibleUnzipFolder +{ + _installData.unzipURL = [NSURL fileURLWithPath:@"/temp/surelynotexistingfolder"]; + + [self unzipUntilDelegateMethodsReturn:]; + + XCTAssertFalse(_unzippingSuccessful); +} +*/ + +- (void)testUnzipOfPasswordProtectedPackage +{ + NSString *pathToZip = [[NSBundle mainBundle] pathForResource:@"Resources-shared/Packages/password-iOS-phone" ofType:@"zip"]; + _package.localDownloadURL = [NSURL fileURLWithPath:pathToZip]; + + [self unzipUntilDelegateMethodsReturn:@"foobar"]; + + NSString *secretFilePath = [_package.unzipURL.path stringByAppendingPathComponent:@"password-iOS-phone/secret.txt"]; + NSError *error; + NSString *contentsOfSecretFile = [NSString stringWithContentsOfFile:secretFilePath encoding:NSUTF8StringEncoding error:&error]; + XCTAssertNil(error); + CCAssertEqualStrings(contentsOfSecretFile, @"unzipping successful"); +} + + +#pragma mark - CCPackageUnzipperDelegate + +- (void)unzipFinished:(CCPackageUnzipper *)packageUnzipper +{ + self.unzippingSuccessful = YES; + self.unzipperReturned = YES; + [_condition signal]; +} + +- (void)unzipFailed:(CCPackageUnzipper *)packageUnzipper error:(NSError *)error +{ + self.unzippingSuccessful = NO; + self.unzippingError = error; + + self.unzipperReturned = YES; + [_condition signal]; +} + + +#pragma mark - Helper + +- (void)unzipUntilDelegateMethodsReturn:(NSString *)password +{ + self.condition = [[NSCondition alloc] init]; + [_condition lock]; + + CCPackageUnzipper *unzipper = [[CCPackageUnzipper alloc] initWithPackage:_package]; + unzipper.password = password; + unzipper.delegate = self; + [unzipper unpackPackage]; + + while(!_unzipperReturned) + { + [_condition wait]; + } + [_condition unlock]; +} + +@end diff --git a/UnitTests/CCPhysicsTests.m b/UnitTests/CCPhysicsTests.m index fbd5c501681..814d18ed36c 100644 --- a/UnitTests/CCPhysicsTests.m +++ b/UnitTests/CCPhysicsTests.m @@ -11,6 +11,7 @@ #import "CCPhysics+ObjectiveChipmunk.h" #import "CCDirector_Private.h" +#import "AppDelegate.h" @interface CCScheduler(Test) @@ -25,6 +26,14 @@ @interface CCPhysicsTests : XCTestCase @implementation CCPhysicsTests +- (void)setUp +{ + [super setUp]; + + [(AppController *)[UIApplication sharedApplication].delegate configureCocos2d]; + [[CCDirector sharedDirector] startAnimation]; +} + static void TestBasicSequenceHelper(id self, CCPhysicsNode *physicsNode, CCNode *parent, CCNode *node, CCPhysicsBody *body) { diff --git a/UnitTests/CCTextureTests.m b/UnitTests/CCTextureTests.m index 3ef58505e46..8683fb021f9 100644 --- a/UnitTests/CCTextureTests.m +++ b/UnitTests/CCTextureTests.m @@ -2,12 +2,22 @@ #import "cocos2d.h" #import "CCTextureCache.h" +#import "CCBReader.h" +#import "AppDelegate.h" @interface CCTextureTests : XCTestCase @end @implementation CCTextureTests + +- (void)setUp +{ + [super setUp]; + + [(AppController *)[UIApplication sharedApplication].delegate configureCocos2d]; +} + -(void)testTextureCache { __weak CCTexture *textures[4]; diff --git a/UnitTests/CCUnitTestAssertions.h b/UnitTests/CCUnitTestAssertions.h new file mode 100644 index 00000000000..435e63f044a --- /dev/null +++ b/UnitTests/CCUnitTestAssertions.h @@ -0,0 +1,15 @@ +#define CCAssertEqualStrings(a1, a2, format...) \ +do { \ + @try { \ + id a1value = (a1); \ + id a2value = (a2); \ + if (a1value == a2value) continue; \ + if ([a1value isKindOfClass:[NSString class]] && \ + [a2value isKindOfClass:[NSString class]] && \ + [a1value compare:a2value options:0] == NSOrderedSame) continue; \ + _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_EqualObjects, 0, @#a1, @#a2, a1value, a2value),format); \ + } \ + @catch (id exception) { \ + _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_EqualObjects, 1, @#a1, @#a2, [exception reason]),format); \ + } \ +} while(0) \ No newline at end of file diff --git a/cocos2d-ios.xcodeproj/project.pbxproj b/cocos2d-ios.xcodeproj/project.pbxproj index d351d9831f2..85aa3345cde 100644 --- a/cocos2d-ios.xcodeproj/project.pbxproj +++ b/cocos2d-ios.xcodeproj/project.pbxproj @@ -114,6 +114,28 @@ 5BF3268F195F8E0300D9A51A /* CCGLView.java in Sources */ = {isa = PBXBuildFile; fileRef = 5BF3268D195F8E0300D9A51A /* CCGLView.java */; }; 5BF32698195F947800D9A51A /* CCActivity.h in Headers */ = {isa = PBXBuildFile; fileRef = 5BF32696195F947800D9A51A /* CCActivity.h */; }; 5BF32699195F947800D9A51A /* CCActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF32697195F947800D9A51A /* CCActivity.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc -O0"; }; }; + 83409E4919D5BADC004B7EB9 /* libSSZipArchive.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 83409E3619D5B791004B7EB9 /* libSSZipArchive.a */; }; + 83B7DB7B19D32FD800B9A452 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 83E1A88D19C8C19D000A3BCA /* libz.dylib */; }; + 83E1A86219C8ACA0000A3BCA /* CCPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A85A19C8ACA0000A3BCA /* CCPackage.h */; }; + 83E1A86319C8ACA0000A3BCA /* CCPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A85B19C8ACA0000A3BCA /* CCPackage.m */; }; + 83E1A86419C8ACA0000A3BCA /* CCPackageConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A85C19C8ACA0000A3BCA /* CCPackageConstants.h */; }; + 83E1A86519C8ACA0000A3BCA /* CCPackageConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A85D19C8ACA0000A3BCA /* CCPackageConstants.m */; }; + 83E1A86619C8ACA0000A3BCA /* CCPackageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A85E19C8ACA0000A3BCA /* CCPackageManager.h */; }; + 83E1A86719C8ACA0000A3BCA /* CCPackageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A85F19C8ACA0000A3BCA /* CCPackageManager.m */; }; + 83E1A86819C8ACA0000A3BCA /* CCPackageManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A86019C8ACA0000A3BCA /* CCPackageManagerDelegate.h */; }; + 83E1A86919C8ACA0000A3BCA /* CCPackageTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A86119C8ACA0000A3BCA /* CCPackageTypes.h */; }; + 83E1A86D19C8ACAF000A3BCA /* CCPackageUnzipper.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A86A19C8ACAF000A3BCA /* CCPackageUnzipper.h */; }; + 83E1A86E19C8ACAF000A3BCA /* CCPackageUnzipper.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A86B19C8ACAF000A3BCA /* CCPackageUnzipper.m */; }; + 83E1A86F19C8ACAF000A3BCA /* CCPackageUnzipperDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A86C19C8ACAF000A3BCA /* CCPackageUnzipperDelegate.h */; }; + 83E1A87619C8ACC1000A3BCA /* CCPackageDownload.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A87019C8ACC1000A3BCA /* CCPackageDownload.h */; }; + 83E1A87719C8ACC1000A3BCA /* CCPackageDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A87119C8ACC1000A3BCA /* CCPackageDownload.m */; }; + 83E1A87919C8ACC1000A3BCA /* CCPackageDownloadManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A87319C8ACC1000A3BCA /* CCPackageDownloadManager.h */; }; + 83E1A87A19C8ACC1000A3BCA /* CCPackageDownloadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A87419C8ACC1000A3BCA /* CCPackageDownloadManager.m */; }; + 83E1A87B19C8ACC1000A3BCA /* CCPackageDownloadManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A87519C8ACC1000A3BCA /* CCPackageDownloadManagerDelegate.h */; }; + 83E1A88619C8ACDC000A3BCA /* CCPackageCocos2dEnabler.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A87E19C8ACDC000A3BCA /* CCPackageCocos2dEnabler.h */; }; + 83E1A88719C8ACDC000A3BCA /* CCPackageCocos2dEnabler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A87F19C8ACDC000A3BCA /* CCPackageCocos2dEnabler.m */; }; + 83E1A88A19C8ACDC000A3BCA /* CCPackageInstaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A88219C8ACDC000A3BCA /* CCPackageInstaller.h */; }; + 83E1A88B19C8ACDC000A3BCA /* CCPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A88319C8ACDC000A3BCA /* CCPackageInstaller.m */; }; 9D85671D191B018200573093 /* CCEffectBrightness.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D85671B191B018200573093 /* CCEffectBrightness.h */; }; 9D85671E191B018200573093 /* CCEffectBrightness.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D85671C191B018200573093 /* CCEffectBrightness.m */; }; 9D856721191B019900573093 /* CCEffectContrast.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D85671F191B019900573093 /* CCEffectContrast.h */; }; @@ -197,6 +219,28 @@ B7EE69E31819E75700B983FE /* CCLayoutBox.m in Sources */ = {isa = PBXBuildFile; fileRef = B7EE69DF1819E75700B983FE /* CCLayoutBox.m */; }; B7EE6A1E181AE34600B983FE /* CCSlider.h in Headers */ = {isa = PBXBuildFile; fileRef = B7EE6A1C181AE34600B983FE /* CCSlider.h */; }; B7EE6A1F181AE34600B983FE /* CCSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = B7EE6A1D181AE34600B983FE /* CCSlider.m */; }; + BC9F4E8819DB632400B25F01 /* CCPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A85B19C8ACA0000A3BCA /* CCPackage.m */; }; + BC9F4E8E19DB632800B25F01 /* CCPackageConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A85D19C8ACA0000A3BCA /* CCPackageConstants.m */; }; + BC9F4E8F19DB632B00B25F01 /* CCPackageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A85F19C8ACA0000A3BCA /* CCPackageManager.m */; }; + BC9F4E9019DB633100B25F01 /* CCPackageHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E525F9EA4B5DD37D0418869E /* CCPackageHelper.m */; }; + BC9F4E9119DB633500B25F01 /* CCPackageDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A87119C8ACC1000A3BCA /* CCPackageDownload.m */; }; + BC9F4E9219DB633B00B25F01 /* CCPackageDownloadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A87419C8ACC1000A3BCA /* CCPackageDownloadManager.m */; }; + BC9F4E9319DB633F00B25F01 /* CCPackageUnzipper.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A86B19C8ACAF000A3BCA /* CCPackageUnzipper.m */; }; + BC9F4E9419DB635000B25F01 /* CCPackageCocos2dEnabler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A87F19C8ACDC000A3BCA /* CCPackageCocos2dEnabler.m */; }; + BC9F4E9519DB635200B25F01 /* CCPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E1A88319C8ACDC000A3BCA /* CCPackageInstaller.m */; }; + BC9F4E9619DB643C00B25F01 /* CCPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A85A19C8ACA0000A3BCA /* CCPackage.h */; }; + BC9F4E9719DB643F00B25F01 /* CCPackageConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A85C19C8ACA0000A3BCA /* CCPackageConstants.h */; }; + BC9F4E9819DB644600B25F01 /* CCPackageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A85E19C8ACA0000A3BCA /* CCPackageManager.h */; }; + BC9F4E9919DB644900B25F01 /* CCPackageManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A86019C8ACA0000A3BCA /* CCPackageManagerDelegate.h */; }; + BC9F4E9A19DB644B00B25F01 /* CCPackageTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A86119C8ACA0000A3BCA /* CCPackageTypes.h */; }; + BC9F4E9B19DB644E00B25F01 /* CCPackageDownload.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A87019C8ACC1000A3BCA /* CCPackageDownload.h */; }; + BC9F4E9C19DB646000B25F01 /* CCPackageDownloadManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A87319C8ACC1000A3BCA /* CCPackageDownloadManager.h */; }; + BC9F4E9D19DB646300B25F01 /* CCPackageDownloadManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A87519C8ACC1000A3BCA /* CCPackageDownloadManagerDelegate.h */; }; + BC9F4E9E19DB649800B25F01 /* CCPackageCocos2dEnabler.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A87E19C8ACDC000A3BCA /* CCPackageCocos2dEnabler.h */; }; + BC9F4E9F19DB649C00B25F01 /* CCPackageInstaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E1A88219C8ACDC000A3BCA /* CCPackageInstaller.h */; }; + BC9F4EA019DB649E00B25F01 /* CCPackageHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = E525FF8C7C018BF691F36044 /* CCPackageHelper.h */; }; + BC9F4EA119DB64A200B25F01 /* CCPackage_private.h in Headers */ = {isa = PBXBuildFile; fileRef = E525FE4C98BCF1228ECC9623 /* CCPackage_private.h */; }; + BC9F4EA219DB64BA00B25F01 /* libSSZipArchive.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 83409E3619D5B791004B7EB9 /* libSSZipArchive.a */; }; D23C5CB4194BC108007CA669 /* CCTouchIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = D23C5CB2194BC108007CA669 /* CCTouchIOS.h */; }; D23C5CB5194BC108007CA669 /* CCTouchIOS.m in Sources */ = {isa = PBXBuildFile; fileRef = D23C5CB3194BC108007CA669 /* CCTouchIOS.m */; }; D23C5CBC194BC500007CA669 /* CCTouchAndroid.h in Headers */ = {isa = PBXBuildFile; fileRef = D23C5CBA194BC500007CA669 /* CCTouchAndroid.h */; }; @@ -582,11 +626,49 @@ E0F924711224140400EF2362 /* CCTexturePVR.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F9246F1224140400EF2362 /* CCTexturePVR.h */; }; E0F924721224140400EF2362 /* CCTexturePVR.m in Sources */ = {isa = PBXBuildFile; fileRef = E0F924701224140400EF2362 /* CCTexturePVR.m */; }; E0F92AC41224559800EF2362 /* CCNS.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F92AC31224559800EF2362 /* CCNS.h */; }; + E525F0F74C7C69C4371E20EE /* CCPackageHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E525F9EA4B5DD37D0418869E /* CCPackageHelper.m */; }; + E525F3A2BBCBB446B67D8260 /* CCPackage_private.h in Headers */ = {isa = PBXBuildFile; fileRef = E525FE4C98BCF1228ECC9623 /* CCPackage_private.h */; }; + E525FCC2CF72D3667DE4D71B /* CCPackageHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = E525FF8C7C018BF691F36044 /* CCPackageHelper.h */; }; FC39962219C3BBCF00C93E5E /* libObjectAL (Android).a in Frameworks */ = {isa = PBXBuildFile; fileRef = FC39961F19C3B92F00C93E5E /* libObjectAL (Android).a */; }; FC64014019C79716003E595A /* libObjectAL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FC39961B19C3B92F00C93E5E /* libObjectAL.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 83409E3119D5B791004B7EB9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 21CC41A517DB7D1300201DDC; + remoteInfo = SSZipArchiveExampleApp; + }; + 83409E3319D5B791004B7EB9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 21CC41C017DB7D1300201DDC; + remoteInfo = SSZipArchiveTests; + }; + 83409E3519D5B791004B7EB9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83409DCE19D5B65B004B7EB9; + remoteInfo = SSZipArchive; + }; + 83409E3719D5B791004B7EB9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83409DDB19D5B65C004B7EB9; + remoteInfo = SSZipArchiveTests; + }; + 83409E4C19D5E8A1004B7EB9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 83409DCD19D5B65B004B7EB9; + remoteInfo = SSZipArchive; + }; B759E52C1880C66900E8166C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B759E5281880C66900E8166C /* Chipmunk7-ios.xcodeproj */; @@ -601,6 +683,13 @@ remoteGlobalIDString = D309B21217EFE2EF00AA52C8; remoteInfo = "ObjectiveChipmunk-iPhone"; }; + BC9F4E7919DB5B7300B25F01 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 83409DCD19D5B65B004B7EB9; + remoteInfo = SSZipArchive; + }; D28B2E7D19CBA89A00DC6E08 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = FC39961319C3B92F00C93E5E /* ObjectAL.xcodeproj */; @@ -774,6 +863,29 @@ 5BF3268D195F8E0300D9A51A /* CCGLView.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; path = CCGLView.java; sourceTree = ""; }; 5BF32696195F947800D9A51A /* CCActivity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CCActivity.h; path = Android/CCActivity.h; sourceTree = ""; }; 5BF32697195F947800D9A51A /* CCActivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CCActivity.m; path = Android/CCActivity.m; sourceTree = ""; }; + 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SSZipArchive.xcodeproj; path = external/SSZipArchive/SSZipArchive.xcodeproj; sourceTree = ""; }; + 83E1A85A19C8ACA0000A3BCA /* CCPackage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackage.h; sourceTree = ""; }; + 83E1A85B19C8ACA0000A3BCA /* CCPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackage.m; sourceTree = ""; }; + 83E1A85C19C8ACA0000A3BCA /* CCPackageConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageConstants.h; sourceTree = ""; }; + 83E1A85D19C8ACA0000A3BCA /* CCPackageConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageConstants.m; sourceTree = ""; }; + 83E1A85E19C8ACA0000A3BCA /* CCPackageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageManager.h; sourceTree = ""; }; + 83E1A85F19C8ACA0000A3BCA /* CCPackageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageManager.m; sourceTree = ""; }; + 83E1A86019C8ACA0000A3BCA /* CCPackageManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageManagerDelegate.h; sourceTree = ""; }; + 83E1A86119C8ACA0000A3BCA /* CCPackageTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageTypes.h; sourceTree = ""; }; + 83E1A86A19C8ACAF000A3BCA /* CCPackageUnzipper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageUnzipper.h; sourceTree = ""; }; + 83E1A86B19C8ACAF000A3BCA /* CCPackageUnzipper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageUnzipper.m; sourceTree = ""; }; + 83E1A86C19C8ACAF000A3BCA /* CCPackageUnzipperDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageUnzipperDelegate.h; sourceTree = ""; }; + 83E1A87019C8ACC1000A3BCA /* CCPackageDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageDownload.h; sourceTree = ""; }; + 83E1A87119C8ACC1000A3BCA /* CCPackageDownload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageDownload.m; sourceTree = ""; }; + 83E1A87219C8ACC1000A3BCA /* CCPackageDownloadDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageDownloadDelegate.h; sourceTree = ""; }; + 83E1A87319C8ACC1000A3BCA /* CCPackageDownloadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageDownloadManager.h; sourceTree = ""; }; + 83E1A87419C8ACC1000A3BCA /* CCPackageDownloadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageDownloadManager.m; sourceTree = ""; }; + 83E1A87519C8ACC1000A3BCA /* CCPackageDownloadManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageDownloadManagerDelegate.h; sourceTree = ""; }; + 83E1A87E19C8ACDC000A3BCA /* CCPackageCocos2dEnabler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageCocos2dEnabler.h; sourceTree = ""; }; + 83E1A87F19C8ACDC000A3BCA /* CCPackageCocos2dEnabler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageCocos2dEnabler.m; sourceTree = ""; }; + 83E1A88219C8ACDC000A3BCA /* CCPackageInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageInstaller.h; sourceTree = ""; }; + 83E1A88319C8ACDC000A3BCA /* CCPackageInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageInstaller.m; sourceTree = ""; }; + 83E1A88D19C8C19D000A3BCA /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 92FF6C7318F33A2A005B7139 /* CCActionManager_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CCActionManager_Private.h; sourceTree = ""; }; 9D85671A191AE2CC00573093 /* CCEffectStack_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CCEffectStack_Private.h; sourceTree = ""; }; 9D85671B191B018200573093 /* CCEffectBrightness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCEffectBrightness.h; sourceTree = ""; }; @@ -995,6 +1107,9 @@ E0F9246F1224140400EF2362 /* CCTexturePVR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCTexturePVR.h; sourceTree = ""; }; E0F924701224140400EF2362 /* CCTexturePVR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCTexturePVR.m; sourceTree = ""; }; E0F92AC31224559800EF2362 /* CCNS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCNS.h; sourceTree = ""; }; + E525F9EA4B5DD37D0418869E /* CCPackageHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageHelper.m; sourceTree = ""; }; + E525FE4C98BCF1228ECC9623 /* CCPackage_private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackage_private.h; sourceTree = ""; }; + E525FF8C7C018BF691F36044 /* CCPackageHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCPackageHelper.h; sourceTree = ""; }; FC39961319C3B92F00C93E5E /* ObjectAL.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ObjectAL.xcodeproj; path = external/ObjectAL/ObjectAL/ObjectAL.xcodeproj; sourceTree = ""; }; FC55599F1991A26300E29CCE /* libogg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libogg.a; path = "external/ogg/android/ogg/build/Debug-android/libogg.a"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1004,6 +1119,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 83409E4919D5BADC004B7EB9 /* libSSZipArchive.a in Frameworks */, + 83B7DB7B19D32FD800B9A452 /* libz.dylib in Frameworks */, FC64014019C79716003E595A /* libObjectAL.a in Frameworks */, D3903B1A19952ABD003AA81A /* Metal.framework in Frameworks */, D24FAEEC198014B90043E27D /* GLKit.framework in Frameworks */, @@ -1032,6 +1149,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BC9F4EA219DB64BA00B25F01 /* libSSZipArchive.a in Frameworks */, FC39962219C3BBCF00C93E5E /* libObjectAL (Android).a in Frameworks */, D291DCC2195B2FA100278EC0 /* libObjectiveChipmunk-iPhone.a in Frameworks */, D2FEB745194F6C9E00FC0574 /* CoreText.framework in Frameworks */, @@ -1078,6 +1196,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 83E1A88D19C8C19D000A3BCA /* libz.dylib */, FC55599F1991A26300E29CCE /* libogg.a */, D3903B1919952ABD003AA81A /* Metal.framework */, D24FAEEB198014B90043E27D /* GLKit.framework */, @@ -1100,6 +1219,7 @@ 501756090F335CE900624901 /* external */ = { isa = PBXGroup; children = ( + 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */, FC39961319C3B92F00C93E5E /* ObjectAL.xcodeproj */, B759E5281880C66900E8166C /* Chipmunk7-ios.xcodeproj */, ); @@ -1121,6 +1241,7 @@ 506414C30F9C6769007A7B24 /* Layers, Scenes, Transitions Nodes */, A0D7D9D415E2E737000CA0C4 /* Physics */, 50EA8DA011354A6000746D2A /* Misc Nodes */, + 83E1A85619C8AC2A000A3BCA /* Packages */, 50A24E770F3780A2007CAEB0 /* Actions */, 50E1355310ADEA9400C9E7FA /* Textures */, 501CCFA30E99657C00B86F68 /* Support */, @@ -1420,6 +1541,72 @@ path = cocos2d; sourceTree = ""; }; + 83409E2B19D5B791004B7EB9 /* Products */ = { + isa = PBXGroup; + children = ( + 83409E3219D5B791004B7EB9 /* SSZipArchive.app */, + 83409E3419D5B791004B7EB9 /* SSZipArchiveTests.xctest */, + 83409E3619D5B791004B7EB9 /* libSSZipArchive.a */, + 83409E3819D5B791004B7EB9 /* SSZipArchiveTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 83E1A85619C8AC2A000A3BCA /* Packages */ = { + isa = PBXGroup; + children = ( + 83E1A85A19C8ACA0000A3BCA /* CCPackage.h */, + 83E1A85B19C8ACA0000A3BCA /* CCPackage.m */, + 83E1A85C19C8ACA0000A3BCA /* CCPackageConstants.h */, + 83E1A85D19C8ACA0000A3BCA /* CCPackageConstants.m */, + 83E1A85E19C8ACA0000A3BCA /* CCPackageManager.h */, + 83E1A85F19C8ACA0000A3BCA /* CCPackageManager.m */, + 83E1A86019C8ACA0000A3BCA /* CCPackageManagerDelegate.h */, + 83E1A86119C8ACA0000A3BCA /* CCPackageTypes.h */, + 83E1A85719C8AC56000A3BCA /* Download */, + 83E1A85919C8AC6A000A3BCA /* Unzip */, + 83E1A85819C8AC65000A3BCA /* Install */, + E525FF8C7C018BF691F36044 /* CCPackageHelper.h */, + E525F9EA4B5DD37D0418869E /* CCPackageHelper.m */, + E525FE4C98BCF1228ECC9623 /* CCPackage_private.h */, + ); + name = Packages; + sourceTree = ""; + }; + 83E1A85719C8AC56000A3BCA /* Download */ = { + isa = PBXGroup; + children = ( + 83E1A87019C8ACC1000A3BCA /* CCPackageDownload.h */, + 83E1A87119C8ACC1000A3BCA /* CCPackageDownload.m */, + 83E1A87219C8ACC1000A3BCA /* CCPackageDownloadDelegate.h */, + 83E1A87319C8ACC1000A3BCA /* CCPackageDownloadManager.h */, + 83E1A87419C8ACC1000A3BCA /* CCPackageDownloadManager.m */, + 83E1A87519C8ACC1000A3BCA /* CCPackageDownloadManagerDelegate.h */, + ); + name = Download; + sourceTree = ""; + }; + 83E1A85819C8AC65000A3BCA /* Install */ = { + isa = PBXGroup; + children = ( + 83E1A87E19C8ACDC000A3BCA /* CCPackageCocos2dEnabler.h */, + 83E1A87F19C8ACDC000A3BCA /* CCPackageCocos2dEnabler.m */, + 83E1A88219C8ACDC000A3BCA /* CCPackageInstaller.h */, + 83E1A88319C8ACDC000A3BCA /* CCPackageInstaller.m */, + ); + name = Install; + sourceTree = ""; + }; + 83E1A85919C8AC6A000A3BCA /* Unzip */ = { + isa = PBXGroup; + children = ( + 83E1A86A19C8ACAF000A3BCA /* CCPackageUnzipper.h */, + 83E1A86B19C8ACAF000A3BCA /* CCPackageUnzipper.m */, + 83E1A86C19C8ACAF000A3BCA /* CCPackageUnzipperDelegate.h */, + ); + name = Unzip; + sourceTree = ""; + }; A0D7D9D415E2E737000CA0C4 /* Physics */ = { isa = PBXGroup; children = ( @@ -1751,6 +1938,7 @@ 506602110E38A70D000B500E /* CCScheduler.h in Headers */, D24161001958F6EE003673BD /* CCAnimationManager+FrameAnimation.h in Headers */, D268FE0F198078FF00ECBCD0 /* CCEffectGlass.h in Headers */, + 83E1A86419C8ACA0000A3BCA /* CCPackageConstants.h in Headers */, D24160FE1958F6EE003673BD /* CCAnimationManager.h in Headers */, 50305AF40E40E33200F63373 /* cocos2d.h in Headers */, D285ECF8192EF5B2009F4E88 /* CCDirectorAndroid.h in Headers */, @@ -1762,6 +1950,7 @@ 50BAF3990F33CCD8003F654C /* ccMacros.h in Headers */, B798D13D181F2E4500E7BFCD /* CCNode_Private.h in Headers */, 509A79970F6188420032F449 /* CCSprite.h in Headers */, + 83E1A86619C8ACA0000A3BCA /* CCPackageManager.h in Headers */, 50C508C60F7C194400799124 /* CCFileUtils.h in Headers */, D268FE231980791C00ECBCD0 /* CCEffectStack_Private.h in Headers */, 503798C60F912C2000986724 /* CGPointExtension.h in Headers */, @@ -1795,8 +1984,10 @@ 50316AA610291280003ACFE7 /* CCRenderTexture.h in Headers */, B7D273171822F4AA0054849B /* CCBSequenceProperty.h in Headers */, 50316AD41029A126003ACFE7 /* CCMotionStreak.h in Headers */, + 83E1A86819C8ACA0000A3BCA /* CCPackageManagerDelegate.h in Headers */, D38058201889CE7700822437 /* CCCache.h in Headers */, D309055018AC23110081BF11 /* CCRenderer_Private.h in Headers */, + 83E1A87619C8ACC1000A3BCA /* CCPackageDownload.h in Headers */, 505462FC1062768000AB7C52 /* ccConfig.h in Headers */, 50E2A15010A45E7F00D894CE /* CCSpriteFrame.h in Headers */, D2DDB0A319805E8400233D80 /* CCVector2.h in Headers */, @@ -1806,6 +1997,7 @@ B79F905A17FE2A2E00908504 /* CCTableView.h in Headers */, 50E1357510ADEB1B00C9E7FA /* CCTexture.h in Headers */, 50D2AC9510E90DFA0068ECEB /* CCTiledMapLayer.h in Headers */, + 83E1A87919C8ACC1000A3BCA /* CCPackageDownloadManager.h in Headers */, 50D2AC9710E90DFA0068ECEB /* CCTiledMapObjectGroup.h in Headers */, D268FE11198078FF00ECBCD0 /* CCEffectHue.h in Headers */, B7D273151822F4AA0054849B /* CCBSequence.h in Headers */, @@ -1813,6 +2005,7 @@ D285ECF0192EA92A009F4E88 /* CCGLView.h in Headers */, 5015043B113300F900A9CA65 /* CCActionProgressTimer.h in Headers */, D24161091958F72B003673BD /* CCEffectRenderer.h in Headers */, + 83E1A88619C8ACDC000A3BCA /* CCPackageCocos2dEnabler.h in Headers */, 50FBB2DA117613F500150761 /* CCActionTween.h in Headers */, D2DDB0A119805E8400233D80 /* CCVector4.h in Headers */, 50D898F31192CB7500458C29 /* uthash.h in Headers */, @@ -1821,8 +2014,10 @@ E0C54DCB11F9CF2700B9E4CB /* ccUtils.h in Headers */, D23C5CBC194BC500007CA669 /* CCTouchAndroid.h in Headers */, E01E6D8C121F130E001A484F /* CCLabelBMFont.h in Headers */, + 83E1A86D19C8ACAF000A3BCA /* CCPackageUnzipper.h in Headers */, D2DDB09519805E8400233D80 /* CCMatrix3.h in Headers */, E0EAD0FF121F4B4600B0C81C /* CCDirectorIOS.h in Headers */, + 83E1A86219C8ACA0000A3BCA /* CCPackage.h in Headers */, E0EAD108121F4B4600B0C81C /* CCGLView.h in Headers */, B798D143181F469100E7BFCD /* CCLabelBMFont_Private.h in Headers */, D3903B0A1995285B003AA81A /* CCEffectBlur.h in Headers */, @@ -1831,6 +2026,7 @@ 9D85671D191B018200573093 /* CCEffectBrightness.h in Headers */, D272032B18FC89A000B100FF /* CCEffectNode.h in Headers */, E0F924711224140400EF2362 /* CCTexturePVR.h in Headers */, + 83E1A87B19C8ACC1000A3BCA /* CCPackageDownloadManagerDelegate.h in Headers */, E0F92AC41224559800EF2362 /* CCNS.h in Headers */, E02BB6D6126CA93A006E46A2 /* CCAnimationCache.h in Headers */, D27451AA19AD430A006DA0A1 /* CCEffectDropShadow.h in Headers */, @@ -1851,6 +2047,8 @@ A0C87D1A14F9A3A100C0E8B2 /* NSThread+performBlock.h in Headers */, D3903B14199528B6003AA81A /* CCMetalView.h in Headers */, B791E84A182064BF00DAE1D7 /* CCTiledMapLayer_Private.h in Headers */, + 83E1A86F19C8ACAF000A3BCA /* CCPackageUnzipperDelegate.h in Headers */, + 83E1A86919C8ACA0000A3BCA /* CCPackageTypes.h in Headers */, D27451C619B111A9006DA0A1 /* CCEffectDFOutline.h in Headers */, A6DC4E0918055DCC00C280A6 /* CCTransition.h in Headers */, B791E85D182074C500DAE1D7 /* CCProgressNode_Private.h in Headers */, @@ -1862,6 +2060,7 @@ A039EBFF155C686B0061EE37 /* CCNode+Debug.h in Headers */, A0DA0BC315BCDCA200E80A92 /* CCDrawNode.h in Headers */, 2B192835163361B10049A044 /* CCClippingNode.h in Headers */, + 83E1A88A19C8ACDC000A3BCA /* CCPackageInstaller.h in Headers */, B7EE6A1E181AE34600B983FE /* CCSlider.h in Headers */, A003AC8C1657071100C7B792 /* ccFPSImages.h in Headers */, B74C2B9B17BDA63A00A829C0 /* CCSprite9Slice.h in Headers */, @@ -1880,6 +2079,8 @@ B78AE46817E7AF1C0028BE0B /* CCScrollView.h in Headers */, B78AE46C17E7AF6C0028BE0B /* UITouch+CC.h in Headers */, B7D2730F1822F4AA0054849B /* CCBKeyframe.h in Headers */, + E525FCC2CF72D3667DE4D71B /* CCPackageHelper.h in Headers */, + E525F3A2BBCBB446B67D8260 /* CCPackage_private.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1895,6 +2096,7 @@ D2FEB618194F6C9E00FC0574 /* CCActionInterval.h in Headers */, D268FE281980791D00ECBCD0 /* CCEffectHue.h in Headers */, D2FEB619194F6C9E00FC0574 /* CCLabelTTF.h in Headers */, + BC9F4E9E19DB649800B25F01 /* CCPackageCocos2dEnabler.h in Headers */, D2FEB61A194F6C9E00FC0574 /* CCNodeColor.h in Headers */, D2FEB61B194F6C9E00FC0574 /* CCScene.h in Headers */, D2FEB61C194F6C9E00FC0574 /* CCTextureCache.h in Headers */, @@ -1908,6 +2110,7 @@ D2DDB0A219805E8400233D80 /* CCVector4.h in Headers */, D2FEB628194F6C9E00FC0574 /* CCPhysicsShape.h in Headers */, D2FEB629194F6C9E00FC0574 /* CCParticleExamples.h in Headers */, + BC9F4E9F19DB649C00B25F01 /* CCPackageInstaller.h in Headers */, D268FE2E1980791D00ECBCD0 /* CCEffectStack_Private.h in Headers */, D2FEB62A194F6C9E00FC0574 /* CCScheduler.h in Headers */, D241610A1958F72B003673BD /* CCEffectRenderer.h in Headers */, @@ -1924,6 +2127,7 @@ D2FEB63B194F6C9E00FC0574 /* CCFileUtils.h in Headers */, D2FEB63E194F6C9E00FC0574 /* CGPointExtension.h in Headers */, D2FEB63F194F6C9E00FC0574 /* CCParticleSystem.h in Headers */, + BC9F4E9719DB643F00B25F01 /* CCPackageConstants.h in Headers */, D2DDB09C19805E8400233D80 /* CCMathTypesAndroid.h in Headers */, D268FE261980791D00ECBCD0 /* CCEffectGlass.h in Headers */, D2FEB640194F6C9E00FC0574 /* CCParallaxNode.h in Headers */, @@ -1934,6 +2138,7 @@ D27451C719B111A9006DA0A1 /* CCEffectDFOutline.h in Headers */, D24161141958F738003673BD /* CCAnimationManager.h in Headers */, D2FEB647194F6C9E00FC0574 /* CCEffect_Private.h in Headers */, + BC9F4EA019DB649E00B25F01 /* CCPackageHelper.h in Headers */, D2FEB648194F6C9E00FC0574 /* CCEffectStack.h in Headers */, D2FEB64A194F6C9E00FC0574 /* CCTextField.h in Headers */, D2FEB64B194F6C9E00FC0574 /* CCBReader.h in Headers */, @@ -1941,6 +2146,8 @@ D2FEB64D194F6C9E00FC0574 /* CCActionManager.h in Headers */, D2FEB64E194F6C9E00FC0574 /* CCTiledMap.h in Headers */, D2FEB64F194F6C9E00FC0574 /* base64.h in Headers */, + BC9F4E9819DB644600B25F01 /* CCPackageManager.h in Headers */, + BC9F4E9919DB644900B25F01 /* CCPackageManagerDelegate.h in Headers */, D2FEB650194F6C9E00FC0574 /* ZipUtils.h in Headers */, D2FEB652194F6C9E00FC0574 /* CCTMXXMLParser.h in Headers */, D2FEB653194F6C9E00FC0574 /* CCRenderTexture.h in Headers */, @@ -1957,6 +2164,7 @@ D268FE2A1980791D00ECBCD0 /* CCEffectReflection.h in Headers */, D2FEB65F194F6C9E00FC0574 /* CCTexture.h in Headers */, D2FEB660194F6C9E00FC0574 /* CCTiledMapLayer.h in Headers */, + BC9F4E9C19DB646000B25F01 /* CCPackageDownloadManager.h in Headers */, D24161161958F738003673BD /* CCAnimationManager+FrameAnimation.h in Headers */, D27451AB19AD430A006DA0A1 /* CCEffectDropShadow.h in Headers */, D2FEB664194F6C9E00FC0574 /* CCTiledMapObjectGroup.h in Headers */, @@ -1967,6 +2175,7 @@ D2FEB667194F6C9E00FC0574 /* CCGLView.h in Headers */, D2FEB668194F6C9E00FC0574 /* CCActionProgressTimer.h in Headers */, D2FEB66B194F6C9E00FC0574 /* CCActionTween.h in Headers */, + BC9F4E9B19DB644E00B25F01 /* CCPackageDownload.h in Headers */, D2DDB0A019805E8400233D80 /* CCVector3.h in Headers */, D2FEB66D194F6C9E00FC0574 /* uthash.h in Headers */, D2FEB66E194F6C9E00FC0574 /* utlist.h in Headers */, @@ -1986,12 +2195,15 @@ D2FEB684194F6C9E00FC0574 /* CCAnimationCache.h in Headers */, D2FEB685194F6C9E00FC0574 /* CCAnimation.h in Headers */, D2DDB0A419805E8400233D80 /* CCVector2.h in Headers */, + BC9F4E9A19DB644B00B25F01 /* CCPackageTypes.h in Headers */, D2FEB686194F6C9E00FC0574 /* CCBuilderReader.h in Headers */, + BC9F4EA119DB64A200B25F01 /* CCPackage_private.h in Headers */, D2FEB687194F6C9E00FC0574 /* CCAppDelegate.h in Headers */, D2FEB688194F6C9E00FC0574 /* CCShader.h in Headers */, D27451BE19AEC84F006DA0A1 /* CCEffectDistanceField.h in Headers */, D268FE1E1980791400ECBCD0 /* CCEffectUtils.h in Headers */, D2FEB68C194F6C9E00FC0574 /* CCShader_Private.h in Headers */, + BC9F4E9619DB643C00B25F01 /* CCPackage.h in Headers */, D2FEB68E194F6C9E00FC0574 /* CCParticleBatchNode.h in Headers */, D2FEB690194F6C9E00FC0574 /* CCDirectorMac.h in Headers */, D2FEB691194F6C9E00FC0574 /* CCGLView.h in Headers */, @@ -2020,6 +2232,7 @@ D2FEB6AC194F6C9E00FC0574 /* CCResponder.h in Headers */, D2FEB6AD194F6C9E00FC0574 /* CCButton.h in Headers */, D2FEB6AE194F6C9E00FC0574 /* CCParticleSystem_Private.h in Headers */, + BC9F4E9D19DB646300B25F01 /* CCPackageDownloadManagerDelegate.h in Headers */, D2FEB6AF194F6C9E00FC0574 /* CCTouchEvent.h in Headers */, D2FEB6B1194F6C9E00FC0574 /* CCControl.h in Headers */, D241610E1958F72B003673BD /* CCEffectSaturation.h in Headers */, @@ -2037,13 +2250,14 @@ isa = PBXNativeTarget; buildConfigurationList = 5018F2500DFDEAE300C013A5 /* Build configuration list for PBXNativeTarget "cocos2d" */; buildPhases = ( - 5018F2490DFDEAC400C013A5 /* Headers */, 5018F24A0DFDEAC400C013A5 /* Sources */, + 5018F2490DFDEAC400C013A5 /* Headers */, 5018F24B0DFDEAC400C013A5 /* Frameworks */, ); buildRules = ( ); dependencies = ( + 83409E4D19D5E8A1004B7EB9 /* PBXTargetDependency */, FCFDA23E19C78E2D00B90910 /* PBXTargetDependency */, B759E5321880C70A00E8166C /* PBXTargetDependency */, ); @@ -2082,6 +2296,7 @@ buildRules = ( ); dependencies = ( + BC9F4E7A19DB5B7300B25F01 /* PBXTargetDependency */, FC39962119C3BB3A00C93E5E /* PBXTargetDependency */, D2FEB60E194F6C9E00FC0574 /* PBXTargetDependency */, ); @@ -2120,6 +2335,10 @@ ProductGroup = FC39961419C3B92F00C93E5E /* Products */; ProjectRef = FC39961319C3B92F00C93E5E /* ObjectAL.xcodeproj */; }, + { + ProductGroup = 83409E2B19D5B791004B7EB9 /* Products */; + ProjectRef = 83409E2A19D5B791004B7EB9 /* SSZipArchive.xcodeproj */; + }, ); projectRoot = ""; targets = ( @@ -2132,6 +2351,35 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 83409E3219D5B791004B7EB9 /* SSZipArchive.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + name = SSZipArchive.app; + path = SSZipArchiveExampleApp.app; + remoteRef = 83409E3119D5B791004B7EB9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 83409E3419D5B791004B7EB9 /* SSZipArchiveTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = SSZipArchiveTests.xctest; + remoteRef = 83409E3319D5B791004B7EB9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 83409E3619D5B791004B7EB9 /* libSSZipArchive.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libSSZipArchive.a; + remoteRef = 83409E3519D5B791004B7EB9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 83409E3819D5B791004B7EB9 /* SSZipArchiveTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = SSZipArchiveTests.xctest; + remoteRef = 83409E3719D5B791004B7EB9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; B759E52D1880C66900E8166C /* libObjectiveChipmunk-iPhone.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -2193,6 +2441,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 83E1A87A19C8ACC1000A3BCA /* CCPackageDownloadManager.m in Sources */, D34CAD6419C3A0FD009BED7A /* NSValue+CCRenderer.m in Sources */, 5018F26A0DFDEAFF00C013A5 /* CCAction.m in Sources */, D3903B0B1995285B003AA81A /* CCEffectBlur.m in Sources */, @@ -2206,6 +2455,7 @@ D268FE10198078FF00ECBCD0 /* CCEffectGlass.m in Sources */, 5018F2740DFDEAFF00C013A5 /* CCLabelTTF.m in Sources */, 9D856722191B019900573093 /* CCEffectContrast.m in Sources */, + 83E1A86E19C8ACAF000A3BCA /* CCPackageUnzipper.m in Sources */, 5018F2760DFDEAFF00C013A5 /* CCNodeColor.m in Sources */, 5018F2780DFDEAFF00C013A5 /* CCScene.m in Sources */, 5018F27E0DFDEAFF00C013A5 /* CCTextureCache.m in Sources */, @@ -2240,6 +2490,7 @@ 502C6C6D0FB87970002BF3C2 /* CCParallaxNode.m in Sources */, 50CB3B0F100AC43A00B7A750 /* CCActionManager.m in Sources */, D3903B11199528A0003AA81A /* CCRenderDispatch.m in Sources */, + 83E1A86319C8ACA0000A3BCA /* CCPackage.m in Sources */, B7E775D118527EF0004221AA /* CCAppDelegate.m in Sources */, 9DBCA31519B68BE400EFE96D /* CCEffectColorChannelOffset.m in Sources */, D3903B15199528B6003AA81A /* CCMetalView.m in Sources */, @@ -2247,6 +2498,7 @@ 50F29F5610204FD60046CA73 /* base64.c in Sources */, 50F2A104102094550046CA73 /* ZipUtils.m in Sources */, 571CD02B19649E03003D460C /* CCPlatformTextFieldIOS.m in Sources */, + 83E1A86519C8ACA0000A3BCA /* CCPackageConstants.m in Sources */, D272032E18FC89A000B100FF /* CCEffectStack.m in Sources */, 50316AA710291280003ACFE7 /* CCRenderTexture.m in Sources */, 50316AD51029A126003ACFE7 /* CCMotionStreak.m in Sources */, @@ -2258,6 +2510,7 @@ 5015043C113300F900A9CA65 /* CCActionProgressTimer.m in Sources */, D37D197818B6665700B23FDE /* CCTiledMapLayer.m in Sources */, D272032618FC89A000B100FF /* CCEffect.m in Sources */, + 83E1A87719C8ACC1000A3BCA /* CCPackageDownload.m in Sources */, 50FBB2DB117613F500150761 /* CCActionTween.m in Sources */, D37D197B18B6666E00B23FDE /* CCSpriteBatchNode.m in Sources */, D3903B0D1995288D003AA81A /* CCNoARC.m in Sources */, @@ -2294,6 +2547,7 @@ A039EC00155C686B0061EE37 /* CCNode+Debug.m in Sources */, D2DDB0A519805E8400233D80 /* CCQuaternion.m in Sources */, D36D31B718BD3CAA00E45F08 /* CCProgressNode.m in Sources */, + 83E1A86719C8ACA0000A3BCA /* CCPackageManager.m in Sources */, D27451B819AE5517006DA0A1 /* CCEffectDistanceField.m in Sources */, A0DA0BC415BCDCA200E80A92 /* CCDrawNode.m in Sources */, D27451AC19AD430A006DA0A1 /* CCEffectDropShadow.m in Sources */, @@ -2304,9 +2558,11 @@ B74C2B9C17BDA63A00A829C0 /* CCSprite9Slice.m in Sources */, B75C2E7D17C5908B002B0E0D /* NSAttributedString+CCAdditions.m in Sources */, B7EE69E31819E75700B983FE /* CCLayoutBox.m in Sources */, + 83E1A88719C8ACDC000A3BCA /* CCPackageCocos2dEnabler.m in Sources */, B7E776221857A159004221AA /* CCColor.m in Sources */, A6DC4E0A18055DCC00C280A6 /* CCTransition.m in Sources */, A6A0734617C788EB004343C8 /* CCResponderManager.m in Sources */, + 83E1A88B19C8ACDC000A3BCA /* CCPackageInstaller.m in Sources */, D23C5CB5194BC108007CA669 /* CCTouchIOS.m in Sources */, A6A0734B17C78EF3004343C8 /* CCResponder.m in Sources */, B78AE46317E7AF1C0028BE0B /* CCButton.m in Sources */, @@ -2317,6 +2573,7 @@ B78AE46D17E7AF6C0028BE0B /* UITouch+CC.m in Sources */, D33803E318032ECE0072D8FE /* CCPhysicsBody.m in Sources */, D268FE12198078FF00ECBCD0 /* CCEffectHue.m in Sources */, + E525F0F74C7C69C4371E20EE /* CCPackageHelper.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2361,6 +2618,7 @@ D2FEB6D0194F6C9E00FC0574 /* TGAlib.m in Sources */, D2FEB6D1194F6C9E00FC0574 /* CCTextField.m in Sources */, D2FEB6D2194F6C9E00FC0574 /* CCActionEase.m in Sources */, + BC9F4E8E19DB632800B25F01 /* CCPackageConstants.m in Sources */, 57BFF2BA1991937C00A3FE9C /* CCEditText.m in Sources */, D2FEB6D3194F6C9E00FC0574 /* CCPhysicsShape.m in Sources */, 577D72D11970AC59005ABDC0 /* CCPlatformTextField.m in Sources */, @@ -2382,6 +2640,7 @@ D27451BD19AEC84C006DA0A1 /* CCEffectDistanceField.m in Sources */, D2FEB6E3194F6C9E00FC0574 /* CCBReader.m in Sources */, D2FEB6E5194F6C9E00FC0574 /* base64.c in Sources */, + BC9F4E9219DB633B00B25F01 /* CCPackageDownloadManager.m in Sources */, D3903B17199528DF003AA81A /* CCRenderDispatch.m in Sources */, D2FEB6E7194F6C9E00FC0574 /* ZipUtils.m in Sources */, D2FEB6EA194F6C9E00FC0574 /* CCEffectStack.m in Sources */, @@ -2393,22 +2652,28 @@ D2FEB6F0194F6C9E00FC0574 /* CCTexture.m in Sources */, D2FEB6F1194F6C9E00FC0574 /* CCConfiguration.m in Sources */, D2FEB6F2194F6C9E00FC0574 /* CCProfiling.m in Sources */, + BC9F4E8819DB632400B25F01 /* CCPackage.m in Sources */, D2FEB6F3194F6C9E00FC0574 /* CCActionProgressTimer.m in Sources */, D24161151958F738003673BD /* CCAnimationManager.m in Sources */, + BC9F4E9019DB633100B25F01 /* CCPackageHelper.m in Sources */, D2FEB6F4194F6C9E00FC0574 /* CCTiledMapLayer.m in Sources */, D2FEB6F5194F6C9E00FC0574 /* CCEffect.m in Sources */, D268FE251980791D00ECBCD0 /* CCEffectBloom.m in Sources */, D2FEB6F7194F6C9E00FC0574 /* CCActionTween.m in Sources */, + BC9F4E9519DB635200B25F01 /* CCPackageInstaller.m in Sources */, D268FE271980791D00ECBCD0 /* CCEffectGlass.m in Sources */, D2FEB6F9194F6C9E00FC0574 /* CCSpriteBatchNode.m in Sources */, D2FEB6FA194F6C9E00FC0574 /* ccUtils.c in Sources */, D2FEB6FB194F6C9E00FC0574 /* CCTouchAndroid.m in Sources */, + BC9F4E9119DB633500B25F01 /* CCPackageDownload.m in Sources */, D3903B16199528DB003AA81A /* CCNoARC.m in Sources */, D268FE1C1980791400ECBCD0 /* CCEffectRefraction.m in Sources */, + BC9F4E9319DB633F00B25F01 /* CCPackageUnzipper.m in Sources */, D2FEB6FC194F6C9E00FC0574 /* CCPhysicsNode.m in Sources */, D2FEB6FD194F6C9E00FC0574 /* CCGLView.m in Sources */, D2FEB6FE194F6C9E00FC0574 /* CCLabelBMFont.m in Sources */, D2FEB701194F6C9E00FC0574 /* CCDirectorAndroid.m in Sources */, + BC9F4E9419DB635000B25F01 /* CCPackageCocos2dEnabler.m in Sources */, D2FEB702194F6C9E00FC0574 /* CCGLView.m in Sources */, D3903B18199528F3003AA81A /* CCEffectBlur.m in Sources */, D2FEB703194F6C9E00FC0574 /* CCEffectBrightness.m in Sources */, @@ -2449,6 +2714,7 @@ D2FEB732194F6C9E00FC0574 /* CCTransition.m in Sources */, D2FEB734194F6C9E00FC0574 /* CCResponderManager.m in Sources */, 5BC3CB5A19626FA000C4F0D0 /* CCGestureListener.m in Sources */, + BC9F4E8F19DB632B00B25F01 /* CCPackageManager.m in Sources */, D2FEB735194F6C9E00FC0574 /* CCTouchIOS.m in Sources */, D2FEB736194F6C9E00FC0574 /* CCResponder.m in Sources */, D2FEB737194F6C9E00FC0574 /* CCButton.m in Sources */, @@ -2465,11 +2731,21 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 83409E4D19D5E8A1004B7EB9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SSZipArchive; + targetProxy = 83409E4C19D5E8A1004B7EB9 /* PBXContainerItemProxy */; + }; B759E5321880C70A00E8166C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "ObjectiveChipmunk-iPhone"; targetProxy = B759E5311880C70A00E8166C /* PBXContainerItemProxy */; }; + BC9F4E7A19DB5B7300B25F01 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SSZipArchive; + targetProxy = BC9F4E7919DB5B7300B25F01 /* PBXContainerItemProxy */; + }; D2FEB60E194F6C9E00FC0574 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "ObjectiveChipmunk-iPhone"; @@ -2516,6 +2792,7 @@ "$(SRCROOT)/external/Chipmunk/include", "$(SRCROOT)/external/Chipmunk/objectivec/include", "$(SRCROOT)/external/ObjectAL/**", + "$(SRCROOT)/external/SSZipArchive/**", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2550,6 +2827,7 @@ "$(SRCROOT)/external/Chipmunk/include", "$(SRCROOT)/external/Chipmunk/objectivec/include", "$(SRCROOT)/external/ObjectAL/**", + "$(SRCROOT)/external/SSZipArchive/**", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2672,6 +2950,7 @@ external/Chipmunk/include, external/Chipmunk/objectivec/include, "external/ObjectAL/**", + "external/SSZipArchive/**", ); IPHONEOS_DEPLOYMENT_TARGET = 5.0; ONLY_ACTIVE_ARCH = YES; @@ -2724,6 +3003,7 @@ external/Chipmunk/include, external/Chipmunk/objectivec/include, "external/ObjectAL/**", + "external/SSZipArchive/**", ); IPHONEOS_DEPLOYMENT_TARGET = 5.0; ONLY_ACTIVE_ARCH = NO; diff --git a/cocos2d-tests-ios.xcodeproj/project.pbxproj b/cocos2d-tests-ios.xcodeproj/project.pbxproj index 7f2ae8e56d5..917d532037c 100644 --- a/cocos2d-tests-ios.xcodeproj/project.pbxproj +++ b/cocos2d-tests-ios.xcodeproj/project.pbxproj @@ -26,9 +26,11 @@ 758A6C7B18440D5C00D1A8D2 /* ParticleTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 758A6C7A18440D5C00D1A8D2 /* ParticleTest.m */; }; 758A6C80184410EE00D1A8D2 /* Particles in Resources */ = {isa = PBXBuildFile; fileRef = 758A6C7F184410EE00D1A8D2 /* Particles */; }; 75F76497185A831B00E2FAFE /* Sounds in Resources */ = {isa = PBXBuildFile; fileRef = 75F76496185A831B00E2FAFE /* Sounds */; }; + 839B149F19CC1E440000E5E2 /* CCPackageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 839B149E19CC1E440000E5E2 /* CCPackageTest.m */; }; 92324E2A18EB635500D78D3F /* CCReaderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92324E2918EB635500D78D3F /* CCReaderTest.m */; }; 9269312A1923D8A700CE6285 /* Resources-shared in Resources */ = {isa = PBXBuildFile; fileRef = B7C6237517EA695100928F91 /* Resources-shared */; }; 92FE241118F5F06F00647961 /* CCAnimationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92FE241018F5F06F00647961 /* CCAnimationTest.m */; }; + 9D96557319D6113500428E79 /* CCEffectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D96557219D6113500428E79 /* CCEffectTests.m */; }; A6167B93189A7D4D0044D391 /* VertexZTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A6167B92189A7D4D0044D391 /* VertexZTest.m */; }; A664A4EF18A3D9B8006184B8 /* PositioningTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A664A4EE18A3D9B8006184B8 /* PositioningTest.m */; }; B70AFC30180F2D7400516435 /* CCTransitionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B70AFC2F180F2D7400516435 /* CCTransitionTest.m */; }; @@ -142,6 +144,15 @@ D4AFE94B1977100000261299 /* tuffy_bold_italic-charmap.png in Resources */ = {isa = PBXBuildFile; fileRef = D4AFE9101977100000261299 /* tuffy_bold_italic-charmap.png */; }; D4AFE94C1977100000261299 /* west_england-64.fnt in Resources */ = {isa = PBXBuildFile; fileRef = D4AFE9111977100000261299 /* west_england-64.fnt */; }; D4AFE94D1977100000261299 /* west_england-64.png in Resources */ = {isa = PBXBuildFile; fileRef = D4AFE9121977100000261299 /* west_england-64.png */; }; + E525F033EECC363CA8EAF2A6 /* CCPackageDownloadManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E525F2B770C5914D0810E4C2 /* CCPackageDownloadManagerTests.m */; }; + E525F4D0568A8A4176684936 /* CCPackageHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E525F99055258F35DFF00ECD /* CCPackageHelperTests.m */; }; + E525F5DB1E3086DDE0296CEC /* CCPackageDownloadTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E525F5FACFF098F0ACBA6419 /* CCPackageDownloadTests.m */; }; + E525F7C977339D17470FC149 /* CCPackageInstallerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E525FBF58DFECF27272F5D6F /* CCPackageInstallerTests.m */; }; + E525F81797DA5573FC26BEBB /* CCPackageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E525F771C583AAF68EB42E15 /* CCPackageTests.m */; }; + E525FA44681404CC22A3A8D9 /* CCPackageUnzipperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E525F30DA34A6712527102E5 /* CCPackageUnzipperTests.m */; }; + E525FCAF2CC6D200B8065E2E /* CCPackageCocos2dEnablerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E525FB8A3CC89D071A8377B1 /* CCPackageCocos2dEnablerTests.m */; }; + E525FF2E8351476CDD7DEAC4 /* CCPackageManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E525F395FE85323D86232416 /* CCPackageManagerTests.m */; }; + E525FFABB4646A7446269A98 /* CCUnitTestAssertions.h in Resources */ = {isa = PBXBuildFile; fileRef = E525F832B569117922DA339E /* CCUnitTestAssertions.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -223,8 +234,10 @@ 75EA924B1860BFC800BF914A /* PerformanceTextureTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PerformanceTextureTest.h; sourceTree = ""; }; 75EA924C1860BFC800BF914A /* PerformanceTextureTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerformanceTextureTest.m; sourceTree = ""; }; 75F76496185A831B00E2FAFE /* Sounds */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Sounds; path = Resources/Sounds; sourceTree = SOURCE_ROOT; }; + 839B149E19CC1E440000E5E2 /* CCPackageTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CCPackageTest.m; path = "cocos2d-ui-tests/tests/CCPackageTest.m"; sourceTree = SOURCE_ROOT; }; 92324E2918EB635500D78D3F /* CCReaderTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCReaderTest.m; sourceTree = ""; }; 92FE241018F5F06F00647961 /* CCAnimationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCAnimationTest.m; sourceTree = ""; }; + 9D96557219D6113500428E79 /* CCEffectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCEffectTests.m; sourceTree = ""; }; A6167B92189A7D4D0044D391 /* VertexZTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VertexZTest.m; path = "cocos2d-ui-tests/tests/VertexZTest.m"; sourceTree = SOURCE_ROOT; }; A664A4EE18A3D9B8006184B8 /* PositioningTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PositioningTest.m; path = "cocos2d-ui-tests/tests/PositioningTest.m"; sourceTree = SOURCE_ROOT; }; B70AFC2F180F2D7400516435 /* CCTransitionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CCTransitionTest.m; path = "cocos2d-ui-tests/tests/CCTransitionTest.m"; sourceTree = SOURCE_ROOT; }; @@ -348,6 +361,15 @@ D4AFE9101977100000261299 /* tuffy_bold_italic-charmap.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tuffy_bold_italic-charmap.png"; sourceTree = ""; }; D4AFE9111977100000261299 /* west_england-64.fnt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "west_england-64.fnt"; sourceTree = ""; }; D4AFE9121977100000261299 /* west_england-64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "west_england-64.png"; sourceTree = ""; }; + E525F2B770C5914D0810E4C2 /* CCPackageDownloadManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageDownloadManagerTests.m; sourceTree = ""; }; + E525F30DA34A6712527102E5 /* CCPackageUnzipperTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageUnzipperTests.m; sourceTree = ""; }; + E525F395FE85323D86232416 /* CCPackageManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageManagerTests.m; sourceTree = ""; }; + E525F5FACFF098F0ACBA6419 /* CCPackageDownloadTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageDownloadTests.m; sourceTree = ""; }; + E525F771C583AAF68EB42E15 /* CCPackageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageTests.m; sourceTree = ""; }; + E525F832B569117922DA339E /* CCUnitTestAssertions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCUnitTestAssertions.h; sourceTree = ""; }; + E525F99055258F35DFF00ECD /* CCPackageHelperTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageHelperTests.m; sourceTree = ""; }; + E525FB8A3CC89D071A8377B1 /* CCPackageCocos2dEnablerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageCocos2dEnablerTests.m; sourceTree = ""; }; + E525FBF58DFECF27272F5D6F /* CCPackageInstallerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCPackageInstallerTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -398,6 +420,7 @@ isa = PBXGroup; children = ( 92FE241018F5F06F00647961 /* CCAnimationTest.m */, + 9D96557219D6113500428E79 /* CCEffectTests.m */, 755569E31856361100ED1B0F /* CCFileUtilTests.m */, 755569E41856361100ED1B0F /* CCNodeTests.m */, 755569E51856361100ED1B0F /* CCPhysicsTests.m */, @@ -407,6 +430,8 @@ 92324E2918EB635500D78D3F /* CCReaderTest.m */, D32FDE8519B645CA0078CC16 /* CCTextureTests.m */, 755569EC1856361100ED1B0F /* Supporting Files */, + E525F25081EB1E197CD567E4 /* Packages */, + E525F832B569117922DA339E /* CCUnitTestAssertions.h */, ); path = UnitTests; sourceTree = ""; @@ -524,6 +549,7 @@ D3B2A7E4187DD60B00406C5A /* CCBMFontTest.m */, A6167B92189A7D4D0044D391 /* VertexZTest.m */, A664A4EE18A3D9B8006184B8 /* PositioningTest.m */, + 839B149E19CC1E440000E5E2 /* CCPackageTest.m */, D3269DE719CC985900406282 /* TestShaders.metal */, ); name = Tests; @@ -538,6 +564,7 @@ B7E2605F17E7D278007067F0 /* cocos2d-tests-ios */, B7E2604A17E7D278007067F0 /* Frameworks */, B7E2604917E7D278007067F0 /* Products */, + E525F171C7FA9B056FA8C500, ); sourceTree = ""; }; @@ -725,6 +752,45 @@ path = Resources/Fonts; sourceTree = SOURCE_ROOT; }; + E525F18C1684DF1CDDEBE3AE /* Install */ = { + isa = PBXGroup; + children = ( + E525FBF58DFECF27272F5D6F /* CCPackageInstallerTests.m */, + E525FB8A3CC89D071A8377B1 /* CCPackageCocos2dEnablerTests.m */, + ); + name = Install; + sourceTree = ""; + }; + E525F25081EB1E197CD567E4 /* Packages */ = { + isa = PBXGroup; + children = ( + E525FC81EFFEFE098ED2ADD3 /* Download */, + E525F18C1684DF1CDDEBE3AE /* Install */, + E525F544D63123C8E948AA05 /* Unzip */, + E525F771C583AAF68EB42E15 /* CCPackageTests.m */, + E525F395FE85323D86232416 /* CCPackageManagerTests.m */, + E525F99055258F35DFF00ECD /* CCPackageHelperTests.m */, + ); + name = Packages; + sourceTree = ""; + }; + E525F544D63123C8E948AA05 /* Unzip */ = { + isa = PBXGroup; + children = ( + E525F30DA34A6712527102E5 /* CCPackageUnzipperTests.m */, + ); + name = Unzip; + sourceTree = ""; + }; + E525FC81EFFEFE098ED2ADD3 /* Download */ = { + isa = PBXGroup; + children = ( + E525F5FACFF098F0ACBA6419 /* CCPackageDownloadTests.m */, + E525F2B770C5914D0810E4C2 /* CCPackageDownloadManagerTests.m */, + ); + name = Download; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -837,6 +903,7 @@ 9269312A1923D8A700CE6285 /* Resources-shared in Resources */, 75556A1B1856374700ED1B0F /* InfoPlist.strings in Resources */, 75556A15185636FB00ED1B0F /* powered.png in Resources */, + E525FFABB4646A7446269A98 /* CCUnitTestAssertions.h in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -934,6 +1001,7 @@ files = ( 92324E2A18EB635500D78D3F /* CCReaderTest.m in Sources */, D32FDE8619B645CA0078CC16 /* CCTextureTests.m in Sources */, + 9D96557319D6113500428E79 /* CCEffectTests.m in Sources */, 75556A161856370A00ED1B0F /* CCFileUtilTests.m in Sources */, 92FE241118F5F06F00647961 /* CCAnimationTest.m in Sources */, 75556A171856370A00ED1B0F /* CCNodeTests.m in Sources */, @@ -941,6 +1009,14 @@ 75556A181856370A00ED1B0F /* CCPhysicsTests.m in Sources */, D34CD3C518ABF4AA00D8E537 /* CCRendererTests.m in Sources */, 75556A191856370A00ED1B0F /* CCSchedulerTests.m in Sources */, + E525F81797DA5573FC26BEBB /* CCPackageTests.m in Sources */, + E525F5DB1E3086DDE0296CEC /* CCPackageDownloadTests.m in Sources */, + E525FA44681404CC22A3A8D9 /* CCPackageUnzipperTests.m in Sources */, + E525F7C977339D17470FC149 /* CCPackageInstallerTests.m in Sources */, + E525FCAF2CC6D200B8065E2E /* CCPackageCocos2dEnablerTests.m in Sources */, + E525FF2E8351476CDD7DEAC4 /* CCPackageManagerTests.m in Sources */, + E525F033EECC363CA8EAF2A6 /* CCPackageDownloadManagerTests.m in Sources */, + E525F4D0568A8A4176684936 /* CCPackageHelperTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -970,6 +1046,7 @@ 7587BDB3185A0A6900EEEFDE /* TextureTest.m in Sources */, D3C9C867188078B900C58900 /* CCSchedulerTest.m in Sources */, 7587BDB7185A64AD00EEEFDE /* ColorTest.m in Sources */, + 839B149F19CC1E440000E5E2 /* CCPackageTest.m in Sources */, D340E10F185660BE006E605C /* TilemapTest.m in Sources */, B7E2622117E7D3B2007067F0 /* MainMenu.m in Sources */, D3D6CF5F18BD5F0500A51531 /* CCRendererTest.m in Sources */, diff --git a/cocos2d-ui-tests/Resources-shared/Packages/password-iOS-phone.zip b/cocos2d-ui-tests/Resources-shared/Packages/password-iOS-phone.zip new file mode 100755 index 00000000000..9862e7da1e6 Binary files /dev/null and b/cocos2d-ui-tests/Resources-shared/Packages/password-iOS-phone.zip differ diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phone.zip b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phone.zip new file mode 100644 index 00000000000..219615165e6 Binary files /dev/null and b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phone.zip differ diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd.zip b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd.zip new file mode 100644 index 00000000000..317633a0331 Binary files /dev/null and b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd.zip differ diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/boredSmiley.png b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/boredSmiley.png new file mode 100644 index 00000000000..a4eb9fa7ec1 Binary files /dev/null and b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/boredSmiley.png differ diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/configCocos2d.plist b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/configCocos2d.plist new file mode 100644 index 00000000000..5261fca23b3 --- /dev/null +++ b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/configCocos2d.plist @@ -0,0 +1,12 @@ + + + + + CCSetupScreenMode + CCScreenModeFlexible + CCSetupScreenOrientation + CCScreenOrientationLandscape + CCSetupTabletScale2X + + + diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/fileLookup.plist b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/fileLookup.plist new file mode 100644 index 00000000000..7a04cd9ea7a --- /dev/null +++ b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/fileLookup.plist @@ -0,0 +1,13 @@ + + + + + filenames + + metadata + + version + 1 + + + diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/jollySmiley.png b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/jollySmiley.png new file mode 100644 index 00000000000..8a43cd0e4c7 Binary files /dev/null and b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/jollySmiley.png differ diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/smileys.plist b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/smileys.plist new file mode 100644 index 00000000000..76af79e8630 --- /dev/null +++ b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/smileys.plist @@ -0,0 +1,44 @@ + + + + + frames + + smileys/angrySmiley.png + + frame + {{1, 29}, {42, 30}} + offset + {1, 2} + rotated + + sourceColorRect + {{5, 8}, {42, 30}} + sourceSize + {50, 50} + + smileys/boredSmiley.png + + frame + {{1, 1}, {44, 26}} + offset + {1, -1} + rotated + + sourceColorRect + {{4, 13}, {44, 26}} + sourceSize + {50, 50} + + + metadata + + format + 2 + size + {64, 64} + textureFileName + smileys.png + + + diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/smileys.png b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/smileys.png new file mode 100644 index 00000000000..d56cae67163 Binary files /dev/null and b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/resources-phonehd/smileys.png differ diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/spriteFrameFileList.plist b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/spriteFrameFileList.plist new file mode 100644 index 00000000000..63342a96fd3 --- /dev/null +++ b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-phonehd_unzipped/testpackage-iOS-phonehd/spriteFrameFileList.plist @@ -0,0 +1,15 @@ + + + + + metadata + + version + 1 + + spriteFrameFiles + + smileys.plist + + + diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-tablet.zip b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-tablet.zip new file mode 100644 index 00000000000..532af840871 Binary files /dev/null and b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-tablet.zip differ diff --git a/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-tablethd.zip b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-tablethd.zip new file mode 100644 index 00000000000..dd774da2717 Binary files /dev/null and b/cocos2d-ui-tests/Resources-shared/Packages/testpackage-iOS-tablethd.zip differ diff --git a/cocos2d-ui-tests/ios/AppDelegate.h b/cocos2d-ui-tests/ios/AppDelegate.h index 385fa09ff7a..c58a20e50d4 100644 --- a/cocos2d-ui-tests/ios/AppDelegate.h +++ b/cocos2d-ui-tests/ios/AppDelegate.h @@ -29,4 +29,8 @@ { } +- (void)configureCocos2d; + +- (void)configureFileUtilsSearchPathAndRegisterSpriteSheets; + @end diff --git a/cocos2d-ui-tests/ios/AppDelegate.m b/cocos2d-ui-tests/ios/AppDelegate.m index a8639b95574..ea5f633c803 100644 --- a/cocos2d-ui-tests/ios/AppDelegate.m +++ b/cocos2d-ui-tests/ios/AppDelegate.m @@ -31,12 +31,32 @@ @implementation AppController - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + [self configureCocos2d]; + + return YES; +} + +- (void)configureCocos2d { // Configure the file utils to work with SpriteBuilder, but use a custom resource path (Resources-shared instead of Published-iOS) [CCBReader configureCCFileUtils]; - + + [self configureFileUtilsSearchPathAndRegisterSpriteSheets]; + + [self setupCocos2dWithOptions:@{ + CCSetupDepthFormat: @GL_DEPTH24_STENCIL8, +// CCSetupScreenMode: CCScreenModeFixed, +// CCSetupScreenOrientation: CCScreenOrientationPortrait, + CCSetupTabletScale2X: @YES, + CCSetupShowDebugStats: @(getenv("SHOW_DEBUG_STATS") != nil), + }]; +} + +- (void)configureFileUtilsSearchPathAndRegisterSpriteSheets +{ CCFileUtils* sharedFileUtils = [CCFileUtils sharedFileUtils]; - + sharedFileUtils.searchPath = [NSArray arrayWithObjects: [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Images"], @@ -44,21 +64,11 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Resources-shared"], [[NSBundle mainBundle] resourcePath], nil]; - + // Register spritesheets. [[CCSpriteFrameCache sharedSpriteFrameCache] registerSpriteFramesFile:@"Interface.plist"]; [[CCSpriteFrameCache sharedSpriteFrameCache] registerSpriteFramesFile:@"Sprites.plist"]; [[CCSpriteFrameCache sharedSpriteFrameCache] registerSpriteFramesFile:@"TilesAtlassed.plist"]; - - [self setupCocos2dWithOptions:@{ - CCSetupDepthFormat: @GL_DEPTH24_STENCIL8, -// CCSetupScreenMode: CCScreenModeFixed, -// CCSetupScreenOrientation: CCScreenOrientationPortrait, - CCSetupTabletScale2X: @YES, - CCSetupShowDebugStats: @(getenv("SHOW_DEBUG_STATS") != nil), - }]; - - return YES; } - (CCScene*) startScene diff --git a/cocos2d-ui-tests/tests/CCPackageTest.m b/cocos2d-ui-tests/tests/CCPackageTest.m new file mode 100644 index 00000000000..95257b6c741 --- /dev/null +++ b/cocos2d-ui-tests/tests/CCPackageTest.m @@ -0,0 +1,249 @@ +// +// CCPackageTest.m +// cocos2d-tests-ios +// +// Created by Nicky Weber on 19.09.14. +// Copyright (c) 2014 Cocos2d. All rights reserved. +// +#import "CCPlatformTextField.h" +#import "TestBase.h" +#import "CCPackageManager.h" +#import "CCPackageManagerDelegate.h" +#import "CCPackage.h" +#import "CCPackageConstants.h" +#import "AppDelegate.h" + + + +@interface CCPackageTestURLProtocol : NSURLProtocol + +@end + + +@implementation CCPackageTestURLProtocol + ++ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest +{ + return [theRequest.URL.scheme caseInsensitiveCompare:@"http"] == NSOrderedSame; +} + ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)theRequest +{ + return theRequest; +} + +- (void)startLoading +{ + NSString *fileName = [self.request.URL lastPathComponent]; + + NSString *pathToPackage = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"Resources-shared/Packages/%@", fileName] ofType:nil]; + NSData *data = [NSData dataWithContentsOfFile:pathToPackage]; + + NSHTTPURLResponse *response; + if (pathToPackage) + { + response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL + statusCode:200 + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + } + else + { + response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL + statusCode:404 + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + } + + + id client = [self client]; + [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [client URLProtocol:self didLoadData:data]; + [client URLProtocolDidFinishLoading:self]; +} + +- (void)stopLoading +{ + // Nothing to do +} + +@end + + +#pragma mark - Test class + +@interface CCPackageTest : TestBase + +@property (nonatomic, strong) CCPackage *package; + +@end + + +@implementation CCPackageTest + +- (void) setupPackageTest +{ + [self setupLocalHTTPRequests]; + + [self.contentNode removeAllChildren]; + + [self removePersistedPackages]; + + [self removeAllPackages]; + + [self resetCocos2d]; + + [self cleanDirectories]; + + [CCPackageManager sharedManager].delegate = self; + + [self addLabels]; + + self.package = [[CCPackageManager sharedManager] downloadPackageWithName:@"testpackage" + resolution:@"phonehd" + remoteURL:[NSURL URLWithString:@"http://package.request.fake/testpackage-iOS-phonehd.zip"] + enableAfterDownload:YES]; +} + +- (void)setupLocalHTTPRequests +{ + [NSURLProtocol registerClass:[CCPackageTestURLProtocol class]]; +} + +- (void)removePersistedPackages +{ + [[NSUserDefaults standardUserDefaults] removeObjectForKey:PACKAGE_STORAGE_USERDEFAULTS_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (void)addLabels +{ + CGSize winSize = [CCDirector sharedDirector].viewSize; + + CCLabelTTF *labelSmiley1 = [CCLabelTTF labelWithString:@"Jolly Smiley? ->" fontName:@"HelveticaNeue-Light" fontSize:10 * [CCDirector sharedDirector].UIScaleFactor]; + CCLabelTTF *labelSmiley2 = [CCLabelTTF labelWithString:@"<- Angry Smiley?" fontName:@"HelveticaNeue-Light" fontSize:10 * [CCDirector sharedDirector].UIScaleFactor]; + + labelSmiley1.position = ccp((CGFloat) (winSize.width / 2.0 - 75.0), (CGFloat) (winSize.height / 2.0)); + labelSmiley2.position = ccp((CGFloat) (winSize.width / 2.0 + 75.0), (CGFloat) (winSize.height / 2.0)); + + [self.contentNode addChild:labelSmiley1]; + [self.contentNode addChild:labelSmiley2]; +} + +- (void)removeAllPackages +{ + for (CCPackage *aPackage in [[CCPackageManager sharedManager].allPackages mutableCopy]) + { + [[CCPackageManager sharedManager] deletePackage:aPackage error:nil]; + } +} + +- (void)resetCocos2d +{ + [(AppController *) [UIApplication sharedApplication].delegate configureFileUtilsSearchPathAndRegisterSpriteSheets]; +} + +- (void)cleanDirectories +{ + NSString *installFolder = [CCPackageManager sharedManager].installedPackagesPath; + NSString *unzipFolder = [NSTemporaryDirectory() stringByAppendingPathComponent:PACKAGE_REL_UNZIP_FOLDER]; + NSString *downloadFolder = [NSTemporaryDirectory() stringByAppendingPathComponent:PACKAGE_REL_DOWNLOAD_FOLDER]; + + NSArray *foldersToClean = @[ + [NSURL fileURLWithPath:installFolder], + [NSURL fileURLWithPath:unzipFolder], + [NSURL fileURLWithPath:downloadFolder]]; + + + for (NSURL *folderURL in foldersToClean) + { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtURL:folderURL + includingPropertiesForKeys:@[NSURLNameKey] + options:NSDirectoryEnumerationSkipsSubdirectoryDescendants + errorHandler:^BOOL(NSURL *url, NSError *error) { + return YES; + }]; + + NSLog(@"%@", folderURL); + for (NSURL *fileURL in dirEnumerator) + { + NSLog(@"\t%@", fileURL); + + NSError *error; + if (![fileManager removeItemAtURL:fileURL error:&error]) + { + NSLog(@"Error removing file: %@", error); + } + } + } +} + + +#pragma mark - CCPackageManagerDelegate + +- (void)packageInstallationFinished:(CCPackage *)package +{ + self.subTitle = [NSString stringWithFormat:@"Package installed"]; + + CGSize winSize = [CCDirector sharedDirector].viewSize; + + CCSprite *smiley1 = [CCSprite spriteWithImageNamed:@"jollySmiley.png"]; + [self.contentNode addChild:smiley1]; + smiley1.position = ccp((CGFloat) (winSize.width / 2.0 - 20.0), (CGFloat) (winSize.height / 2.0)); + + CCSprite *smiley2 = [CCSprite spriteWithImageNamed:@"smileys/angrySmiley.png"]; + [self.contentNode addChild:smiley2]; + smiley2.position = ccp((CGFloat) (winSize.width / 2.0 + 20.0) , (CGFloat) (winSize.height / 2.0)); + + +/* + NSError *error; + if (![[CCPackageManager sharedManager] deletePackage:package error:&error]) + { + NSLog(@"Error removing package: %@", error); + } +*/ +} + +- (void)packageInstallationFailed:(CCPackage *)package error:(NSError *)error +{ + self.subTitle = [NSString stringWithFormat:@"Test failed: installation failed."]; + [NSURLProtocol unregisterClass:[CCPackageTestURLProtocol class]]; +} + +- (void)packageDownloadFinished:(CCPackage *)package +{ + self.subTitle = [NSString stringWithFormat:@"Download finished"]; + [NSURLProtocol unregisterClass:[CCPackageTestURLProtocol class]]; +} + +- (void)packageDownloadFailed:(CCPackage *)package error:(NSError *)error +{ + self.subTitle = [NSString stringWithFormat:@"Test failed: download failed."]; + [NSURLProtocol unregisterClass:[CCPackageTestURLProtocol class]]; +} + +- (void)packageUnzippingFinished:(CCPackage *)package +{ + self.subTitle = [NSString stringWithFormat:@"Unzip finished"]; + [NSURLProtocol unregisterClass:[CCPackageTestURLProtocol class]]; +} + +- (void)packageUnzippingFailed:(CCPackage *)package error:(NSError *)error +{ + self.subTitle = [NSString stringWithFormat:@"Test failed: unzipping failed."]; + [NSURLProtocol unregisterClass:[CCPackageTestURLProtocol class]]; +} + +- (void)packageDownloadProgress:(CCPackage *)package downloadedBytes:(NSUInteger)downloadedBytes totalBytes:(NSUInteger)totalBytes +{ + NSLog(@"downloading... %u / %u", downloadedBytes, totalBytes); +} + +- (void)packageUnzippingProgress:(CCPackage *)package unzippedBytes:(NSUInteger)unzippedBytes totalBytes:(NSUInteger)totalBytes +{ + NSLog(@"unzipping... %u / %u", unzippedBytes, totalBytes); +} + +@end diff --git a/cocos2d/CCEffectGlass.m b/cocos2d/CCEffectGlass.m index 162bd463b27..cc49197cf0e 100644 --- a/cocos2d/CCEffectGlass.m +++ b/cocos2d/CCEffectGlass.m @@ -160,9 +160,20 @@ -(void)buildFragmentFunctions // map alpha also allows the effect to be disabled for specific pixels. vec4 refraction = normalMap.a * inBounds * texture2D(u_refractEnvMap, refractTexCoords) * (1.0 - primaryColor.a); + // Compute Schlick's approximation (http://en.wikipedia.org/wiki/Schlick's_approximation) of the + // fresnel reflectance. + float fresnel = max(u_fresnelBias + (1.0 - u_fresnelBias) * pow((1.0 - nDotV), u_fresnelPower), 0.0); + + // Apply a cutoff to nDotV to reduce the aliasing that occurs in the reflected + // image. As the surface normal approaches a 90 degree angle relative to the viewing + // direction, the sampling of the reflection map becomes more and more compressed + // which can lead to undesirable aliasing artifacts. The cutoff threshold reduces + // the contribution of these pixels to the final image and hides this aliasing. + const float NDOTV_CUTOFF = 0.2; + fresnel *= smoothstep(0.0, NDOTV_CUTOFF, nDotV); + // Add the reflected color modulated by the fresnel term. Multiplying by the normal // map alpha also allows the effect to be disabled for specific pixels. - float fresnel = max(u_fresnelBias + (1.0 - u_fresnelBias) * pow((1.0 - nDotV), u_fresnelPower), 0.0); vec4 reflection = normalMap.a * fresnel * u_shininess * texture2D(u_reflectEnvMap, reflectTexCoords); return primaryColor + refraction + reflection; diff --git a/cocos2d/CCEffectReflection.m b/cocos2d/CCEffectReflection.m index 6219e8b506f..39e61c62a63 100644 --- a/cocos2d/CCEffectReflection.m +++ b/cocos2d/CCEffectReflection.m @@ -137,8 +137,18 @@ -(void)buildFragmentFunctions // Compute the combination of the sprite's color and texture. vec4 primaryColor = inputValue; + // Compute Schlick's approximation (http://en.wikipedia.org/wiki/Schlick's_approximation) of the + // fresnel reflectance. float fresnel = max(u_fresnelBias + (1.0 - u_fresnelBias) * pow((1.0 - nDotV), u_fresnelPower), 0.0); + // Apply a cutoff to nDotV to reduce the aliasing that occurs in the reflected + // image. As the surface normal approaches a 90 degree angle relative to the viewing + // direction, the sampling of the reflection map becomes more and more compressed + // which can lead to undesirable aliasing artifacts. The cutoff threshold reduces + // the contribution of these pixels to the final image and hides this aliasing. + const float NDOTV_CUTOFF = 0.2; + fresnel *= smoothstep(0.0, NDOTV_CUTOFF, nDotV); + // If the reflected texture coordinates are within the bounds of the environment map // blend the primary color with the reflected environment. Multiplying by the normal // map alpha also allows the effect to be disabled for specific pixels. diff --git a/cocos2d/CCEffectUtils.m b/cocos2d/CCEffectUtils.m index 744d571fbcd..2127a5920dc 100644 --- a/cocos2d/CCEffectUtils.m +++ b/cocos2d/CCEffectUtils.m @@ -48,6 +48,10 @@ GLKMatrix4 CCEffectUtilsTransformFromNodeToNode(CCNode *first, CCNode *second, B { *isPossible = (commonAncestor != nil); } + if (commonAncestor == nil) + { + return GLKMatrix4Identity; + } // Compute the transform from this node to the common ancestor CGAffineTransform t1 = [first nodeToParentTransform]; diff --git a/cocos2d/CCPackage.h b/cocos2d/CCPackage.h new file mode 100644 index 00000000000..731fb919258 --- /dev/null +++ b/cocos2d/CCPackage.h @@ -0,0 +1,138 @@ +#import +#import "CCPackageTypes.h" + +@class CCPackageManager; + +@interface CCPackage : NSObject + +/** + * Name of the package + */ +@property (nonatomic, copy, readonly) NSString *name; + +/** + * Resolution of the package, e.g. tablethd, phonehd, etc. + */ +@property (nonatomic, copy, readonly) NSString *resolution; + +/** + * OS of the package e.g. iOS, Android, Mac + */ +@property (nonatomic, copy, readonly) NSString *os; + +/** + * The remote URL of the package + */ +@property (nonatomic, copy, readonly) NSURL *remoteURL; + +/** + * The local URL where the package is installed. This value will be initially nil and set only if installation was successful. + */ +@property (nonatomic, copy, readonly) NSURL *installURL; + +/** + * Local URL of the download file when download finishes. While downloading a temp name + * is used which won't be accessible. + */ +@property (nonatomic, copy, readonly) NSURL *localDownloadURL; + +/** + * Local URL of the folder the package is unzipped to + */ +@property (nonatomic, copy, readonly) NSURL *unzipURL; + +/** + * Name of the folder inside the unzip folder. A zipped package is supposed to contain a folder named + * like this --. Example: DLC-iOS-phonehd. + * This name can vary though and can be determined by delegation if a standard name was not found + * during installation. + */ +@property (nonatomic, copy, readonly) NSString *folderName; + +/** + * Whether or not the the package should be enabled in cocos2d after installation. + */ +@property (nonatomic, readonly) BOOL enableOnDownload; + +/** + * The current status of the package + */ +@property (nonatomic, readonly) CCPackageStatus status; + + +/** + * Creates a new instance of a package. + * OS and resolution are determined implicitly. Resolution is derived from CCFileUtils' searchResolutionsOrder first entry. + * + * @param name Name of the package + * @param remoteURL Remote URL of the package + * + * @return New instance of CCPackage + */ +- (instancetype)initWithName:(NSString *)name + remoteURL:(NSURL *)remoteURL; + +/** + * Creates a new instance of a package. + * OS is determined implicitly. + * + * @param name Name of the package + * @param resolution Resolution of the package + * @param remoteURL Remote URL of the package + * + * @return New instance of CCPackage + */ +- (instancetype)initWithName:(NSString *)name + resolution:(NSString *)resolution + remoteURL:(NSURL *)remoteURL; + +/** + * Creates a new instance of a package. + * + * @param name Name of the package + * @param resolution Resolution of the package + * @param os OS of the package, usally determined internally + * @param remoteURL Remote URL of the package + * + * @return New instance of CCPackage + */ +- (instancetype)initWithName:(NSString *)name + resolution:(NSString *)resolution + os:(NSString *)os + remoteURL:(NSURL *)remoteURL; + +/** + * Creates a new instance of a package populated with the contents of the dictionary. + * Used in context of serialization. + * + * @param dictionary Dictionary containing values to populate the package with. + * + * @return New instance of CCPackage + */ +- (instancetype)initWithDictionary:(NSDictionary *)dictionary; + +/** + * Returns a dictionary containing the values of the package's properties. + * Used in context of serialization. + * + * @return A dictionary containing the values of the package + */ +- (NSDictionary *)toDictionary; + +/** + * Returns an identifier of the package: The pattern is --. Example: DLC-iOS-phonehd. + * + * @return A dictionary containing the values of the package + */ +- (NSString *)standardIdentifier; + +/** + * Returns the status as a string. + * Debugging purposes. + * + * @return A string representation of the status property + */ +- (NSString *)statusToString; + +@end + diff --git a/cocos2d/CCPackage.m b/cocos2d/CCPackage.m new file mode 100644 index 00000000000..2d98c682736 --- /dev/null +++ b/cocos2d/CCPackage.m @@ -0,0 +1,156 @@ +#import +#import "CCPackage.h" +#import "CCPackageHelper.h" +#import "CCPackage_private.h" + +static NSUInteger PACKAGE_SERIALIZATION_VERSION = 1; +static NSString *const PACKAGE_SERIALIZATION_KEY_NAME = @"name"; +static NSString *const PACKAGE_SERIALIZATION_KEY_RESOLUTION = @"resolution"; +static NSString *const PACKAGE_SERIALIZATION_KEY_OS = @"os"; +static NSString *const PACKAGE_SERIALIZATION_KEY_REMOTE_URL = @"remoteURL"; +static NSString *const PACKAGE_SERIALIZATION_KEY_INSTALL_URL = @"installURL"; +static NSString *const PACKAGE_SERIALIZATION_KEY_VERSION = @"version"; +static NSString *const PACKAGE_SERIALIZATION_KEY_STATUS = @"status"; +static NSString *const PACKAGE_SERIALIZATION_KEY_LOCAL_DOWNLOAD_URL = @"localDownloadURL"; +static NSString *const PACKAGE_SERIALIZATION_KEY_LOCAL_UNZIP_URL = @"localUnzipURL"; +static NSString *const PACKAGE_SERIALIZATION_KEY_FOLDER_NAME = @"folderName"; +static NSString *const PACKAGE_SERIALIZATION_KEY_ENABLE_ON_DOWNLOAD = @"enableOnDownload"; + + +@implementation CCPackage + +- (instancetype)initWithName:(NSString *)name resolution:(NSString *)resolution os:(NSString *)os remoteURL:(NSURL *)remoteURL +{ + NSAssert(name != nil, @"name must not be nil"); + NSAssert(resolution != nil, @"resolution must not be nil"); + NSAssert(os != nil, @"os must not be nil"); + NSAssert(remoteURL != nil, @"remoteURL must not be nil"); + + self = [super init]; + if (self) + { + self.name = name; + self.resolution = resolution; + self.os = os; + self.remoteURL = remoteURL; + self.status = CCPackageStatusInitial; + } + + return self; +} + +- (instancetype)initWithName:(NSString *)name resolution:(NSString *)resolution remoteURL:(NSURL *)remoteURL +{ + return [[CCPackage alloc] initWithName:name + resolution:resolution + os:[CCPackageHelper currentOS] + remoteURL:remoteURL]; +} + +- (instancetype)initWithName:(NSString *)name remoteURL:(NSURL *)remoteURL +{ + return [[CCPackage alloc] initWithName:name + resolution:[CCPackageHelper defaultResolution] + os:[CCPackageHelper currentOS] + remoteURL:remoteURL]; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + CCPackage *package = [[CCPackage alloc] initWithName:dictionary[PACKAGE_SERIALIZATION_KEY_NAME] + resolution:dictionary[PACKAGE_SERIALIZATION_KEY_RESOLUTION] + os:dictionary[PACKAGE_SERIALIZATION_KEY_OS] + remoteURL:[NSURL URLWithString:dictionary[PACKAGE_SERIALIZATION_KEY_REMOTE_URL]]]; + + package.installURL = [NSURL fileURLWithPath:dictionary[PACKAGE_SERIALIZATION_KEY_INSTALL_URL]]; + package.status = (CCPackageStatus) [dictionary[PACKAGE_SERIALIZATION_KEY_STATUS] unsignedIntegerValue]; + + if (dictionary[PACKAGE_SERIALIZATION_KEY_LOCAL_DOWNLOAD_URL]) + { + package.localDownloadURL = [NSURL fileURLWithPath:dictionary[PACKAGE_SERIALIZATION_KEY_LOCAL_DOWNLOAD_URL]]; + } + + if (dictionary[PACKAGE_SERIALIZATION_KEY_LOCAL_UNZIP_URL]) + { + package.unzipURL = [NSURL fileURLWithPath:dictionary[PACKAGE_SERIALIZATION_KEY_LOCAL_UNZIP_URL]]; + } + + if (dictionary[PACKAGE_SERIALIZATION_KEY_FOLDER_NAME]) + { + package.folderName = dictionary[PACKAGE_SERIALIZATION_KEY_FOLDER_NAME]; + } + + if (dictionary[PACKAGE_SERIALIZATION_KEY_FOLDER_NAME]) + { + package.enableOnDownload = [dictionary[PACKAGE_SERIALIZATION_KEY_ENABLE_ON_DOWNLOAD] boolValue]; + } + + return package; +} + +- (NSString *)standardIdentifier +{ + return [NSString stringWithFormat:@"%@-%@-%@", _name, _os, _resolution]; +} + +- (NSDictionary *)toDictionary +{ + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + + dictionary[PACKAGE_SERIALIZATION_KEY_STATUS] = @(_status); + dictionary[PACKAGE_SERIALIZATION_KEY_NAME] = _name; + dictionary[PACKAGE_SERIALIZATION_KEY_RESOLUTION] = _resolution; + dictionary[PACKAGE_SERIALIZATION_KEY_OS] = _os; + dictionary[PACKAGE_SERIALIZATION_KEY_REMOTE_URL] = [_remoteURL absoluteString]; + dictionary[PACKAGE_SERIALIZATION_KEY_VERSION] = @(PACKAGE_SERIALIZATION_VERSION); + if (_installURL) + { + dictionary[PACKAGE_SERIALIZATION_KEY_INSTALL_URL] = [_installURL path]; + } + + if (_localDownloadURL) + { + dictionary[PACKAGE_SERIALIZATION_KEY_LOCAL_DOWNLOAD_URL] = [_localDownloadURL path]; + } + + if (_unzipURL) + { + dictionary[PACKAGE_SERIALIZATION_KEY_LOCAL_UNZIP_URL] = [_unzipURL path]; + } + + if (_folderName) + { + dictionary[PACKAGE_SERIALIZATION_KEY_FOLDER_NAME] = _folderName; + } + + dictionary[PACKAGE_SERIALIZATION_KEY_ENABLE_ON_DOWNLOAD] = @(_enableOnDownload); + + return dictionary; +} + +- (NSString *)statusToString +{ + switch (_status) + { + case CCPackageStatusInitial : + return @"Initial"; + case CCPackageStatusDownloading : + return @"Downloading"; + case CCPackageStatusDownloadPaused : + return @"Download Paused"; + case CCPackageStatusDownloaded : + return @"Downloaded"; + case CCPackageStatusUnzipping : + return @"Unzipping"; + case CCPackageStatusUnzipped : + return @"Unzipped"; + case CCPackageStatusInstalledEnabled : + return @"Installed/Enabled"; + case CCPackageStatusInstalledDisabled : + return @"Installed/Disabled"; + + default : return @"Unknown"; + } +} + +@end diff --git a/cocos2d/CCPackageCocos2dEnabler.h b/cocos2d/CCPackageCocos2dEnabler.h new file mode 100644 index 00000000000..be1837a3f67 --- /dev/null +++ b/cocos2d/CCPackageCocos2dEnabler.h @@ -0,0 +1,27 @@ +#import + +@interface CCPackageCocos2dEnabler : NSObject + +/** + * Enables packages by adding to cocos2d's search path and loading sprite sheets and filename lookups. + * Note: This method won't check a package's status and it is meant to be used for initialization + * of packages on startup by the CCPackagesManager. + * If a package is already among the search path then it won't be added again, spritesheets and file lookups not reloaded + * Sets the package's status to CCPackageStatusInstalledEnabled + * + * @param packages An array of CCPackage instance to be enabled + */ +- (void)enablePackages:(NSArray *)packages; + +/** + * Disables packages by removing them fromcocos2d's search path after that reloading sprite sheets and filename lookups + * of remaining search paths. + * Note: This method won't check a package's status and it is meant to be used for initialization + * of packages on startup by the CCPackagesManager. + * Sets the package's status to CCPackageStatusInstalledDisabled* + * + * @param packages An array of CCPackage instance to be disabled + */ +- (void)disablePackages:(NSArray *)array; + +@end diff --git a/cocos2d/CCPackageCocos2dEnabler.m b/cocos2d/CCPackageCocos2dEnabler.m new file mode 100644 index 00000000000..d87a80c0f37 --- /dev/null +++ b/cocos2d/CCPackageCocos2dEnabler.m @@ -0,0 +1,89 @@ +#import "CCPackageCocos2dEnabler.h" +#import "CCTextureCache.h" +#import "CCPackage.h" +#import "CCFileUtils.h" +#import "CCSpriteFrameCache.h" + + +@implementation CCPackageCocos2dEnabler + +- (BOOL)isPackagInSearchPath:(CCPackage *)package +{ + NSString *newPackagePath = package.installURL.path; + + return [[CCFileUtils sharedFileUtils].searchPath containsObject:newPackagePath]; +} + +- (void)enablePackages:(NSArray *)packages +{ + if ([self addPackagestoSearchPath:packages]); + { + CCLOGINFO(@"[PACKAGE/INSTALL][INFO] Enable packages - Search path: %@", [CCFileUtils sharedFileUtils].searchPath); + + [self reloadCocos2dFiles]; + } +} + +- (void)disablePackages:(NSArray *)array +{ + [self removePackagesFromSearchPath:array]; + + CCLOGINFO(@"[PACKAGE/INSTALL][INFO] Disable packages - Search path: %@", [CCFileUtils sharedFileUtils].searchPath); + + [self reloadCocos2dFiles]; +} + +- (BOOL)addPackagestoSearchPath:(NSArray *)packages +{ + BOOL searchPathChanged = NO; + + for (CCPackage *aPackage in packages) + { + NSAssert(aPackage.installURL != nil, @"aPackage.installURL must not be nil for package %@", aPackage); + + NSMutableArray *newSearchPath = [[CCFileUtils sharedFileUtils].searchPath mutableCopy]; + NSString *newPackagePath = aPackage.installURL.path; + + if (![newSearchPath containsObject:newPackagePath]) + { + [aPackage setValue:@(CCPackageStatusInstalledEnabled) forKey:NSStringFromSelector(@selector(status))]; + + [newSearchPath insertObject:newPackagePath atIndex:0]; + [CCFileUtils sharedFileUtils].searchPath = newSearchPath; + searchPathChanged = YES; + } + } + + return searchPathChanged; +} + +- (void)reloadCocos2dFiles +{ + [[CCFileUtils sharedFileUtils] purgeCachedEntries]; + [CCSpriteFrameCache purgeSharedSpriteFrameCache]; + [CCTextureCache purgeSharedTextureCache]; + + [[CCFileUtils sharedFileUtils] loadFileNameLookupsInAllSearchPathsWithName:@"fileLookup.plist"]; + + [[CCSpriteFrameCache sharedSpriteFrameCache] loadSpriteFrameLookupsInAllSearchPathsWithName:@"spriteFrameFileList.plist"]; +} + +- (void)removePackagesFromSearchPath:(NSArray *)packages +{ + for (CCPackage *aPackage in packages) + { + NSMutableArray *newSearchPath = [[CCFileUtils sharedFileUtils].searchPath mutableCopy]; + NSString *packagePathToRemove = aPackage.installURL.path; + + [aPackage setValue:@(CCPackageStatusInstalledDisabled) forKey:NSStringFromSelector(@selector(status))]; + + if ([newSearchPath containsObject:packagePathToRemove]) + { + [newSearchPath removeObject:packagePathToRemove]; + } + + [CCFileUtils sharedFileUtils].searchPath = newSearchPath; + } +} + +@end diff --git a/cocos2d/CCPackageConstants.h b/cocos2d/CCPackageConstants.h new file mode 100644 index 00000000000..90840b821e8 --- /dev/null +++ b/cocos2d/CCPackageConstants.h @@ -0,0 +1,22 @@ +// Errors +extern NSUInteger const PACKAGE_ERROR_DOWNLOAD_SERVER_RESPONSE_NOT_OK; + +extern NSUInteger const PACKAGE_ERROR_INSTALL_UNZIPPED_PACKAGE_NOT_FOUND; + +extern NSUInteger const PACKAGE_ERROR_INSTALL_COULD_NOT_MOVE_PACKAGE_TO_INSTALL_FOLDER; + +extern NSUInteger const PACKAGE_ERROR_INSTALL_PACKAGE_EMPTY; + +extern NSUInteger const PACKAGE_ERROR_INSTALL_PACKAGE_FOLDER_NAME_NOT_FOUND; + +extern NSUInteger const PACKAGE_ERROR_MANAGER_CANNOT_ENABLE_NON_DISABLED_PACKAGE; + +extern NSUInteger const PACKAGE_ERROR_MANAGER_CANNOT_DISABLE_NON_ENABLED_PACKAGE; + + +// Misc +extern NSString *const PACKAGE_REL_DOWNLOAD_FOLDER; + +extern NSString *const PACKAGE_REL_UNZIP_FOLDER; + +extern NSString *const PACKAGE_STORAGE_USERDEFAULTS_KEY; diff --git a/cocos2d/CCPackageConstants.m b/cocos2d/CCPackageConstants.m new file mode 100644 index 00000000000..6baf06e6b6b --- /dev/null +++ b/cocos2d/CCPackageConstants.m @@ -0,0 +1,25 @@ +#import + +// Errors +NSUInteger const PACKAGE_ERROR_DOWNLOAD_SERVER_RESPONSE_NOT_OK = 10000; + +NSUInteger const PACKAGE_ERROR_INSTALL_UNZIPPED_PACKAGE_NOT_FOUND = 10010; + +NSUInteger const PACKAGE_ERROR_INSTALL_COULD_NOT_MOVE_PACKAGE_TO_INSTALL_FOLDER = 10011; + +NSUInteger const PACKAGE_ERROR_INSTALL_PACKAGE_EMPTY = 10012; + +NSUInteger const PACKAGE_ERROR_INSTALL_PACKAGE_FOLDER_NAME_NOT_FOUND = 10013; + +NSUInteger const PACKAGE_ERROR_MANAGER_CANNOT_ENABLE_NON_DISABLED_PACKAGE = 10020; + +NSUInteger const PACKAGE_ERROR_MANAGER_CANNOT_DISABLE_NON_ENABLED_PACKAGE = 10021; + + +// Misc +NSString *const PACKAGE_REL_DOWNLOAD_FOLDER = @"com.cocos2d/Packages/Downloads"; + +NSString *const PACKAGE_REL_UNZIP_FOLDER = @"com.cocos2d/Packages/Unzipped"; + +NSString *const PACKAGE_STORAGE_USERDEFAULTS_KEY = @"cocos2d.packages"; + diff --git a/cocos2d/CCPackageDownload.h b/cocos2d/CCPackageDownload.h new file mode 100644 index 00000000000..12ad2825a74 --- /dev/null +++ b/cocos2d/CCPackageDownload.h @@ -0,0 +1,65 @@ +#import + +@protocol CCPackageDownloadDelegate; +@class CCPackage; + + +@interface CCPackageDownload : NSObject + +/** + * The URL of the package download, includes the filename + */ +@property (nonatomic, copy, readonly) NSURL *localURL; + +/** + * The package being downloaded + */ +@property (nonatomic, strong, readonly) CCPackage *package; + +/** + * Total bytes of the download. Might be 0 if no Content-Lenght header is sent by the + * host. + */ +@property (nonatomic, readonly) NSUInteger totalBytes; + +/** + * Bytes downloaded. + */ +@property (nonatomic, readonly) NSUInteger downloadedBytes; + +/** + * The delegate of the download. + */ +@property (nonatomic, weak) id delegate; + +/** + * Returns a new instance of a CCPackageDownload + * + * @param package The package that should be downloaded + * @param localURL The URL of the package download, includes the filename + * + * @return A new instance of a CCPackageDownload + */ +- (instancetype)initWithPackage:(CCPackage *)package localURL:(NSURL *)localURL; + +/** + * Starts the download, if there is a downloaded data the delegate is asked if the download should be resumed + */ +- (void)start; + +/** + * Stops the download and deletes the download file + */ +- (void)cancel; + +/** + * Stops the download + */ +- (void)pause; + +/** + * Resumes a download, otherwise starts from the beginning + */ +- (void)resume; + +@end diff --git a/cocos2d/CCPackageDownload.m b/cocos2d/CCPackageDownload.m new file mode 100644 index 00000000000..5e4a21b8d04 --- /dev/null +++ b/cocos2d/CCPackageDownload.m @@ -0,0 +1,483 @@ +#include + +#import "CCPackageDownload.h" +#import "CCPackageDownloadDelegate.h" +#import "CCPackage.h" +#import "CCPackageConstants.h" +#import "ccMacros.h" +#import "CCPackage_private.h" + +@interface CCPackageDownload() + +@property (nonatomic, strong) NSFileHandle *fileHandle; +@property (nonatomic, strong) NSURLConnection *connection; +@property (nonatomic, strong, readwrite) CCPackage *package; +@property (nonatomic, copy) NSString *tempPath; +@property (nonatomic, copy, readwrite) NSURL *localURL; + +@property (nonatomic, readwrite) NSUInteger totalBytes; +@property (nonatomic, readwrite) NSUInteger downloadedBytes; + +@property (nonatomic) NSUInteger fileSize; +@end + + +@implementation CCPackageDownload + +- (instancetype)initWithPackage:(CCPackage *)package localURL:(NSURL *)localURL +{ + NSAssert(package != nil, @"package must not be nil"); + NSAssert(localURL != nil, @"installURL must not be nil"); + + self = [super init]; + if (self) + { + self.package = package; + self.localURL = localURL; + self.fileSize = [self fileSizeOfDownload]; + self.tempPath = [[_localURL.path stringByDeletingLastPathComponent] stringByAppendingPathComponent:[self createTempName]]; + + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Package Download created: \n%@\n%@", _package, _localURL); + } + + return self; +} + +- (void)dealloc +{ + [_fileHandle closeFile]; +} + + +#pragma mark - actions + +- (void)start +{ + [self startDownloadAskingDelegateIfToResume:YES]; +} + +- (void)cancel +{ + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Cancelling"); + + [self closeConnectionAndFileHandle]; + + [self removeTempAndDownloadFile]; +} + +- (void)pause +{ + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Pause"); + + _package.status = CCPackageStatusDownloadPaused; + + [self closeConnectionAndFileHandle]; +} + +- (void)resume +{ + [self startDownloadAskingDelegateIfToResume:NO]; +} + + +#pragma mark - actions + +- (NSString *)createTempName +{ + return [NSString stringWithFormat:@"%@_%@", [self sha1:[_package.remoteURL absoluteString]], [_package standardIdentifier]]; +} + +- (NSString *)sha1:(NSString *)str +{ + const char *cStr = [str UTF8String]; + unsigned char result[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1(cStr, strlen(cStr), result); + NSString *hash = [NSString stringWithFormat: + @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + result[0], result[1], result[2], result[3], result[4], + result[5], result[6], result[7],result[8], result[9], + result[10], result[11], result[12], result[13], result[14], + result[15], result[16], result[17], result[18], result[19] + ]; + + return hash; +} + +- (void)createConnectionAndStartDownload +{ + NSURLRequest *request = [self createRequest]; + self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; + + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] starting download of %@", _package); + + _package.status = CCPackageStatusDownloading; + + [_connection start]; +} + +- (void)determineIfDownloadShouldBeResumedAndShouldAskDelegate:(BOOL)askDelegate +{ + BOOL shouldResume = _fileSize > 0; + BOOL delegateDecision = askDelegate + && [_delegate respondsToSelector:@selector(shouldResumeDownload:)] + && [_delegate shouldResumeDownload:self]; + + if (shouldResume + && (!askDelegate || delegateDecision)) + { + [_fileHandle seekToEndOfFile]; + } + else + { + [_fileHandle seekToFileOffset:0]; + self.fileSize = 0; + } +} + +- (NSUInteger)fileSizeOfDownload +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if (![fileManager fileExistsAtPath:_tempPath]) + { + return 0; + } + + NSError *error; + NSDictionary *attributes = [fileManager attributesOfItemAtPath:_tempPath error:&error]; + + if (error) + { + return 0; + } + + return [attributes[NSFileSize] unsignedIntegerValue]; +} + +- (void)startDownloadAskingDelegateIfToResume:(BOOL)askDelegate +{ + if ([self fileAlreadyDownloaded] + && [self keepDownload]) + { + [self finishDownload]; + return; + } + + if (_connection) + { + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Already downloading"); + return; + } + + NSError *error; + if (![self createFileHandle:&error]) + { + [self connection:_connection didFailWithError:error]; + return; + } + + self.fileSize = [self fileSizeOfDownload]; + + [self determineIfDownloadShouldBeResumedAndShouldAskDelegate:askDelegate]; + + self.downloadedBytes = _fileSize; + + [self createConnectionAndStartDownload]; +} + +- (BOOL)keepDownload +{ + if ([self shouldOverwriteAlreadyDownloadedFile]) + { + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Overwriting download file %@", _localURL); + [self removeDownloadFile]; + } + else + { + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Download file exists %@", _localURL); + [self closeConnectionAndFileHandle]; + + _package.status = CCPackageStatusDownloaded; + + if ([_delegate respondsToSelector:@selector(downloadFinished:)]) + { + [_delegate downloadFinished:self]; + } + return YES; + } + return NO; +} + +- (BOOL)fileAlreadyDownloaded +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + return [fileManager fileExistsAtPath:_localURL.path]; +} + +- (BOOL)shouldOverwriteAlreadyDownloadedFile +{ + return [_delegate respondsToSelector:@selector(shouldOverwriteDownloadedFile:)] + && [_delegate shouldOverwriteDownloadedFile:self]; +} + +- (void)removeTempAndDownloadFile +{ + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Removing download file: %@", _localURL); + + [_fileHandle closeFile]; + self.fileHandle = nil; + + [self removeDownloadFile]; + [self removeTempFile]; +} + +- (void)removeDownloadFile +{ + NSError *error; + if (![[NSFileManager defaultManager] removeItemAtURL:_localURL error:&error]) + { + CCLOG(@"[PACKAGE/DOWNLOAD][ERROR] Removing download file \"%@\" with error %@", _localURL, error); + } +} + +- (void)removeTempFile +{ + NSError *error; + if (![[NSFileManager defaultManager] removeItemAtPath:_tempPath error:&error]) + { + CCLOG(@"[PACKAGE/DOWNLOAD][ERROR] Removing temp file \"%@\" with error %@", _tempPath, error); + } +} + +- (NSURLRequest *)createRequest +{ + NSMutableURLRequest *result = [NSMutableURLRequest requestWithURL:_package.remoteURL]; + + if ([_delegate respondsToSelector:@selector(request:ofDownload:)]) + { + [_delegate request:result ofDownload:self]; + } + + if (_fileSize > 0) + { + NSString *requestRange = [NSString stringWithFormat:@"bytes=%d-", _fileSize]; + [result setValue:requestRange forHTTPHeaderField:@"Range"]; + } + + return result; +} + +- (BOOL)createFileHandle:(NSError **)error +{ + NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingToURL:[NSURL fileURLWithPath:_tempPath] error:error]; + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Opening/Creating file for download: %@", _tempPath); + + if (!fileHandle) + { + [[NSFileManager defaultManager] createFileAtPath:_tempPath contents:nil attributes:nil]; + fileHandle = [NSFileHandle fileHandleForWritingToURL:[NSURL fileURLWithPath:_tempPath] error:error]; + } + + if (!fileHandle) + { + CCLOG(@"[PACKAGE/DOWNLOAD][ERROR] %@, cannot open file for writing download %@", error, _tempPath); + return NO; + } + + self.fileHandle = fileHandle; + + return YES; +} + + +#pragma mark - NSURLConnectionDataDelegate + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + CCLOGINFO(@"[PACKAGE/DOWNLOAD][DEBUG]RESPONSE HEADERS: %@", [httpResponse allHeaderFields]); + + if ([httpResponse statusCode] >= 400) + { + [self cancel]; + [self forwardResponseErrorToDelegate:httpResponse]; + return; + } + + if ([self didServerRejectRangeRequest:httpResponse]) + { + [self restartDownload]; + return; + } + + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] response received - %d %@", [httpResponse statusCode], [NSHTTPURLResponse localizedStringForStatusCode:[httpResponse statusCode]]); + + if ([httpResponse expectedContentLength] != NSURLResponseUnknownLength) + { + self.totalBytes = [self extractTotalBytesFromResponse:httpResponse]; + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Download Content-Length: %u", _totalBytes); + + if (_fileSize == _totalBytes) + { + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Download already finished. Stopping download request."); + [self connectionDidFinishLoading:_connection]; + [self closeConnectionAndFileHandle]; + } + else if (_fileSize > _totalBytes) + { + CCLOG(@"[PACKAGE/DOWNLOAD][ERROR] Restarting download: Size mismatch: File is larger(%u) than expected download size(%u)", _fileSize, _totalBytes); + [self restartDownload]; + } + } + else + { + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] No Content-Length header set. totalBytes is 0."); + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSError *error; + if (![[NSFileManager defaultManager] moveItemAtURL:[NSURL fileURLWithPath:_tempPath] toURL:_localURL error:&error]) + { + [self connection:connection didFailWithError:error]; + return; + } + + [self finishDownload]; +} + +- (void)finishDownload +{ + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] Download finished"); + CCLOGINFO(@"[PACKAGE/DOWNLOAD][INFO] local file: %@", _localURL.path); + + [self closeConnectionAndFileHandle]; + + _package.status = CCPackageStatusDownloaded; + + [_delegate downloadFinished:self]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + if (!_fileHandle) + { + return; + } + + self.downloadedBytes += [data length]; + + // CCLOGINFO(@"[PACKAGE DOWNLOAD] [INFO] Download progress: %u / %u", _downloadedBytes, _totalBytes); + + @try + { + [_fileHandle writeData:data]; + + if ([_delegate respondsToSelector:@selector(downlowdProgress:downloadedBytes:totalBytes:)]) + { + [_delegate downlowdProgress:self downloadedBytes:_downloadedBytes totalBytes:_totalBytes]; + } + } + @catch (NSException *e) + { + CCLOG(@"[PACKAGE/DOWNLOAD][ERROR] writing to file %@", _tempPath); + + [self cancel]; + } +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + CCLOG(@"[PACKAGE/DOWNLOAD][ERROR] Download failed %@", error); + + [self cancel]; + + _package.status = CCPackageStatusDownloadFailed; + + [_delegate downloadFailed:self error:error]; +} + + +#pragma mark - Misc + +- (void)forwardResponseErrorToDelegate:(NSHTTPURLResponse *)httpResponse +{ + NSError *error = [NSError errorWithDomain:@"Cocos2d" + code:PACKAGE_ERROR_DOWNLOAD_SERVER_RESPONSE_NOT_OK + userInfo:@{ + NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Error: The host respondeded with status code %d.", [httpResponse statusCode]], + @"HTTPResponse" : httpResponse + }]; + + _package.status = CCPackageStatusDownloadFailed; + + [_delegate downloadFailed:self error:error]; +} + +- (NSUInteger)extractTotalBytesFromResponse:(NSHTTPURLResponse *)response +{ + NSDictionary *headers = [response allHeaderFields]; + if (headers[@"Content-Range"]) + { + NSString *rangeValue = headers[@"Content-Range"]; + + NSError *error; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"bytes \\d+-\\d+/(\\d+)" + options:NSRegularExpressionCaseInsensitive + error:&error]; + + NSTextCheckingResult *match = [regex firstMatchInString:rangeValue + options:NSMatchingAnchored + range:NSMakeRange(0, rangeValue.length)]; + + if (match.numberOfRanges == 2) + { + NSString *byteStr = [rangeValue substringWithRange:[match rangeAtIndex:1]]; + return (NSUInteger) [byteStr integerValue]; + } + } + + return (NSUInteger) [response expectedContentLength]; +} + +- (BOOL)didServerRejectRangeRequest:(NSHTTPURLResponse *)httpResponse +{ + return _fileSize > 0 + && (_fileSize != [httpResponse expectedContentLength]) + && ([httpResponse.allHeaderFields[@"Accept-Ranges"] isEqualToString:@"none"] + || !httpResponse.allHeaderFields[@"Accept-Ranges"]); +} + +- (void)restartDownload +{ + [_fileHandle truncateFileAtOffset:0]; + [_fileHandle seekToFileOffset:0]; + + self.downloadedBytes = 0; + + [self closeConnectionAndFileHandle]; + + [self startDownloadAskingDelegateIfToResume:NO]; +} + +- (void)closeConnectionAndFileHandle +{ + [_fileHandle closeFile]; + self.fileHandle = nil; + + [_connection cancel]; + self.connection = nil; + + self.fileSize = 0; +} + + +#pragma mark - debug + +- (NSString *)description +{ + return [NSString stringWithFormat:@"PACKAGE URL: %@, LOCAL URL: %@", _package.remoteURL, _localURL]; +} + +@end diff --git a/cocos2d/CCPackageDownloadDelegate.h b/cocos2d/CCPackageDownloadDelegate.h new file mode 100644 index 00000000000..6079125d65c --- /dev/null +++ b/cocos2d/CCPackageDownloadDelegate.h @@ -0,0 +1,67 @@ +#import + +@class CCPackageDownload; + +@protocol CCPackageDownloadDelegate + +@required + +/** + * Called when the http request finishes successfully + * + * @param download The download object that finished + */ +- (void)downloadFinished:(CCPackageDownload *)download; + +/** + * Called when the http request fails, for example a timeout, not a 404 returned. + * + * @param download The download object that failed + * @param error Error pointer to an error object containing details + */ +- (void)downloadFailed:(CCPackageDownload *)download error:(NSError *)error; + + +@optional + +/** + * Called whenever new bytes are received from the host. + * + * @param download The download object that reported progress + * @param downloadedBytes bytes downloaded + * @param totalBytes Total bytes downloaded. Total bytes can be 0 throughout a download if the response does not contain a Content-Length header. + */ +- (void)downlowdProgress:(CCPackageDownload *)download downloadedBytes:(NSUInteger)downloadedBytes totalBytes:(NSUInteger)totalBytes; + + +/** + * Return YES if a download should be resumed. If not implemented YES is default. + * Return NO if the download should start over. + * This method will only be invoked if there is a downloaded file + * + * @param download The download object that should be resumed + * + * @return whether a download should be resumed(YES) or started over(NO) + */ +- (BOOL)shouldResumeDownload:(CCPackageDownload *)download; + +/** + * Return whether a completed download with the same filename should be overwritten. + * If not implemented default is NO. + * + * @param download The download object which downloaded file should be overwritten. + * + * @return whether an already downloaded file should be overwritten(YES) or started over(NO) + */ +- (BOOL)shouldOverwriteDownloadedFile:(CCPackageDownload *)download; + +/** + * Provides the request before it is sent to let delegate adjust headers etc. + * If there is a partial download which should be resumed a Range header will be set after this invocation. + * + * @param request The request object that will be used for the download + * @param download The download object which will start with the given request + */ +- (void)request:(NSMutableURLRequest *)request ofDownload:(CCPackageDownload *)download; + +@end diff --git a/cocos2d/CCPackageDownloadManager.h b/cocos2d/CCPackageDownloadManager.h new file mode 100644 index 00000000000..73ab3ead90c --- /dev/null +++ b/cocos2d/CCPackageDownloadManager.h @@ -0,0 +1,78 @@ +#import +#import "CCPackageDownloadDelegate.h" + +@class CCPackage; +@protocol CCPackageDownloadManagerDelegate; + + +@interface CCPackageDownloadManager : NSObject + +/** + * All active downloads + */ +@property (nonatomic, strong, readonly) NSArray *allDownloads; + +/** + * The download folder path for all downloads. + * If the path does not exist it will be created. + * In case the creation of that new download path failed the value will remain unchanged. + */ +@property (nonatomic, copy) NSString *downloadPath; + +/** + * If downloads should be resumed if partial downloads found + * Default is NO + */ +@property (nonatomic) BOOL resumeDownloads; + +/** + * If a downloaded file should be overwritten in case the download is started over + * Default is NO + */ +@property (nonatomic) BOOL overwriteFinishedDownloads; + +/** + * Download manager's delegate + */ +@property (nonatomic, weak) id delegate; + +/** + * Creates a new download for a given package. + * A package cannot be enqueued twice as long as it is being downloaded already. + * + * @param package The package that should be downloaded + */ +- (void)enqueuePackageForDownload:(CCPackage *)package; + +/** + * Cancels a download of a given package. Downloaded file will be deleted. + * + * @param package The package that should be cancelled + */ +- (void)cancelDownloadOfPackage:(CCPackage *)package; + +/** + * Pause a download of a given package. + * + * @param package The package that should be paused + */ +- (void)pauseDownloadOfPackage:(CCPackage *)package; + +/** + * Resumes the download of a package given the host accepts range requests. + * + * @param package The package that should be resumed + */ +- (void)resumeDownloadOfPackage:(CCPackage *)package; + +/** + * Pauses all package downloads already enqueued. + */ +- (void)pauseAllDownloads; + +/** + * Resumes all package downloads already enqueued. + */ +- (void)resumeAllDownloads; + +@end diff --git a/cocos2d/CCPackageDownloadManager.m b/cocos2d/CCPackageDownloadManager.m new file mode 100644 index 00000000000..23139de0433 --- /dev/null +++ b/cocos2d/CCPackageDownloadManager.m @@ -0,0 +1,195 @@ +#import "CCPackageDownloadManager.h" +#import "CCPackage.h" +#import "CCPackageDownload.h" +#import "CCPackageDownloadManagerDelegate.h" +#import "CCPackageConstants.h" +#import "ccMacros.h" +#import "CCPackage_private.h" + + +@interface CCPackageDownloadManager() + +@property (nonatomic, strong) NSMutableArray *downloads; + +@end + + +@implementation CCPackageDownloadManager + +- (id)init +{ + self = [super init]; + + if (self) + { + self.downloads = [NSMutableArray array]; + self.downloadPath = [NSTemporaryDirectory() stringByAppendingPathComponent:PACKAGE_REL_DOWNLOAD_FOLDER]; + self.resumeDownloads = NO; + self.overwriteFinishedDownloads = NO; + } + + return self; +} + +- (void)setDownloadPath:(NSString *)newDownloadPath +{ + if ([_downloadPath isEqualToString:newDownloadPath]) + { + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:newDownloadPath]) + { + NSError *error; + if (![fileManager createDirectoryAtPath:newDownloadPath + withIntermediateDirectories:YES + attributes:nil + error:&error]) + { + CCLOG(@"[PACKAGE/DOWNLOAD][ERROR] Setting installation path to %@ - %@", newDownloadPath, error); + return; + } + } + + [self willChangeValueForKey:@"downloadPath"]; + _downloadPath = [newDownloadPath copy]; + [self didChangeValueForKey:@"downloadPath"]; +} + +- (NSArray *)allDownloads +{ + return _downloads; +} + +- (void)enqueuePackageForDownload:(CCPackage *)package +{ + if (package.status == CCPackageStatusDownloadPaused) + { + [self resumeDownloadOfPackage:package]; + return; + } + + if ([self packageDownloadForPackage:package]) + { + return; + } + + if (!(package.status == CCPackageStatusDownloadFailed + || package.status == CCPackageStatusInitial)) + { + return; + } + + NSString *fileName = [[package standardIdentifier] stringByAppendingPathExtension:@"zip"]; + + CCPackageDownload *packageDownload = [[CCPackageDownload alloc] initWithPackage:package + localURL:[NSURL fileURLWithPath:[_downloadPath stringByAppendingPathComponent:fileName]]]; + + package.localDownloadURL = [NSURL fileURLWithPath:[_downloadPath stringByAppendingPathComponent:fileName]]; + packageDownload.delegate = self; + + [_downloads addObject:packageDownload]; + + CCLOGINFO(@"[PACKAGE/DOWNLOADS][INFO] Download enqueued of package %@.", package); + + [packageDownload start]; +} + +- (void)cancelDownloadOfPackage:(CCPackage *)package +{ + CCPackageDownload *packageDownload = [self packageDownloadForPackage:package]; + [_downloads removeObject:packageDownload]; + + [packageDownload cancel]; +} + +- (CCPackageDownload *)packageDownloadForPackage:(CCPackage *)aPackage +{ + for (CCPackageDownload *download in _downloads) + { + if (download.package == aPackage) + { + return download; + } + } + + return nil; +} + +- (void)pauseDownloadOfPackage:(CCPackage *)package +{ + CCLOGINFO(@"[PACKAGE/DOWNLOADS][INFO] Pausing download of package %@.", package); + CCPackageDownload *packageDownload = [self packageDownloadForPackage:package]; + [packageDownload pause]; +} + +- (void)resumeDownloadOfPackage:(CCPackage *)package +{ + CCLOGINFO(@"[PACKAGE/DOWNLOADS][INFO] Resuming download of package %@.", package); + CCPackageDownload *packageDownload = [self packageDownloadForPackage:package]; + [packageDownload resume]; +} + +- (void)pauseAllDownloads +{ + CCLOGINFO(@"[PACKAGE/DOWNLOADS][INFO] Pausing all downloads."); + for (CCPackageDownload *download in _downloads) + { + [download pause]; + } +} + +- (void)resumeAllDownloads +{ + CCLOGINFO(@"[PACKAGE/DOWNLOADS][INFO] Resuming all downloads."); + for (CCPackageDownload *download in _downloads) + { + [download resume]; + } +} + + +#pragma mark - CCPackageDownloadDelegate + +- (void)downloadFinished:(CCPackageDownload *)download +{ + [_downloads removeObject:download]; + + [_delegate downloadFinishedOfPackage:download.package]; +} + +- (void)downloadFailed:(CCPackageDownload *)download error:(NSError *)error +{ + [_downloads removeObject:download]; + + [_delegate downloadFailedOfPackage:download.package error:error]; +} + +- (void)downlowdProgress:(CCPackageDownload *)download downloadedBytes:(NSUInteger)downloadedBytes totalBytes:(NSUInteger)totalBytes +{ + if ([_delegate respondsToSelector:@selector(downloadProgressOfPackage:downloadedBytes:totalBytes:)]) + { + [_delegate downloadProgressOfPackage:download.package downloadedBytes:downloadedBytes totalBytes:totalBytes]; + } +} + +- (BOOL)shouldResumeDownload:(CCPackageDownload *)download +{ + return _resumeDownloads; +} + +- (BOOL)shouldOverwriteDownloadedFile:(CCPackageDownload *)download +{ + return _overwriteFinishedDownloads; +} + +- (void)request:(NSMutableURLRequest *)request ofDownload:(CCPackageDownload *)download +{ + if ([_delegate respondsToSelector:@selector(request:ofPackage:)]) + { + [_delegate request:request ofPackage:download.package]; + } +} + +@end diff --git a/cocos2d/CCPackageDownloadManagerDelegate.h b/cocos2d/CCPackageDownloadManagerDelegate.h new file mode 100644 index 00000000000..e4a140f1faf --- /dev/null +++ b/cocos2d/CCPackageDownloadManagerDelegate.h @@ -0,0 +1,46 @@ +#import + +@class CCPackage; + +@protocol CCPackageDownloadManagerDelegate + +@required + +/** + * Called when the http request of a package download finishes successfully + * + * @param package The package downloaded successfully + */ +- (void)downloadFinishedOfPackage:(CCPackage *)package; + +/** + * Called when the http request of a package download failed + * + * @param package The package which download failed + * @param error Error pointer to an error object containing details + */ +- (void)downloadFailedOfPackage:(CCPackage *)package error:(NSError *)error; + + +@optional + +/** + * Returns the bytes downloaded and the total bytes of a package download. + * Total bytes can be 0 throughout a download if the response does not contain a Content-Length header. + * + * @param package The package which download reported progress + * @param downloadedBytes bytes downloaded + * @param totalBytes Total bytes downloaded. Total bytes can be 0 throughout a download if the response does not contain a Content-Length header. + */ +- (void)downloadProgressOfPackage:(CCPackage *)package downloadedBytes:(NSUInteger)downloadedBytes totalBytes:(NSUInteger)totalBytes; + +/** + * Provides the request before it is sent to let delegate adjust headers etc. + * If there is a partial download which should be resumed a Range header will be set after this invocation. + * + * @param request The request object that will be used for the download + * @param download The download object which will start with the given request + */ +- (void)request:(NSMutableURLRequest *)request ofPackage:(CCPackage *)package; + +@end diff --git a/cocos2d/CCPackageHelper.h b/cocos2d/CCPackageHelper.h new file mode 100644 index 00000000000..b7f04e0714c --- /dev/null +++ b/cocos2d/CCPackageHelper.h @@ -0,0 +1,25 @@ +#import + + +@interface CCPackageHelper : NSObject + +/** + * Returns the current OS, possible values: iOS, Android and Mac + */ ++ (NSString *)currentOS; + +/** + * Returns a spritebuilder resolution(phone, phonehd, tablet, tablethd) for a CCFileUtil's device/resolution suffix like CCFileUtilsSuffixiPadHD + * If none given or not found it'll return nil. + * + * @param suffix + */ ++ (NSString *)ccFileUtilsSuffixToResolution:(NSString *)suffix; + +/** + * Returns the preferred Sprite Builder resolution(phone, phonehd, tablet, tablethd) for the current cocos2d setup by looking at the entries + * in CCFileUtils searchResolutionsOrder array. If none can be found or mapped phonehd will be returned as default. + */ ++ (NSString *)defaultResolution; + +@end \ No newline at end of file diff --git a/cocos2d/CCPackageHelper.m b/cocos2d/CCPackageHelper.m new file mode 100644 index 00000000000..44677e1fceb --- /dev/null +++ b/cocos2d/CCPackageHelper.m @@ -0,0 +1,67 @@ +#import +#import "CCPackageHelper.h" +#import "CCFileUtils.h" + + +@implementation CCPackageHelper + ++ (NSString *)currentOS +{ +#ifdef __CC_PLATFORM_IOS + return @"iOS"; + +#elif defined(__CC_PLATFORM_MAC) + return @"Mac"; + +#elif defined(__CC_PLATFORM_ANDROID) + return @"Android"; + +#endif + return @"iOS"; +} + ++ (NSString *)ccFileUtilsSuffixToResolution:(NSString *)suffix +{ + if ([suffix isEqualToString:CCFileUtilsSuffixiPhone5HD] + || [suffix isEqualToString:CCFileUtilsSuffixiPhone5] + || [suffix isEqualToString:CCFileUtilsSuffixiPhoneHD] + || [suffix isEqualToString:CCFileUtilsSuffixDefault]) + { + return @"phonehd"; + } + + if ([suffix isEqualToString:CCFileUtilsSuffixiPhone]) + { + return @"phone"; + } + + if ([suffix isEqualToString:CCFileUtilsSuffixiPadHD] + || [suffix isEqualToString:CCFileUtilsSuffixMacHD]) + { + return @"tablethd"; + } + + if ([suffix isEqualToString:CCFileUtilsSuffixMac] + || [suffix isEqualToString:CCFileUtilsSuffixiPad]) + { + return @"tablet"; + } + + return nil; +} + ++ (NSString *)defaultResolution +{ + for (NSString *resolution in [CCFileUtils sharedFileUtils].searchResolutionsOrder) + { + NSString *result = [CCPackageHelper ccFileUtilsSuffixToResolution:resolution]; + if (result) + { + return result; + } + } + + return @"phonehd"; +} + +@end diff --git a/cocos2d/CCPackageInstaller.h b/cocos2d/CCPackageInstaller.h new file mode 100644 index 00000000000..63edea47760 --- /dev/null +++ b/cocos2d/CCPackageInstaller.h @@ -0,0 +1,37 @@ +#import + +@class CCPackage; + +@interface CCPackageInstaller : NSObject + +/** + * The package that should be installed + */ +@property (nonatomic, strong, readonly) CCPackage *package; + +/** + * The local installation path for the package + */ +@property (nonatomic, copy, readonly) NSString *installPath; + +/** + * Returns a new instance of a CCPackageInstaller + * + * @param package The package that should be installed + * @param installPath The path to the folder where the package should be installed to + * + * @return A new instance of a CCPackageInstaller + */ +- (instancetype)initWithPackage:(CCPackage *)package installPath:(NSString *)installPath; + +/** + * Installs the package. The contents of the unzipped packages folder are moved to the + * installPath folder. + * + * @param error Error pointer to an error object containing details if installation failed + * + * @return Whether the installation was successful(YES) or not(NO) + */ +- (BOOL)installWithError:(NSError **)error; + +@end diff --git a/cocos2d/CCPackageInstaller.m b/cocos2d/CCPackageInstaller.m new file mode 100644 index 00000000000..ae3b6f1a849 --- /dev/null +++ b/cocos2d/CCPackageInstaller.m @@ -0,0 +1,138 @@ +#import "CCPackageInstaller.h" +#import "CCPackageConstants.h" +#import "CCPackage.h" +#import "ccMacros.h" + + +@interface CCPackageInstaller () + +@property (nonatomic, strong, readwrite) CCPackage *package; +@property (nonatomic, copy, readwrite) NSString *installPath; + +@end + + +@implementation CCPackageInstaller + +- (instancetype)initWithPackage:(CCPackage *)package installPath:(NSString *)installPath +{ + NSAssert(package != nil, @"package must not be nil"); + NSAssert(installPath != nil, @"installPath must not be nil"); + + self = [super init]; + if (self) + { + self.package = package; + self.installPath = installPath; + } + + return self; +} + +- (BOOL)installWithError:(NSError **)error +{ + +/* TODO + CCPackageInstallData *installData = [_package installData]; + + NSAssert(installData != nil, @"installData must not be nil"); + NSAssert(installData.unzipURL != nil, @"installData.unzipURL must not be nil"); +*/ + + if (![self unzippedPackageFolderExists:error]) + { + [_package setValue:@(CCPackageStatusInstallationFailed) forKey:@"status"]; + return NO; + } + + if (![self movePackageToInstallPathWithError:error]) + { + [_package setValue:@(CCPackageStatusInstallationFailed) forKey:@"status"]; + return NO; + } + + [_package setValue:@(CCPackageStatusInstalledDisabled) forKey:@"status"]; + + return YES; +} + +- (BOOL)movePackageToInstallPathWithError:(NSError **)error +{ + // TODO + // CCPackageInstallData *installData = [_package installData]; + + NSAssert(_package.unzipURL != nil, @"package.unzipURL must not be nil."); + NSAssert(_package.folderName != nil, @"package.folderName must not be nil."); + + [_package setValue:[NSURL fileURLWithPath:[_installPath stringByAppendingPathComponent:_package.folderName]] forKey:@"installURL"]; + + NSError *errorMove; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager moveItemAtPath:[_package.unzipURL.path stringByAppendingPathComponent:_package.folderName] + toPath:_package.installURL.path + error:&errorMove]) + { + [_package setValue:nil forKey:@"installURL"]; + + [self setNewError:error + code:PACKAGE_ERROR_INSTALL_COULD_NOT_MOVE_PACKAGE_TO_INSTALL_FOLDER + message:[NSString stringWithFormat:@"Could not move package to install path \"%@\", underlying error: %@", _installPath, errorMove] + underlyingError:errorMove]; + + CCLOG(@"[PACKAGE/INSTALL][ERROR] Moving unzipped package to installation folder: %@", *error); + + return NO; + } + + return YES; +} + +- (BOOL)unzippedPackageFolderExists:(NSError **)error +{ +/* TODO + CCPackageInstallData *installData = [_package installData]; + NSAssert(installData.unzipURL, @"installData.unzipURL must not be nil"); +*/ + NSAssert(_package.unzipURL != nil, @"package.unzipURL must not be nil."); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:_package.unzipURL.path]) + { + [self setNewError:error + code:PACKAGE_ERROR_INSTALL_UNZIPPED_PACKAGE_NOT_FOUND + message:[NSString stringWithFormat:@"Package to install not found at path \"%@\"", _package.unzipURL]]; + + CCLOG(@"[PACKAGE/INSTALL][ERROR] Moving unzipped package to installation folder, package already exists! %@", *error); + return NO; + } + return YES; +} + +- (void)setNewError:(NSError **)errorPtr code:(NSInteger)code message:(NSString *)message underlyingError:(NSError *)underlyingError +{ + NSMutableDictionary *userInfo = [@{NSLocalizedDescriptionKey : message, @"package" : _package} mutableCopy]; + if (underlyingError) + { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + + NSError *error = [NSError errorWithDomain:@"cocos2d" + code:code + userInfo:userInfo]; + + if (errorPtr) + { + *errorPtr = error; + } + else + { + CCLOG(@"[PACKAGE/INSTALL][ERROR] Error pointer not set."); + } +} + +- (void)setNewError:(NSError **)errorPtr code:(NSInteger)code message:(NSString *)message +{ + [self setNewError:errorPtr code:code message:message underlyingError:nil]; +} + +@end diff --git a/cocos2d/CCPackageManager.h b/cocos2d/CCPackageManager.h new file mode 100644 index 00000000000..89ca1a39332 --- /dev/null +++ b/cocos2d/CCPackageManager.h @@ -0,0 +1,213 @@ +#import +#import "CCPackageDownloadManagerDelegate.h" +#import "CCPackageUnzipperDelegate.h" + +@class CCPackage; +@protocol CCPackageManagerDelegate; + + +@interface CCPackageManager : NSObject + +/** + * The path where all installed packages are stored. Default is /Library/Caches/Packages + */ +@property (nonatomic, copy) NSString *installedPackagesPath; + +/** + * URL used as base to locate packages. A package standard identifier is added to create a full URL. + * BaseURL is only used in conjunction with downloadPackageWithName:resolution:enableAfterDownload. More details below. + */ +@property (nonatomic, copy) NSURL *baseURL; + +/** + * Returns all packages managed by the CCPackageManager + */ +@property (nonatomic, readonly) NSArray *allPackages; + +/** + * If downloads should be resumed if partial downloads found + * Default is YES + */ +@property (nonatomic) BOOL resumeDownloads; + +/** + * Package manager's delegate + */ +@property (nonatomic, weak) id delegate; + +/** + * The queue on which unzipping of packages is achieved, default is DISPATCH_QUEUE_PRIORITY_LOW. + * On iOS 5.0, MacOS 10.7 and below you have to get rid of the queue after use if it's not a global one. + * If set to nil, queue will be reset to default. + */ +#if OS_OBJECT_HAVE_OBJC_SUPPORT == 1 +@property (nonatomic, strong) dispatch_queue_t unzippingQueue; +#else +@property (nonatomic) dispatch_queue_t unzippingQueue; +#endif + +/** + * Returns a shared instance of the CCPackageManager, this is the suggested way to use the manager. + */ ++ (CCPackageManager *)sharedManager; + +/** + * Loads all packages from user defaults. Supposed to be invoked after app finished launching and Cocos2d has been set up. + */ +- (void)loadPackages; + +/** + * Persists all packages to user defaults. Save often! Suggestion is to save on Application will terminate and will enter background. + */ +- (void)savePackages; + +/** + * Returns a package identified by name. Resolution and OS are determined implicitly. + * + * @param name Name of the package + */ +- (CCPackage *)packageWithName:(NSString *)name; + +/** + * The all inclsuive method to add a package to your app. + * Returns a new package immediately which will be downloaded, unzipped and installed asynchronously to the Packages folder in /Library/Caches (default) + * OS and resolution are determined implicitly. Resolution is derived from CCFileUtils' searchResolutionsOrder first entry. + * + * If a package with the same name and resolution already exists it won't be rescheduled for downloading. + * If you need to update a package by re-downloading it you will have to delete it first. + * The various delegate callbacks provide feedback about the current steps of the whole process. + * You can KVO the package's status property as well, which will change during the cause of the whole process. + * The URL is determined by the baseURL property and the standard identifier created by the name, os and resolution. + * Example: base is http://foo, name: DLC, os: iOS (determined by manager), resolution: phonehd => http://foo/DLC-iOS-phonehd.zip + * + * @param name Name of the package + * @param enableAfterDownload If the package should be enabled in cocos2d after download. You can enable it with the enablePackage: method later on. + */ +- (CCPackage *)downloadPackageWithName:(NSString *)name + enableAfterDownload:(BOOL)enableAfterDownload; + +/** + * Like the method above. Instead of using the baseURL, name and resolution you can provide the URL directly. + * Returns a new package immediately which will be downloaded, unzipped and installed asynchronously to the Packages folder in /Library/Caches (default) + * OS is determined implicitly. + * + * @param name Name of the package + * @param resolution Resolution of the package, e.g. phonehd, tablethd etc. + * @param enableAfterDownload If the package should be enabled in cocos2d after download. You can enable it with the enablePackage: method later on. + */ +- (CCPackage *)downloadPackageWithName:(NSString *)name + resolution:(NSString *)resolution + enableAfterDownload:(BOOL)enableAfterDownload; + +/** + * Like the method above. Instead of using the baseURL and name you can provide the URL directly. + * OS and resolution are determined implicitly. Resolution is derived from CCFileUtils' searchResolutionsOrder first entry. + * + * @param name Name of the package + * @param remoteURL URL of the package to be downloaded + * @param enableAfterDownload If the package should be enabled in cocos2d after download. You can enable it with the enablePackage: method later on. + */ +- (CCPackage *)downloadPackageWithName:(NSString *)name + remoteURL:(NSURL *)remoteURL + enableAfterDownload:(BOOL)enableAfterDownload; + +/** + * Like the method above. Instead of using the baseURL, name and resolution you can provide the URL directly. + * OS is determined implicitly. + * + * @param name Name of the package + * @param resolution Resolution of the package, e.g. phonehd, tablethd etc. + * @param remoteURL URL of the package to be downloaded + * @param enableAfterDownload If the package should be enabled in cocos2d after download. You can enable it with the enablePackage: method later on. + */ +- (CCPackage *)downloadPackageWithName:(NSString *)name + resolution:(NSString *)resolution + remoteURL:(NSURL *)remoteURL + enableAfterDownload:(BOOL)enableAfterDownload; + +/** + * Downloads a package. This is supposed to work in conjunction with addPackage where a package is created without the package manager + * and should become managed. + * A download will only start if the status is CCPackageStatusInitial, CCPackageStatusDownloadFailed. + * A package with status CCPackageStatusDownloadPaused will be resumed if possible. + * + * @param name The package to be manager by the package manager + * @param enableAfterDownload If the package should be enabled in cocos2d after download. You can enable it with the enablePackage: method later on. + */ +- (BOOL)downloadPackage:(CCPackage *)package enableAfterDownload:(BOOL)enableAfterDownload; + +/** + * Disables a package. Only packages with state CCPackageStatusInstalledEnabled can be disabled. + * The package is removed from cocos2d's search, sprite sheets and filename lookups are reloaded. + * + * @param package The package to be disabled + * @param error Error pointer with details about a failed operation + * + * @return Success(YES) or failure(NO) of the operation + */ +- (BOOL)disablePackage:(CCPackage *)package error:(NSError **)error; + +/** + * Enables a package. Only packages with state CCPackageStatusInstalledDisabled can be enabled. + * + * The package is added to cocos2d's search, sprite sheets getting loaded as well as filename lookups + * + * @param package The package to be enabled + * @param error Error pointer with details about a failed operation + * + * @return Success(YES) or failure(NO) of the operation + */ +- (BOOL)enablePackage:(CCPackage *)package error:(NSError **)error; + +/** + * Adds a package to the package manager. Only packages with status initial can be added. + * + * @param package The package to be added to the package manager + */ +- (void)addPackage:(CCPackage *)package; + +/** + * Deletes a package. + * Will disable the package first and delete it from disk. Temp download and unzip files will be removed as well. + * + * @param package The package to be deleted + * @param error Error pointer with details about a failed operation + * + * @return Success(YES) or failure(NO) of the operation + */ +- (BOOL)deletePackage:(CCPackage *)package error:(NSError **)error; + +/** + * Cancels the download of a package. + * This will remove the package from the download manager as well from the package manager if the status is in one of the download states: + * CCPackageStatusDownloadPaused, CCPackageStatusDownloading, CCPackageStatusDownloaded, CCPackageStatusDownloadFailed + * + * @param package The package which download should be cancelled + */ +- (void)cancelDownloadOfPackage:(CCPackage *)package; + +/** + * Pauses the download of a package. + * + * @param package The package which download should be paused + */ +- (void)pauseDownloadOfPackage:(CCPackage *)package; + +/** + * Resumes the download of a package. + * + * @param package The package which download should be resumed + */ +- (void)resumeDownloadOfPackage:(CCPackage *)package; + +/** + * Pauses all downloads of packages + */ +- (void)pauseAllDownloads; + +/** + * Resumes all downloads of packages + */ +- (void)resumeAllDownloads; + +@end diff --git a/cocos2d/CCPackageManager.m b/cocos2d/CCPackageManager.m new file mode 100644 index 00000000000..7a1d9ca8811 --- /dev/null +++ b/cocos2d/CCPackageManager.m @@ -0,0 +1,740 @@ +#import "CCPackageManager.h" +#import "CCPackage.h" +#import "CCPackageDownloadManager.h" +#import "CCPackageUnzipper.h" +#import "CCPackageInstaller.h" +#import "CCPackageManagerDelegate.h" +#import "CCPackageConstants.h" +#import "CCPackageCocos2dEnabler.h" +#import "ccMacros.h" +#import "CCPackageHelper.h" +#import "CCPackage_private.h" + + +@interface CCPackageManager() + +@property (nonatomic, strong) NSMutableArray *packages; +@property (nonatomic, strong) NSMutableArray *unzipTasks; +@property (nonatomic, strong) CCPackageDownloadManager *downloadManager; +@property (nonatomic) BOOL initialized; + +@end + + +@implementation CCPackageManager + ++ (CCPackageManager *)sharedManager +{ + static CCPackageManager *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + sharedInstance = [[CCPackageManager alloc] init]; + }); + return sharedInstance; +} + +- (id)init +{ + self = [super init]; + if (self) + { + self.initialized = NO; + + self.packages = [NSMutableArray array]; + self.unzipTasks = [NSMutableArray array]; + + NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; + self.installedPackagesPath = [cachesPath stringByAppendingPathComponent:@"Packages"]; + + self.downloadManager = [[CCPackageDownloadManager alloc] init]; + _downloadManager.delegate = self; + _downloadManager.resumeDownloads = YES; + + self.unzippingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); + } + + return self; +} + +- (NSArray *)allPackages +{ + return _packages; +} + +- (void)loadPackages +{ + if (_initialized) + { + return; + } + + [self loadPackagesFromUserDefaults]; + + [self enablePackages]; + + [self resumePausedDownloads]; + + [self restartUnzippingTasks]; + + CCLOGINFO(@"[PACKAGES] Packages loaded (%u): %@", _packages.count, _packages); + + self.initialized = YES; +} + +- (void)restartUnzippingTasks +{ + for (CCPackage *aPackage in _packages) + { + CCPackageUnzipper *unzipper = [self unzipperForPackage:aPackage]; + if (unzipper) + { + continue; + } + + if (aPackage.status == CCPackageStatusUnzipped + || aPackage.status == CCPackageStatusUnzipping) + { + [self unzipPackage:aPackage]; + } + } +} + +- (CCPackageUnzipper *)unzipperForPackage:(CCPackage *)aPackage +{ + for (CCPackageUnzipper *packageUnzipper in _unzipTasks) + { + if (packageUnzipper.package == aPackage) + { + return packageUnzipper; + } + } + + return nil; +} + +- (void)resumePausedDownloads +{ + for (CCPackage *aPackage in _packages) + { + if (aPackage.status == CCPackageStatusDownloadPaused) + { + [_downloadManager enqueuePackageForDownload:aPackage]; + } + } +} + +- (void)loadPackagesFromUserDefaults +{ + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSArray *packages = [userDefaults objectForKey:PACKAGE_STORAGE_USERDEFAULTS_KEY]; + + CCLOGINFO(@"[PACKAGE][INFO] Packages info loading from userdefaults..."); + for (NSDictionary *aPackageDict in packages) + { + CCPackage *aPackage = [[CCPackage alloc] initWithDictionary:aPackageDict]; + +/* TODO + CCPackageInstallData *installData = [[CCPackageInstallData alloc] initWithPackage:aPackage]; + [aPackage setInstallData:installData]; + [installData populateInstallDataWithDictionary:aPackageDict]; +*/ + + [_packages addObject:aPackage]; + CCLOGINFO(@"[PACKAGE][INFO] Package info added: %@: %@", [aPackage standardIdentifier], [aPackage statusToString]); + } +} + +- (void)enablePackages +{ + NSMutableArray *packagesToEnable = [NSMutableArray array]; + for (CCPackage *aPackage in _packages) + { + if (aPackage.status == CCPackageStatusInstalledEnabled) + { + [packagesToEnable addObject:aPackage]; + } + } + + CCPackageCocos2dEnabler *packageCocos2dEnabler = [[CCPackageCocos2dEnabler alloc] init]; + [packageCocos2dEnabler enablePackages:packagesToEnable]; +} + +- (void)savePackages +{ + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSMutableArray *packagesToSave = [NSMutableArray arrayWithCapacity:_packages.count]; + + for (CCPackage *aPackage in _packages) + { + [packagesToSave addObject:[aPackage toDictionary]]; + } + + [userDefaults setObject:packagesToSave forKey:PACKAGE_STORAGE_USERDEFAULTS_KEY]; + [userDefaults synchronize]; +} + +- (void)setInstalledPackagesPath:(NSString *)installedPackagesPath +{ + if ([_installedPackagesPath isEqualToString:installedPackagesPath]) + { + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:_installedPackagesPath]) + { + NSError *error; + if (![fileManager createDirectoryAtPath:installedPackagesPath + withIntermediateDirectories:YES + attributes:nil + error:&error]) + { + CCLOGINFO(@"[PACKAGE][Error] Setting installation path to %@ - %@", installedPackagesPath, error); + return; + } + } + + [self willChangeValueForKey:@"installedPackagesPath"]; + _installedPackagesPath = installedPackagesPath; + [self didChangeValueForKey:@"installedPackagesPath"]; +} + +- (void)setUnzippingQueue:(dispatch_queue_t)unzippingQueue +{ + if (!unzippingQueue) + { + _unzippingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); + return; + } + + _unzippingQueue = unzippingQueue; +} + +- (void)setResumeDownloads:(BOOL)resumeDownloads +{ + _downloadManager.resumeDownloads = resumeDownloads; +} + +- (BOOL)resumeDownloads +{ + return _downloadManager.resumeDownloads; +} + + +#pragma mark - download + +- (CCPackage *)downloadPackageWithName:(NSString *)name enableAfterDownload:(BOOL)enableAfterDownload +{ + return [self downloadPackageWithName:name + resolution:[CCPackageHelper defaultResolution] + enableAfterDownload:enableAfterDownload]; +} + +- (CCPackage *)downloadPackageWithName:(NSString *)name resolution:(NSString *)resolution enableAfterDownload:(BOOL)enableAfterDownload +{ + NSAssert(_baseURL != nil, @"baseURL must not be nil"); + + NSString *packageName = [NSString stringWithFormat:@"%@-%@-%@.zip", name, [CCPackageHelper currentOS], resolution]; + NSURL *remoteURL = [_baseURL URLByAppendingPathComponent:packageName]; + + return [self downloadPackageWithName:name resolution:resolution remoteURL:remoteURL enableAfterDownload:enableAfterDownload]; +} + +- (BOOL)downloadPackage:(CCPackage *)package enableAfterDownload:(BOOL)enableAfterDownload +{ + if (![_packages containsObject:package]) + { + return NO; + } + + NSAssert(package, @"package must not be nil"); + NSAssert(package.name, @"package.name must not be nil"); + NSAssert(package.resolution, @"package.resolution must not be nil"); + + if (!package.remoteURL && !_baseURL) + { + return NO; + } + else if (!package.remoteURL) + { + NSString *packageName = [NSString stringWithFormat:@"%@-%@-%@.zip", package.name, package.os, package.resolution]; + NSURL *remoteURL = [_baseURL URLByAppendingPathComponent:packageName]; + [package setValue:remoteURL forKey:@"remoteURL"]; + } + + package.enableOnDownload = enableAfterDownload; + + CCLOGINFO(@"[PACKAGE][INFO]: adding package to download queue: %@", package); + + [_downloadManager enqueuePackageForDownload:package]; + + return YES; +} + +- (CCPackage *)downloadPackageWithName:(NSString *)name remoteURL:(NSURL *)remoteURL enableAfterDownload:(BOOL)enableAfterDownload +{ + return [self downloadPackageWithName:name + resolution:[CCPackageHelper defaultResolution] + remoteURL:remoteURL + enableAfterDownload:enableAfterDownload]; +} + +- (CCPackage *)downloadPackageWithName:(NSString *)name resolution:(NSString *)resolution remoteURL:(NSURL *)remoteURL enableAfterDownload:(BOOL)enableAfterDownload +{ + CCPackage *aPackage = [self packageWithName:name resolution:resolution]; + if (aPackage) + { + return aPackage; + } + + CCPackage *package = [[CCPackage alloc] initWithName:name resolution:resolution remoteURL:remoteURL]; + package.enableOnDownload = enableAfterDownload; + + [_packages addObject:package]; + + CCLOGINFO(@"[PACKAGE][INFO]: adding package to download queue: %@", package); + + [_downloadManager enqueuePackageForDownload:package]; + + return package; +} + +- (CCPackage *)packageWithName:(NSString *)name +{ + NSString *resolution = [CCPackageHelper defaultResolution]; + + return [self packageWithName:name resolution:resolution]; +} + +- (CCPackage *)packageWithName:(NSString *)name resolution:(NSString *)resolution +{ + for (CCPackage *aPackage in _packages) + { + if ([aPackage.name isEqualToString:name] + && [aPackage.resolution isEqualToString:resolution]) + { + return aPackage; + } + } + + return nil; +} + + +#pragma mark - CCPackageDownloadManagerDelegate + +- (void)downloadFinishedOfPackage:(CCPackage *)package +{ + [_delegate packageDownloadFinished:package]; + + [self unzipPackage:package]; +} + +- (void)downloadFailedOfPackage:(CCPackage *)package error:(NSError *)error +{ + [_delegate packageDownloadFailed:package error:error]; +} + +- (void)downloadProgressOfPackage:(CCPackage *)package downloadedBytes:(NSUInteger)downloadedBytes totalBytes:(NSUInteger)totalBytes +{ + if ([_delegate respondsToSelector:@selector(packageDownloadProgress:downloadedBytes:totalBytes:)]) + { + [_delegate packageDownloadProgress:package downloadedBytes:downloadedBytes totalBytes:totalBytes]; + } +} + + +#pragma mark - CCPackageUnzipperDelegate + +- (void)unzipFinished:(CCPackageUnzipper *)packageUnzipper +{ + [self runOnMainQueue:^ + { + [self removeDownloadFile:packageUnzipper.package]; + + [_unzipTasks removeObject:packageUnzipper]; + + [packageUnzipper.package setValue:@(CCPackageStatusUnzipped) forKey:@"status"]; + + if ([_delegate respondsToSelector:@selector(packageUnzippingFinished:)]) + { + [_delegate packageUnzippingFinished:packageUnzipper.package]; + } + + if (![self installPackage:packageUnzipper.package]) + { + return; + } + + [self tidyUpAfterInstallation:packageUnzipper.package]; + }]; +} + +- (void)unzipFailed:(CCPackageUnzipper *)packageUnzipper error:(NSError *)error +{ + [self runOnMainQueue:^ + { + [_unzipTasks removeObject:packageUnzipper]; + + [_delegate packageUnzippingFailed:packageUnzipper.package error:error]; + }]; +} + +- (void)unzipProgress:(CCPackageUnzipper *)packageUnzipper unzippedBytes:(NSUInteger)unzippedBytes totalBytes:(NSUInteger)totalBytes +{ + [self runOnMainQueue:^ + { + if ([_delegate respondsToSelector:@selector(packageUnzippingProgress:unzippedBytes:totalBytes:)]) + { + [_delegate packageUnzippingProgress:packageUnzipper.package unzippedBytes:unzippedBytes totalBytes:totalBytes]; + } + }]; +} + + +#pragma mark - Flow + +- (void)tidyUpAfterInstallation:(CCPackage *)package +{ + [self removeUnzippedPackage:package]; +} + +- (void)unzipPackage:(CCPackage *)package +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:package.unzipURL.path]) + { + NSError *error; + CCLOGINFO(@"[PACKAGE/UNZIP][INFO] Removing incomplete unzipped archive: %@", installData.unzipURL.path); + if ([fileManager removeItemAtURL:package.unzipURL error:&error]) + { + CCLOG(@"[PACKAGE/UNZIP][ERROR] Removing incomplete unzipped archive: %@", error); + } + } + + // Note: This is done on purpose in case a zip contains more than the expected root package folder or something completely different which can lead to a mess + // The content is checked later on after unzipping finishes + package.unzipURL = [NSURL fileURLWithPath:[[NSTemporaryDirectory() stringByAppendingPathComponent:PACKAGE_REL_UNZIP_FOLDER] stringByAppendingPathComponent:[package standardIdentifier]]]; + CCPackageUnzipper *packageUnzipper = [[CCPackageUnzipper alloc] initWithPackage:package]; + + [_unzipTasks addObject:packageUnzipper]; + + if ([_delegate respondsToSelector:@selector(passwordForPackageZipFile:)]) + { + packageUnzipper.password = [_delegate passwordForPackageZipFile:package]; + } + + packageUnzipper.delegate = self; + [packageUnzipper unpackPackageOnQueue:_unzippingQueue]; +} + +- (void)removeDownloadFile:(CCPackage *)package +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSError *error; + if (![fileManager removeItemAtPath:package.localDownloadURL.path error:&error]) + { + CCLOG(@"[PACKAGE][ERROR] Removing download file: %@", error); + } +} + +- (void)removeUnzippedPackage:(CCPackage *)package +{ + NSAssert(package.unzipURL, @"installData.unzipURL must not be nil"); + + NSError *error; + if (![[NSFileManager defaultManager] removeItemAtURL:package.unzipURL error:&error]) + { + CCLOG(@"[PACKAGE][ERROR] removing unzipped package after successful installation: %@", error); + } +} + +- (BOOL)installPackage:(CCPackage *)package +{ + NSError *error; + if (![self determinePackageFolderNameInUnzippedFile:package error:&error]) + { + CCLOG(@"[PACKAGE][ERROR] Could not determine package folder name: %@", error); + + [_delegate packageInstallationFailed:package error:error]; + return NO; + } + + CCPackageInstaller *packageInstaller = [[CCPackageInstaller alloc] initWithPackage:package installPath:_installedPackagesPath]; + + if (![packageInstaller installWithError:&error]) + { + CCLOG(@"[PACKAGE][ERROR] Installation failed: %@", error); + + [package setValue:@(CCPackageStatusInstallationFailed) forKey:NSStringFromSelector(@selector(status))]; + [_delegate packageInstallationFailed:package error:error]; + return NO; + } + + if (package.enableOnDownload) + { + CCPackageCocos2dEnabler *packageCocos2dEnabler = [[CCPackageCocos2dEnabler alloc] init]; + [packageCocos2dEnabler enablePackages:@[package]]; + } + + CCLOGINFO(@"[PACKAGE/INSTALL][INFO] Installation of package successful! Package enabled: %d", [package installData].enableOnDownload); + + [_delegate packageInstallationFinished:package]; + return YES; +} + +- (BOOL)determinePackageFolderNameInUnzippedFile:(CCPackage *)package error:(NSError **)error +{ + NSAssert(package.unzipURL, @"installData.unzipURL must not be nil"); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSArray *files = [fileManager contentsOfDirectoryAtURL:package.unzipURL + includingPropertiesForKeys:@[NSURLIsDirectoryKey, NSURLNameKey] + options:NSDirectoryEnumerationSkipsHiddenFiles | NSDirectoryEnumerationSkipsSubdirectoryDescendants + error:nil]; + + if ([files count] == 0) + { + [self setPackageEmptyError:error package:package]; + return NO; + } + + if ([self searchForStandardFolderNameFiles:files package:package]) + { + return YES; + } + + if ([self askDelegateForCustomFolderName:package files:files]) + { + return YES; + } + + [self setPackageFolderNameUndefinedError:error package:package]; + + return NO; +} + +- (void)setPackageFolderNameUndefinedError:(NSError **)error package:(CCPackage *)package +{ + if (error) + { + *error = [NSError errorWithDomain:@"cocos2d" + code:PACKAGE_ERROR_INSTALL_PACKAGE_FOLDER_NAME_NOT_FOUND + userInfo: + @{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"The package folder name could not be determined. " + "Check delegate method customFolderName:packageContents:."], + @"package" : package}]; + } +} + +- (void)setPackageEmptyError:(NSError **)error package:(CCPackage *)package +{ + NSAssert(package.unzipURL, @"installData.unzipURL must not be nil"); + + if (error) + { + *error = [NSError errorWithDomain:@"cocos2d" + code:PACKAGE_ERROR_INSTALL_PACKAGE_EMPTY + userInfo: + @{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"The zip file is empty: \"%@\"", package.unzipURL], + @"package" : package}]; + } +} + +- (BOOL)askDelegateForCustomFolderName:(CCPackage *)package files:(NSArray *)files +{ + NSAssert(package.unzipURL, @"installData.unzipURL must not be nil"); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([_delegate respondsToSelector:@selector(customFolderName:packageContents:)]) + { + NSString *customFolderNameToUse = [_delegate customFolderName:package packageContents:files]; + if ([fileManager fileExistsAtPath:[package.unzipURL.path stringByAppendingPathComponent:customFolderNameToUse]]) + { + package.folderName = customFolderNameToUse; + return YES; + } + } + return NO; +} + +- (BOOL)searchForStandardFolderNameFiles:(NSArray *)files package:(CCPackage *)package +{ + for (NSURL *fileURL in files) + { + NSDictionary *resourceValues = [fileURL resourceValuesForKeys:@[NSURLIsDirectoryKey, NSURLNameKey] error:nil]; + NSString *name = resourceValues[NSURLNameKey]; + BOOL isDir = [resourceValues[NSURLIsDirectoryKey] boolValue]; + + if (isDir && [name isEqualToString:[package standardIdentifier]]) + { + package.folderName = [package standardIdentifier]; + return YES; + } + } + return NO; +} + +- (BOOL)disablePackage:(CCPackage *)package error:(NSError **)error +{ + if (package.status == CCPackageStatusInstalledDisabled) + { + return YES; + } + + if (package.status != CCPackageStatusInstalledEnabled) + { + if (error) + { + *error = [NSError errorWithDomain:@"com.cocos2d" + code:PACKAGE_ERROR_MANAGER_CANNOT_DISABLE_NON_ENABLED_PACKAGE + userInfo:@{NSLocalizedDescriptionKey: @"Error disabling package. Only packages with status CCPackageStatusInstalledEnabled can be disabled."}]; + } + return NO; + } + + CCPackageCocos2dEnabler *packageCocos2dEnabler = [[CCPackageCocos2dEnabler alloc] init]; + [packageCocos2dEnabler disablePackages:@[package]]; + + if (![_packages containsObject:package]) + { + [_packages addObject:package]; + } + return YES; +} + +- (BOOL)enablePackage:(CCPackage *)package error:(NSError **)error +{ + if (package.status == CCPackageStatusInstalledEnabled) + { + return YES; + } + + if (package.status != CCPackageStatusInstalledDisabled) + { + if (error) + { + *error = [NSError errorWithDomain:@"com.cocos2d" + code:PACKAGE_ERROR_MANAGER_CANNOT_ENABLE_NON_DISABLED_PACKAGE + userInfo:@{NSLocalizedDescriptionKey: @"Error enabling package. Only packages with status CCPackageStatusInstalledDisabled can be enabled."}]; + } + return NO; + } + + CCPackageCocos2dEnabler *packageCocos2dEnabler = [[CCPackageCocos2dEnabler alloc] init]; + [packageCocos2dEnabler enablePackages:@[package]]; + + if (![_packages containsObject:package]) + { + [_packages addObject:package]; + } + return YES; +} + +- (void)addPackage:(CCPackage *)package; +{ + NSAssert(package, @"package must not be nil"); + NSAssert(package.status == CCPackageStatusInitial, @"package status must be CCPackageStatusInitial"); + + if ([_packages containsObject:package] + || [self packageWithName:package.name resolution:package.resolution]) + { + return; + } + + [_packages addObject:package]; +} + +- (BOOL)deletePackage:(CCPackage *)package error:(NSError **)error +{ + CCPackageCocos2dEnabler *packageCocos2dEnabler = [[CCPackageCocos2dEnabler alloc] init]; + [packageCocos2dEnabler disablePackages:@[package]]; + + [_packages removeObject:package]; + [self savePackages]; + + [_downloadManager cancelDownloadOfPackage:package]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (package.unzipURL + && [fileManager fileExistsAtPath:package.unzipURL.path] + && (![fileManager removeItemAtURL:package.unzipURL error:error])) + { + return NO; + } + + BOOL result = ([fileManager fileExistsAtPath:package.installURL.path] + && [fileManager removeItemAtURL:package.installURL error:error]); + + if (result) + { + CCLOGINFO(@"[PACKAGE/INSTALL][INFO] Package deletion successful!"); + } + else + { + CCLOG(@"[PACKAGE/INSTALL][ERROR] Package deletion failed: %@", *error); + } + + return result; +} + +- (void)cancelDownloadOfPackage:(CCPackage *)package +{ + if (!(package.status == CCPackageStatusDownloadPaused + || package.status == CCPackageStatusDownloading + || package.status == CCPackageStatusDownloaded + || package.status == CCPackageStatusDownloadFailed)) + { + return; + } + + [_packages removeObject:package]; + + [_downloadManager cancelDownloadOfPackage:package]; + + [self savePackages]; +} + +- (void)pauseDownloadOfPackage:(CCPackage *)package +{ + [_downloadManager pauseDownloadOfPackage:package]; +} + +- (void)resumeDownloadOfPackage:(CCPackage *)package +{ + [_downloadManager resumeDownloadOfPackage:package]; +} + +- (void)pauseAllDownloads +{ + [_downloadManager pauseAllDownloads]; +} + +- (void)resumeAllDownloads +{ + [_downloadManager resumeAllDownloads]; +} + +- (void)request:(NSMutableURLRequest *)request ofPackage:(CCPackage *)package +{ + if ([_delegate respondsToSelector:@selector(request:ofPackage:)]) + { + [_delegate request:request ofPackage:package]; + } +} + +- (void)runOnMainQueue:(dispatch_block_t)block +{ + if ([NSThread isMainThread]) + { + block(); + } + else + { + dispatch_sync(dispatch_get_main_queue(), block); + } +} + +@end diff --git a/cocos2d/CCPackageManagerDelegate.h b/cocos2d/CCPackageManagerDelegate.h new file mode 100644 index 00000000000..65d32870c2d --- /dev/null +++ b/cocos2d/CCPackageManagerDelegate.h @@ -0,0 +1,114 @@ +#import + +@class CCPackage; + +@protocol CCPackageManagerDelegate + +@required + +/** + * Only called when the full process of downloading, unzipping and installation completed successfully + * + * @param package The package for which the installation finished + */ +- (void)packageInstallationFinished:(CCPackage *)package; + +/** + * Only called when something went wrong during installation + * + * @param package The package for which the installation failed + * @param error Pointer to an error object* + */ +- (void)packageInstallationFailed:(CCPackage *)package error:(NSError *)error; + + +/** + * Called when a download of a package finished successfully + * + * @param package The package for which the download finished + */ +- (void)packageDownloadFinished:(CCPackage *)package; + +/** + * Only called when a download of a package failed, see error pointer. + * + * @param package The package for which the download failed + * @param error Pointer to an error object + */ +- (void)packageDownloadFailed:(CCPackage *)package error:(NSError *)error; + +@optional +/** + * Called whenever the download of a package received bytes. + * Note: Total bytes can be 0 throughout a download if the response does not contain a Content-Length header. + * + * @param package The package for which the download progress occured + * @param downloadedBytes Download progress in bytes + * @param totalBytes Total size of the download in bytes + */ +- (void)packageDownloadProgress:(CCPackage *)package downloadedBytes:(NSUInteger)downloadedBytes totalBytes:(NSUInteger)totalBytes; + + +@required + +/** + * Only called when the process of unzipping finished successfully + * + * @param package The package for which the unzip process finished + */ +- (void)packageUnzippingFinished:(CCPackage *)package; + +/** + * Only called when the process of unzipping failed + * + * @param package The package for which the unzip process failed + * @param error Pointer to an error object + */ +- (void)packageUnzippingFailed:(CCPackage *)package error:(NSError *)error; + +@optional +/** + * Called whenever the process of unzipping reports a progress in bytes + * + * @param package The package for which the unzip progress occured + * @param unzippedBytes Unzip progress in bytes + * @param totalBytes Total size of the unzipping operation + */ +- (void)packageUnzippingProgress:(CCPackage *)package unzippedBytes:(NSUInteger)unzippedBytes totalBytes:(NSUInteger)totalBytes; + + +/** + * When a package is installed the root object of an unzipped package should be a folder named with the + * standard identifier(--). + * + * The package installer will first try to find a folder with the standard identifier. If that fails you can + * omplement this method if a package contains a folder named other than the standard identifier. + * The content of the unzipped package is provided to select a folder name. + * If finding the standard identifier fails and this method is not implemented the installation will fail- + * + * @param package The package for which the unzipped folder name should be determined + * @param packageContens A list of URLs of unzipped package's first level directory + * + * @return The folder name of the package + */ +- (NSString *)customFolderName:(CCPackage *)package packageContents:(NSArray *)packageContents; + +/** + * Provide this method if you want to set a password to unzip a protected zip archive. + * + * @param package The package for which the password should be set + * + * @return The password to be used to unzip a package archive + */ +- (NSString *)passwordForPackageZipFile:(CCPackage *)package; + +/** + * Provides the request before it is sent to let delegate adjust headers etc. + * If there is a partial download which should be resumed a Range header will be set after this invocation. + * + * @param request The request object that will be used for the download + * @param download The download object which will start with the given request + */ +- (void)request:(NSMutableURLRequest *)request ofPackage:(CCPackage *)package; + +@end diff --git a/cocos2d/CCPackageTypes.h b/cocos2d/CCPackageTypes.h new file mode 100644 index 00000000000..7c072f9a77b --- /dev/null +++ b/cocos2d/CCPackageTypes.h @@ -0,0 +1,14 @@ +typedef enum { + CCPackageStatusInitial = 0, + CCPackageStatusDownloading = 1, + CCPackageStatusDownloadPaused = 2, + CCPackageStatusDownloadFailed = 3, + CCPackageStatusDownloaded = 4, + CCPackageStatusUnzipping = 5, + CCPackageStatusUnzipped = 6, + CCPackageStatusUnzipFailed = 7, + CCPackageStatusInstallationFailed = 9, + CCPackageStatusInstalledEnabled = 10, + CCPackageStatusInstalledDisabled = 11 +} +CCPackageStatus; diff --git a/cocos2d/CCPackageUnzipper.h b/cocos2d/CCPackageUnzipper.h new file mode 100644 index 00000000000..88ff7111597 --- /dev/null +++ b/cocos2d/CCPackageUnzipper.h @@ -0,0 +1,62 @@ +#import +#import + +@protocol CCPackageUnzipperDelegate; +@class CCPackage; + +@interface CCPackageUnzipper : NSObject + +/** + * The package to be unzipped + */ +@property (nonatomic, strong, readonly) CCPackage *package; + +/** + * Unzipper's delegate + */ +@property (nonatomic, weak) id delegate; + +/** + * Password used to unzip the package archive. + */ +@property (nonatomic, copy) NSString *password; + +/** + * The queue on which unzipping of packages is achieved + * On iOS 5.0, MacOS 10.7 and below you have to get rid of the queue after use if it's not a global one. + */ +#if OS_OBJECT_HAVE_OBJC_SUPPORT == 1 +@property (nonatomic, strong, readonly) dispatch_queue_t queue; +#else +@property (nonatomic, readonly) dispatch_queue_t queue; +#endif + +/** + * Returns a new instance of CCPackageUnzipper + * + * @param package The package to be unzipped + * + * @return A new instance of CCPackageUnzipper + */ +- (instancetype)initWithPackage:(CCPackage *)package; + +/** + * Unpacks a package archive. The local location of the package archive and the destination + * is determined by the package's install data. See CCPacakgeInstallData. + * Those locations are usually set by the CCPackageManager and the various components to + * install a package. + * + * @param package The package to be unzipped + */ +- (void)unpackPackage; + +/** + * Like the method above. You can specify a queue instead of the default one to run the + * unzip task on. + * Note: On iOS 5.0 and MacOS 10.7 and below you have to claim ownership of the queue if it's not a global one. + * + * @param queue Queue to run unzip task on, default is DISPATCH_QUEUE_PRIORITY_LOW + */ +- (void)unpackPackageOnQueue:(dispatch_queue_t)queue; + +@end diff --git a/cocos2d/CCPackageUnzipper.m b/cocos2d/CCPackageUnzipper.m new file mode 100644 index 00000000000..5a38969cf5a --- /dev/null +++ b/cocos2d/CCPackageUnzipper.m @@ -0,0 +1,101 @@ +#import "CCPackageUnzipper.h" +#import "CCPackageUnzipperDelegate.h" +#import "CCPackage.h" +#import +#import "ccMacros.h" + + +@interface CCPackageUnzipper () + +@property (nonatomic, strong, readwrite) CCPackage *package; +#if OS_OBJECT_HAVE_OBJC_SUPPORT == 1 +@property (nonatomic, strong, readwrite) dispatch_queue_t queue; +#else +@property (nonatomic, readwrite) dispatch_queue_t queue; +#endif + +@end + + +@implementation CCPackageUnzipper + +- (instancetype)initWithPackage:(CCPackage *)package +{ + self = [super init]; + if (self) + { + self.package = package; + self.queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); + } + + return self; +} + + +#pragma mark - actions + +- (void)unpackPackage; +{ + [self unpackPackageOnQueue:_queue]; +} + +- (void)unpackPackageOnQueue:(dispatch_queue_t)queue +{ + NSAssert(queue != nil, @"queue must not be nil"); + NSAssert(_package != nil, @"package must not be nil"); + NSAssert(_package.localDownloadURL != nil, @"package.localDownloadURL must not be nil"); + NSAssert(_package.unzipURL != nil, @"package.unzipURL must not be nil"); + + self.queue = queue; + [_package setValue:@(CCPackageStatusUnzipping) forKey:@"status"]; + + dispatch_async(queue, ^ + { + CCLOGINFO(@"[PACKAGE/UNZIP][INFO]: Unzipping package... %@", _package); + + NSError *error; + BOOL success = [SSZipArchive unzipFileAtPath:_package.localDownloadURL.path + toDestination:_package.unzipURL.path + overwrite:YES + password:_password + error:&error + delegate:self]; + + if (success) + { + CCLOGINFO(@"[PACKAGE/UNZIP][INFO]: Unzipping finished of package: %@", _package); + + [_package setValue:@(CCPackageStatusUnzipped) forKey:@"status"]; + + if ([_delegate respondsToSelector:@selector(unzipFinished:)]) + { + [_delegate unzipFinished:self]; + } + } + else + { + CCLOG(@"[PACKAGE/UNZIP][ERROR]: Unzipping failed of package: %@ with error: %@", _package, error); + + [_package setValue:@(CCPackageStatusUnzipFailed) forKey:@"status"]; + + if ([_delegate respondsToSelector:@selector(unzipFailed:error:)]) + { + [_delegate unzipFailed:self error:error]; + } + } + }); +} + +#pragma mark - SSZipArchiveDelegate + +- (void)zipArchiveProgressEvent:(NSInteger)loaded total:(NSInteger)total +{ + if ([_delegate respondsToSelector:@selector(unzipProgress:unzippedBytes:totalBytes:)]) + { + [_delegate unzipProgress:self unzippedBytes:(NSUInteger) loaded totalBytes:(NSUInteger) total]; + } +} + + + +@end diff --git a/cocos2d/CCPackageUnzipperDelegate.h b/cocos2d/CCPackageUnzipperDelegate.h new file mode 100644 index 00000000000..ec2f0c015a3 --- /dev/null +++ b/cocos2d/CCPackageUnzipperDelegate.h @@ -0,0 +1,33 @@ +#import + +@class CCPackageUnzipper; + +@protocol CCPackageUnzipperDelegate + +@optional + +/** + * Only called when the process of unzipping finished successfully + * + * @param packageUnzipper The package unzipper + */ +- (void)unzipFinished:(CCPackageUnzipper *)packageUnzipper; + +/** + * Only called when the process of unzipping failed + * + * @param packageUnzipper The package unzipper + * @param error Pointer to an error object + */ +- (void)unzipFailed:(CCPackageUnzipper *)packageUnzipper error:(NSError *)error; + +/** + * Called whenever the process of unzipping reports a progress in bytes + * + * @param packageUnzipper The package unzipper + * @param unzippedBytes Unzip progress in bytes + * @param totalBytes Total size of the unzipping operation + */ +- (void)unzipProgress:(CCPackageUnzipper *)packageUnzipper unzippedBytes:(NSUInteger)unzippedBytes totalBytes:(NSUInteger)totalBytes; + +@end diff --git a/cocos2d/CCPackage_private.h b/cocos2d/CCPackage_private.h new file mode 100644 index 00000000000..859c9c55d0c --- /dev/null +++ b/cocos2d/CCPackage_private.h @@ -0,0 +1,18 @@ +#import +#import "CCPackageTypes.h" +#import "CCPackage.h" + +@interface CCPackage() + +@property (nonatomic, copy, readwrite) NSString *name; +@property (nonatomic, copy, readwrite) NSString *resolution; +@property (nonatomic, copy, readwrite) NSString *os; +@property (nonatomic, copy, readwrite) NSURL *remoteURL; +@property (nonatomic, copy, readwrite) NSString *folderName; +@property (nonatomic, copy, readwrite) NSURL *installURL; +@property (nonatomic, copy, readwrite) NSURL *localDownloadURL; +@property (nonatomic, copy, readwrite) NSURL *unzipURL; +@property (nonatomic, readwrite) BOOL enableOnDownload; +@property (nonatomic, readwrite) CCPackageStatus status; + +@end \ No newline at end of file diff --git a/cocos2d/CCSpriteFrameCache.h b/cocos2d/CCSpriteFrameCache.h index 1008032ffdf..da3eb36fa0e 100644 --- a/cocos2d/CCSpriteFrameCache.h +++ b/cocos2d/CCSpriteFrameCache.h @@ -114,6 +114,13 @@ */ -(void) loadSpriteFrameLookupDictionaryFromFile:(NSString*)filename; +/** + * Loads all sprite sheet lookup files in all search paths and registers all the referenced sprite sheets with the sprite frame cache. + * + * @param filename Sprite sheet lookup file. + */ +- (void)loadSpriteFrameLookupsInAllSearchPathsWithName:(NSString *)filename; + /// ----------------------------------------------------------------------- /// @name Sprite Frame Cache Removal /// ----------------------------------------------------------------------- diff --git a/cocos2d/CCSpriteFrameCache.m b/cocos2d/CCSpriteFrameCache.m index cf27556f220..16ac25aa229 100644 --- a/cocos2d/CCSpriteFrameCache.m +++ b/cocos2d/CCSpriteFrameCache.m @@ -129,6 +129,30 @@ -(void) loadSpriteFrameLookupDictionaryFromFile:(NSString*)filename } } +- (void)loadSpriteFrameLookupsInAllSearchPathsWithName:(NSString *)filename +{ + NSArray *paths = [[CCFileUtils sharedFileUtils] fullPathsOfFileNameInAllSearchPaths:filename]; + + for (NSString *spriteFrameLookupFullPath in paths) + { + NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:spriteFrameLookupFullPath]; + + NSDictionary *metadata = dict[@"metadata"]; + NSInteger version = [metadata[@"version"] integerValue]; + if (version != 1) + { + CCLOG(@"cocos2d: ERROR: Invalid filenameLookup dictionary version: %ld. Filename: %@", (long) version, filename); + return; + } + + NSArray *spriteFrameFiles = dict[@"spriteFrameFiles"]; + for (NSString *spriteFrameFile in spriteFrameFiles) + { + [self registerSpriteFramesFile:spriteFrameFile]; + } + } +} + - (void) registerSpriteFramesFile:(NSString*)plist { NSAssert(plist, @"plist filename should not be nil"); diff --git a/cocos2d/Platforms/iOS/CCAppDelegate.m b/cocos2d/Platforms/iOS/CCAppDelegate.m index b7d0a6b133c..255ca7e0316 100644 --- a/cocos2d/Platforms/iOS/CCAppDelegate.m +++ b/cocos2d/Platforms/iOS/CCAppDelegate.m @@ -34,6 +34,7 @@ #import "CCGLView.h" #import "OALSimpleAudio.h" +#import "CCPackageManager.h" #if __CC_METAL_SUPPORTED_AND_ENABLED #import "CCMetalView.h" @@ -263,6 +264,8 @@ - (void) setupCocos2dWithOptions:(NSDictionary*)config [window_ makeKeyAndVisible]; [self forceOrientation]; + + [[CCPackageManager sharedManager] loadPackages]; } // iOS8 hack around orientation bug @@ -313,18 +316,23 @@ -(void) applicationWillEnterForeground:(UIApplication*)application if([CCDirector sharedDirector].animating == NO) { [[CCDirector sharedDirector] startAnimation]; } + [[CCPackageManager sharedManager] savePackages]; } // application will be killed - (void)applicationWillTerminate:(UIApplication *)application { [[CCDirector sharedDirector] end]; + + [[CCPackageManager sharedManager] savePackages]; } // purge memory - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { [[CCDirector sharedDirector] purgeCachedData]; + + [[CCPackageManager sharedManager] savePackages]; } // next delta time will be zero diff --git a/cocos2d/Support/CCFileUtils.h b/cocos2d/Support/CCFileUtils.h index ff6ebcd2d7f..82c5e31655d 100644 --- a/cocos2d/Support/CCFileUtils.h +++ b/cocos2d/Support/CCFileUtils.h @@ -133,7 +133,7 @@ typedef NS_ENUM(NSUInteger, CCFileUtilsSearchMode) { If the property "enableiPhoneResourcesOniPad" is enabled, it will also search for iPhone resources if you are in an iPad. */ -@property (nonatomic, copy) NSArray *searchResolutionsOrder; +@property (nonatomic, strong) NSMutableArray *searchResolutionsOrder; /** Array of search paths. You can use this array to modify the search path of the resources. @@ -285,7 +285,20 @@ typedef NS_ENUM(NSUInteger, CCFileUtilsSearchMode) { */ -(NSString*) fullPathFromRelativePathIgnoringResolutions:(NSString*)relPath; -/** +/** + * Returns all fullpaths of a filename in all search paths without taking into account the screen resolution suffixes or directories. + * It will use the "searchPath" though. + * If the file can't be found, it will return an empty array. + * + * Useful for loading the fileLookup.plist and spriteFrameFileList.plist for packages + * + * @param relPath Relative path. + * + * @return Array of full paths. + */ +- (NSArray *)fullPathsOfFileNameInAllSearchPaths:(NSString *)filename; + +/** * Returns the fullpath for a given filename. * First it will try to get a new filename from the "filenameLookup" dictionary. If a new filename can't be found on the dictionary, it will use the original filename. * Then it will try obtain the full path of the filename using the CCFileUtils search rules: resolutions, and search paths @@ -358,6 +371,15 @@ typedef NS_ENUM(NSUInteger, CCFileUtilsSearchMode) { */ -(void) loadFilenameLookupDictionaryFromFile:(NSString*)filename; +/** + * Loads the filenameLookup dictionary from the contents of a filename in all search paths. + * + * Used for packages to merge filenameLookups found in different search paths. + * + * @param filename Filename to query. + */ +- (void)loadFileNameLookupsInAllSearchPathsWithName:(NSString *)filename; + /** * Removes the suffix from a path. * On iPhone RetinaDisplay it will remove the -hd suffix diff --git a/cocos2d/Support/CCFileUtils.m b/cocos2d/Support/CCFileUtils.m index 6ab469da259..4b84a1e62b0 100644 --- a/cocos2d/Support/CCFileUtils.m +++ b/cocos2d/Support/CCFileUtils.m @@ -468,6 +468,68 @@ -(NSString*) fullPathFromRelativePathIgnoringResolutions:(NSString*)relPath return ret; } +- (NSArray *)fullPathsOfFileNameInAllSearchPaths:(NSString *)filename +{ + NSMutableArray *result = [NSMutableArray array]; + + for (NSString *path in self.searchPath) + { + NSString *aPath = [path stringByAppendingPathComponent:filename]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if ([fileManager fileExistsAtPath:aPath]) + { + [result addObject:aPath]; + continue; + } + + NSString *file = [aPath lastPathComponent]; + NSString *file_path = [aPath stringByDeletingLastPathComponent]; + // Default to normal resource directory + NSString *foundPath = [[NSBundle mainBundle] pathForResource:file + ofType:nil + inDirectory:file_path]; + if (foundPath) + { + [result addObject:aPath]; + } + } + + return result; +} + +- (void)loadAndAddFilenameLookupDictionaryFromFile:(NSString *)filename +{ + NSString *fullpath = [self fullPathForFilenameIgnoringResolutions:filename]; + if( fullpath ) { + NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:fullpath]; + + NSDictionary *metadata = dict[@"metadata"]; + NSInteger version = [metadata[@"version"] integerValue]; + if( version != 1) { + CCLOG(@"cocos2d: ERROR: Invalid filenameLookup dictionary version: %ld. Filename: %@", (long)version, filename); + return; + } + + NSDictionary *filenames = dict[@"filenames"]; + NSMutableDictionary *newFileLookup = [NSMutableDictionary dictionary]; + [newFileLookup addEntriesFromDictionary:filenames]; + [newFileLookup addEntriesFromDictionary:_filenameLookup]; + + self.filenameLookup = newFileLookup; + } +} + +- (void)loadFileNameLookupsInAllSearchPathsWithName:(NSString *)filename +{ + NSArray *paths = [self fullPathsOfFileNameInAllSearchPaths:filename]; + + for (NSString *fileLookupFullPath in paths) + { + [self loadAndAddFilenameLookupDictionaryFromFile:fileLookupFullPath]; + } +} + -(NSString*) fullPathForFilename:(NSString*)filename { return [self fullPathForFilename:filename contentScale:NULL]; @@ -501,10 +563,10 @@ -(NSString*) fullPathForFilename:(NSString*)filename contentScale:(CGFloat *)con BOOL found = NO; NSString *ret = @""; - for( NSString *path in _searchPath ) { - - // Search with Suffixes - for( NSString *device in _searchResolutionsOrder ) { + // Search with Suffixes + for( NSString *device in _searchResolutionsOrder ) { + + for( NSString *path in _searchPath ) { NSString *fileWithPath = [path stringByAppendingPathComponent:newfilename]; diff --git a/external/SSZipArchive b/external/SSZipArchive new file mode 160000 index 00000000000..d150baedffa --- /dev/null +++ b/external/SSZipArchive @@ -0,0 +1 @@ +Subproject commit d150baedffad624ad154d857d94ee4a43c7af052