Skip to content
This repository has been archived by the owner on Oct 7, 2024. It is now read-only.

Commit

Permalink
Merge pull request microsoft#49 from Microsoft/download-progress
Browse files Browse the repository at this point in the history
Download progress
  • Loading branch information
geof90 committed Nov 14, 2015
2 parents 79e4c50 + 0eb3ea6 commit 92112ab
Show file tree
Hide file tree
Showing 17 changed files with 469 additions and 63 deletions.
23 changes: 22 additions & 1 deletion CodePush.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,32 @@

@end

@interface CodePushDownloadHandler : NSObject<NSURLConnectionDelegate>

@property (strong) NSOutputStream *outputFileStream;
@property long expectedContentLength;
@property long receivedContentLength;
@property (copy) void (^progressCallback)(long, long);
@property (copy) void (^doneCallback)();
@property (copy) void (^failCallback)(NSError *err);

- (id)init:(NSString *)downloadFilePath
progressCallback:(void (^)(long, long))progressCallback
doneCallback:(void (^)())doneCallback
failCallback:(void (^)(NSError *err))failCallback;

- (void)download:(NSString*)url;

@end

@interface CodePushPackage : NSObject

+ (void)applyPackage:(NSDictionary *)updatePackage
error:(NSError **)error;

+ (NSDictionary *)getCurrentPackage:(NSError **)error;
+ (NSString *)getCurrentPackageFolderPath:(NSError **)error;
+ (NSString *)getCurrentPackageBundlePath:(NSError **)error;
+ (NSString *)getCurrentPackageHash:(NSError **)error;

+ (NSDictionary *)getPackage:(NSString *)packageHash
Expand All @@ -45,7 +64,9 @@


+ (void)downloadPackage:(NSDictionary *)updatePackage
error:(NSError **)error;
progressCallback:(void (^)(long, long))progressCallback
doneCallback:(void (^)())doneCallback
failCallback:(void (^)(NSError *err))failCallback;

+ (void)rollbackPackage;

Expand Down
50 changes: 27 additions & 23 deletions CodePush.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "RCTBridgeModule.h"
#import "RCTEventDispatcher.h"
#import "RCTRootView.h"
#import "RCTUtils.h"
#import "CodePush.h"
Expand All @@ -13,7 +14,6 @@ @implementation CodePush

NSString * const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
NSString * const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE";
NSString * const UpdateBundleFileName = @"app.jsbundle";

@synthesize bridge = _bridge;

Expand All @@ -27,16 +27,14 @@ + (NSString *)getDocumentsDirectory
+ (NSURL *)getBundleUrl
{
NSError *error;
NSString *packageFolder = [CodePushPackage getCurrentPackageFolderPath:&error];
NSString *packageFile = [CodePushPackage getCurrentPackageBundlePath:&error];
NSURL *binaryJsBundleUrl = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

if (error || !packageFolder)
if (error || !packageFile)
{
return binaryJsBundleUrl;
}

NSString *packageFile = [packageFolder stringByAppendingPathComponent:UpdateBundleFileName];

NSDictionary *binaryFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[binaryJsBundleUrl path] error:nil];
NSDictionary *appFileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:packageFile error:nil];
NSDate *binaryDate = [binaryFileAttributes objectForKey:NSFileModificationDate];
Expand Down Expand Up @@ -198,24 +196,30 @@ - (void)startRollbackTimer:(int)rollbackTimeout
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *err;
[CodePushPackage downloadPackage:updatePackage
error:&err];

if (err) {
return reject(err);
}

NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[@"packageHash"]
error:&err];

if (err) {
return reject(err);
}

resolve(newPackage);
});
[CodePushPackage downloadPackage:updatePackage
progressCallback:^(long expectedContentLength, long receivedContentLength) {
[self.bridge.eventDispatcher
sendAppEventWithName:@"CodePushDownloadProgress"
body:@{
@"totalBytes":[NSNumber numberWithLong:expectedContentLength],
@"receivedBytes":[NSNumber numberWithLong:receivedContentLength]
}];
}
doneCallback:^{
NSError *err;
NSDictionary *newPackage = [CodePushPackage
getPackage:updatePackage[@"packageHash"]
error:&err];

if (err) {
return reject(err);
}

resolve(newPackage);
}
failCallback:^(NSError *err) {
reject(err);
}];
}

RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve
Expand Down
4 changes: 4 additions & 0 deletions CodePush.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; };
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; settings = {ASSET_TAGS = (); }; };
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 810D4E6C1B96935000B397E9 /* CodePushPackage.m */; };
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; };
/* End PBXBuildFile section */
Expand All @@ -28,6 +29,7 @@
134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; };
13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = "<group>"; };
13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = "<group>"; };
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = "<group>"; };
810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = "<group>"; };
81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand All @@ -54,6 +56,7 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */,
810D4E6C1B96935000B397E9 /* CodePushPackage.m */,
81D51F391B6181C2000DA084 /* CodePushConfig.m */,
13BE3DEC1AC21097009241FE /* CodePush.h */,
Expand Down Expand Up @@ -119,6 +122,7 @@
buildActionMask = 2147483647;
files = (
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */,
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */,
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */,
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */,
);
Expand Down
85 changes: 85 additions & 0 deletions CodePushDownloadHandler.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#import "CodePush.h"

@implementation CodePushDownloadHandler

- (id)init:(NSString *)downloadFilePath
progressCallback:(void (^)(long, long))progressCallback
doneCallback:(void (^)())doneCallback
failCallback:(void (^)(NSError *err))failCallback {
self.outputFileStream = [NSOutputStream outputStreamToFileAtPath:downloadFilePath
append:NO];
self.receivedContentLength = 0;
self.progressCallback = progressCallback;
self.doneCallback = doneCallback;
self.failCallback = failCallback;
return self;
}

-(void)download:(NSString*)url {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
[connection start];
}

#pragma mark NSURLConnection Delegate Methods

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
// Return nil to indicate not necessary to store a cached response for this connection
return nil;
}

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.expectedContentLength = response.expectedContentLength;
[self.outputFileStream open];
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
self.receivedContentLength = self.receivedContentLength + [data length];

NSInteger bytesLeft = [data length];

do {
NSInteger bytesWritten = [self.outputFileStream write:[data bytes]
maxLength:bytesLeft];
if (bytesWritten == -1) {
break;
}

bytesLeft -= bytesWritten;
} while (bytesLeft > 0);

self.progressCallback(self.expectedContentLength, self.receivedContentLength);

// bytesLeft should not be negative.
assert(bytesLeft >= 0);

if (bytesLeft) {
[self.outputFileStream close];
[connection cancel];
self.failCallback([self.outputFileStream streamError]);
}
}

- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
[self.outputFileStream close];
self.failCallback(error);
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
// We should have received all of the bytes if this is called.
assert(self.receivedContentLength == self.expectedContentLength);

[self.outputFileStream close];
self.doneCallback();
}

@end
81 changes: 47 additions & 34 deletions CodePushPackage.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@implementation CodePushPackage

NSString * const StatusFile = @"codepush.json";
NSString * const UpdateBundleFileName = @"app.jsbundle";

+ (NSString *)getCodePushPath
{
Expand Down Expand Up @@ -72,6 +73,17 @@ + (NSString *)getCurrentPackageFolderPath:(NSError **)error
return [self getPackageFolderPath:packageHash];
}

+ (NSString *)getCurrentPackageBundlePath:(NSError **)error
{
NSString *packageFolder = [self getCurrentPackageFolderPath:error];

if(*error) {
return NULL;
}

return [packageFolder stringByAppendingPathComponent:UpdateBundleFileName];
}

+ (NSString *)getCurrentPackageHash:(NSError **)error
{
NSDictionary *info = [self getCurrentPackageInfo:error];
Expand Down Expand Up @@ -149,50 +161,51 @@ + (NSString *)getPackageFolderPath:(NSString *)packageHash
}

+ (void)downloadPackage:(NSDictionary *)updatePackage
error:(NSError **)error
progressCallback:(void (^)(long, long))progressCallback
doneCallback:(void (^)())doneCallback
failCallback:(void (^)(NSError *err))failCallback
{
NSString *packageFolderPath = [self getPackageFolderPath:updatePackage[@"packageHash"]];
NSError *error = nil;

if (![[NSFileManager defaultManager] fileExistsAtPath:packageFolderPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:packageFolderPath
withIntermediateDirectories:YES
attributes:nil
error:error];
}

if (*error) {
return;
}

NSURL *url = [[NSURL alloc] initWithString:updatePackage[@"downloadUrl"]];
NSString *updateContents = [[NSString alloc] initWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:error];
if (*error) {
return;
error:&error];
}

[updateContents writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.jsbundle"]
atomically:YES
encoding:NSUTF8StringEncoding
error:error];
if (*error) {
return;
}

NSData *updateSerializedData = [NSJSONSerialization dataWithJSONObject:updatePackage
options:0
error:error];

if (*error) {
return;
}
if (error) {
return failCallback(error);
}

NSString *downloadFilePath = [packageFolderPath stringByAppendingPathComponent:UpdateBundleFileName];

CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc]
init:downloadFilePath
progressCallback:progressCallback
doneCallback:^{
NSError *error;
NSData *updateSerializedData = [NSJSONSerialization
dataWithJSONObject:updatePackage
options:0
error:&error];
NSString *packageJsonString = [[NSString alloc]
initWithData:updateSerializedData encoding:NSUTF8StringEncoding];

[packageJsonString writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.json"]
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
failCallback(error);
} else {
doneCallback();
}
}
failCallback:failCallback];

NSString *packageJsonString = [[NSString alloc] initWithData:updateSerializedData encoding:NSUTF8StringEncoding];
[packageJsonString writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.json"]
atomically:YES
encoding:NSUTF8StringEncoding
error:error];
[downloadHandler download:updatePackage[@"downloadUrl"]];
}

+ (void)applyPackage:(NSDictionary *)updatePackage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
5451ACBA1B86A5B600E2A7DF /* QueryUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */; };
5451ACEC1B86E40A00E2A7DF /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5451ACEB1B86E34300E2A7DF /* libRCTTest.a */; };
54D774BA1B87DAF800F2ABF8 /* ApplyUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */; };
54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */; settings = {ASSET_TAGS = (); }; };
81551E1B1B3B428000F5B9F1 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81551E0F1B3B427200F5B9F1 /* libCodePush.a */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -151,6 +152,7 @@
5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QueryUpdateTests.m; sourceTree = "<group>"; };
5451ACE61B86E34300E2A7DF /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = "node_modules/react-native/Libraries/RCTTest/RCTTest.xcodeproj"; sourceTree = "<group>"; };
54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApplyUpdateTests.m; sourceTree = "<group>"; };
54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownloadProgressTests.m; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CodePush.xcodeproj; path = ../../CodePush.xcodeproj; sourceTree = "<group>"; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -238,6 +240,7 @@
00E356EF1AD99517003FC87E /* CodePushDemoAppTests */ = {
isa = PBXGroup;
children = (
54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */,
5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */,
54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
Expand Down Expand Up @@ -608,6 +611,7 @@
buildActionMask = 2147483647;
files = (
5451ACBA1B86A5B600E2A7DF /* QueryUpdateTests.m in Sources */,
54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */,
54D774BA1B87DAF800F2ABF8 /* ApplyUpdateTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var DownloadAndApplyUpdateTest = React.createClass({
runTest() {
var update = require("./TestPackage");
NativeBridge.downloadUpdate(update).done((downloadedPackage) => {
NativeBridge.applyUpdate(downloadedPackage, 1000);
NativeBridge.applyUpdate(downloadedPackage, /*rollbackTimeout*/ 1000, /*restartImmediately*/ true);
});
},

Expand Down
Loading

0 comments on commit 92112ab

Please sign in to comment.